UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

322 lines (321 loc) 11.7 kB
"use strict"; var types = require("@sanity/types"), selector_isOverlappingSelection = require("./selector.is-overlapping-selection.cjs"), util_sliceBlocks = require("./util.slice-blocks.cjs"), selector_getTextBefore = require("./selector.get-text-before.cjs"), behavior_core = require("./behavior.core.cjs"); function createMarkdownBehaviors(config) { const automaticBlockquoteOnSpace = behavior_core.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selector_isOverlappingSelection.isSelectionCollapsed(snapshot), focusTextBlock = selector_isOverlappingSelection.getFocusTextBlock(snapshot), focusSpan = selector_isOverlappingSelection.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const previousInlineObject = selector_isOverlappingSelection.getPreviousInlineObject(snapshot), blockOffset = util_sliceBlocks.spanSelectionPointToBlockOffset({ value: snapshot.context.value, selectionPoint: { path: [{ _key: focusTextBlock.node._key }, "children", { _key: focusSpan.node._key }], offset: snapshot.context.selection?.focus.offset ?? 0 } }); if (previousInlineObject || !blockOffset) return !1; const blockText = util_sliceBlocks.getTextBlockText(focusTextBlock.node), caretAtTheEndOfQuote = blockOffset.offset === 1, looksLikeMarkdownQuote = /^>/.test(blockText), blockquoteStyle = config.blockquoteStyle?.(snapshot.context); return caretAtTheEndOfQuote && looksLikeMarkdownQuote && blockquoteStyle !== void 0 ? { focusTextBlock, style: blockquoteStyle } : !1; }, actions: [() => [behavior_core.execute({ type: "insert.text", text: " " })], (_, { focusTextBlock, style }) => [behavior_core.execute({ type: "block.unset", props: ["listItem", "level"], at: focusTextBlock.path }), behavior_core.execute({ type: "block.set", props: { style }, at: focusTextBlock.path }), behavior_core.execute({ type: "delete.text", at: { anchor: { path: focusTextBlock.path, offset: 0 }, focus: { path: focusTextBlock.path, offset: 2 } } })]] }), automaticHr = behavior_core.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { const hrCharacter = event.text === "-" ? "-" : event.text === "*" ? "*" : event.text === "_" ? "_" : void 0; if (hrCharacter === void 0) return !1; const hrObject = config.horizontalRuleObject?.(snapshot.context), focusBlock = selector_isOverlappingSelection.getFocusTextBlock(snapshot), selectionCollapsed = selector_isOverlappingSelection.isSelectionCollapsed(snapshot); if (!hrObject || !focusBlock || !selectionCollapsed) return !1; const previousInlineObject = selector_isOverlappingSelection.getPreviousInlineObject(snapshot), textBefore = selector_getTextBefore.getBlockTextBefore(snapshot), hrBlockOffsets = { anchor: { path: focusBlock.path, offset: 0 }, focus: { path: focusBlock.path, offset: 3 } }; return !previousInlineObject && textBefore === `${hrCharacter}${hrCharacter}` ? { hrObject, focusBlock, hrCharacter, hrBlockOffsets } : !1; }, actions: [(_, { hrCharacter }) => [behavior_core.execute({ type: "insert.text", text: hrCharacter })], (_, { hrObject, hrBlockOffsets }) => [behavior_core.execute({ type: "insert.block", placement: "before", block: { _type: hrObject.name, ...hrObject.value ?? {} } }), behavior_core.execute({ type: "delete.text", at: hrBlockOffsets })]] }), automaticHrOnPaste = behavior_core.defineBehavior({ on: "clipboard.paste", guard: ({ snapshot, event }) => { const text = event.originEvent.dataTransfer.getData("text/plain"), hrRegExp = /^(---)$|(___)$|(\*\*\*)$/, hrCharacters = text.match(hrRegExp)?.[0], hrObject = config.horizontalRuleObject?.(snapshot.context), focusBlock = selector_isOverlappingSelection.getFocusBlock(snapshot); return !hrCharacters || !hrObject || !focusBlock ? !1 : { hrCharacters, hrObject, focusBlock }; }, actions: [(_, { hrCharacters }) => [behavior_core.execute({ type: "insert.text", text: hrCharacters })], ({ snapshot }, { hrObject, focusBlock }) => types.isPortableTextTextBlock(focusBlock.node) ? [behavior_core.execute({ type: "insert.block", block: { _type: snapshot.context.schema.block.name, children: focusBlock.node.children }, placement: "after" }), behavior_core.execute({ type: "insert.block", block: { _type: hrObject.name, ...hrObject.value ?? {} }, placement: "after" }), behavior_core.execute({ type: "delete.block", at: focusBlock.path })] : [behavior_core.execute({ type: "insert.block", block: { _type: hrObject.name, ...hrObject.value ?? {} }, placement: "after" })]] }), automaticHeadingOnSpace = behavior_core.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selector_isOverlappingSelection.isSelectionCollapsed(snapshot), focusTextBlock = selector_isOverlappingSelection.getFocusTextBlock(snapshot), focusSpan = selector_isOverlappingSelection.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const blockOffset = util_sliceBlocks.spanSelectionPointToBlockOffset({ value: snapshot.context.value, selectionPoint: { path: [{ _key: focusTextBlock.node._key }, "children", { _key: focusSpan.node._key }], offset: snapshot.context.selection?.focus.offset ?? 0 } }); if (!blockOffset) return !1; const previousInlineObject = selector_isOverlappingSelection.getPreviousInlineObject(snapshot), blockText = util_sliceBlocks.getTextBlockText(focusTextBlock.node), markdownHeadingSearch = /^#+/.exec(blockText), level = markdownHeadingSearch ? markdownHeadingSearch[0].length : void 0, caretAtTheEndOfHeading = blockOffset.offset === level; if (previousInlineObject || !caretAtTheEndOfHeading) return !1; const style = level !== void 0 ? config.headingStyle?.({ schema: snapshot.context.schema, level }) : void 0; return level !== void 0 && style !== void 0 ? { focusTextBlock, style, level } : !1; }, actions: [({ event }) => [behavior_core.execute(event)], (_, { focusTextBlock, style, level }) => [behavior_core.execute({ type: "block.unset", props: ["listItem", "level"], at: focusTextBlock.path }), behavior_core.execute({ type: "block.set", props: { style }, at: focusTextBlock.path }), behavior_core.execute({ type: "delete.text", at: { anchor: { path: focusTextBlock.path, offset: 0 }, focus: { path: focusTextBlock.path, offset: level + 1 } } })]] }), clearStyleOnBackspace = behavior_core.defineBehavior({ on: "delete.backward", guard: ({ snapshot }) => { const selectionCollapsed = selector_isOverlappingSelection.isSelectionCollapsed(snapshot), focusTextBlock = selector_isOverlappingSelection.getFocusTextBlock(snapshot), focusSpan = selector_isOverlappingSelection.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const atTheBeginningOfBLock = focusTextBlock.node.children[0]._key === focusSpan.node._key && snapshot.context.selection?.focus.offset === 0, defaultStyle = config.defaultStyle?.(snapshot.context); return atTheBeginningOfBLock && defaultStyle && focusTextBlock.node.style !== defaultStyle ? { defaultStyle, focusTextBlock } : !1; }, actions: [(_, { defaultStyle, focusTextBlock }) => [behavior_core.execute({ type: "block.set", props: { style: defaultStyle }, at: focusTextBlock.path })]] }), automaticListOnSpace = behavior_core.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selector_isOverlappingSelection.isSelectionCollapsed(snapshot), focusTextBlock = selector_isOverlappingSelection.getFocusTextBlock(snapshot), focusSpan = selector_isOverlappingSelection.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const previousInlineObject = selector_isOverlappingSelection.getPreviousInlineObject(snapshot), blockOffset = util_sliceBlocks.spanSelectionPointToBlockOffset({ value: snapshot.context.value, selectionPoint: { path: [{ _key: focusTextBlock.node._key }, "children", { _key: focusSpan.node._key }], offset: snapshot.context.selection?.focus.offset ?? 0 } }); if (previousInlineObject || !blockOffset) return !1; const blockText = util_sliceBlocks.getTextBlockText(focusTextBlock.node), defaultStyle = config.defaultStyle?.(snapshot.context), looksLikeUnorderedList = /^(-|\*)/.test(blockText), unorderedListStyle = config.unorderedListStyle?.(snapshot.context), caretAtTheEndOfUnorderedList = blockOffset.offset === 1; if (defaultStyle && caretAtTheEndOfUnorderedList && looksLikeUnorderedList && unorderedListStyle !== void 0) return { focusTextBlock, listItem: unorderedListStyle, listItemLength: 1, style: defaultStyle }; const looksLikeOrderedList = /^1\./.test(blockText), orderedListStyle = config.orderedListStyle?.(snapshot.context), caretAtTheEndOfOrderedList = blockOffset.offset === 2; return defaultStyle && caretAtTheEndOfOrderedList && looksLikeOrderedList && orderedListStyle !== void 0 ? { focusTextBlock, listItem: orderedListStyle, listItemLength: 2, style: defaultStyle } : !1; }, actions: [({ event }) => [behavior_core.execute(event)], (_, { focusTextBlock, style, listItem, listItemLength }) => [behavior_core.execute({ type: "block.set", props: { listItem, level: 1, style }, at: focusTextBlock.path }), behavior_core.execute({ type: "delete.text", at: { anchor: { path: focusTextBlock.path, offset: 0 }, focus: { path: focusTextBlock.path, offset: listItemLength + 1 } } })]] }); return [automaticBlockquoteOnSpace, automaticHeadingOnSpace, automaticHr, automaticHrOnPaste, clearStyleOnBackspace, automaticListOnSpace]; } exports.createMarkdownBehaviors = createMarkdownBehaviors; //# sourceMappingURL=behavior.markdown.cjs.map