UNPKG

@portabletext/plugin-markdown-shortcuts

Version:

Adds helpful Markdown shortcuts to the editor

393 lines (392 loc) 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: !0 }); var jsxRuntime = require("react/jsx-runtime"), editor = require("@portabletext/editor"), pluginCharacterPairDecorator = require("@portabletext/plugin-character-pair-decorator"), react = require("react"), behaviors = require("@portabletext/editor/behaviors"), selectors = require("@portabletext/editor/selectors"), utils = require("@portabletext/editor/utils"); function _interopNamespaceCompat(e) { if (e && typeof e == "object" && "default" in e) return e; var n = /* @__PURE__ */ Object.create(null); return e && Object.keys(e).forEach(function(k) { if (k !== "default") { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: !0, get: function() { return e[k]; } }); } }), n.default = e, Object.freeze(n); } var selectors__namespace = /* @__PURE__ */ _interopNamespaceCompat(selectors), utils__namespace = /* @__PURE__ */ _interopNamespaceCompat(utils); function createMarkdownBehaviors(config) { const automaticBlockquoteOnSpace = behaviors.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selectors__namespace.isSelectionCollapsed(snapshot), focusTextBlock = selectors__namespace.getFocusTextBlock(snapshot), focusSpan = selectors__namespace.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const previousInlineObject = selectors__namespace.getPreviousInlineObject(snapshot), blockOffset = utils__namespace.spanSelectionPointToBlockOffset({ context: snapshot.context, 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 = utils__namespace.getTextBlockText(focusTextBlock.node), caretAtTheEndOfQuote = blockOffset.offset === 1, looksLikeMarkdownQuote = /^>/.test(blockText), blockquoteStyle = config.blockquoteStyle?.({ schema: snapshot.context.schema }); return caretAtTheEndOfQuote && looksLikeMarkdownQuote && blockquoteStyle !== void 0 ? { focusTextBlock, style: blockquoteStyle } : !1; }, actions: [ () => [ behaviors.execute({ type: "insert.text", text: " " }) ], (_, { focusTextBlock, style }) => [ behaviors.execute({ type: "block.unset", props: ["listItem", "level"], at: focusTextBlock.path }), behaviors.execute({ type: "block.set", props: { style }, at: focusTextBlock.path }), behaviors.execute({ type: "delete.text", at: { anchor: { path: focusTextBlock.path, offset: 0 }, focus: { path: focusTextBlock.path, offset: 2 } } }) ] ] }), automaticHr = behaviors.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?.({ schema: snapshot.context.schema }), focusBlock = selectors__namespace.getFocusTextBlock(snapshot), selectionCollapsed = selectors__namespace.isSelectionCollapsed(snapshot); if (!hrObject || !focusBlock || !selectionCollapsed) return !1; const previousInlineObject = selectors__namespace.getPreviousInlineObject(snapshot), textBefore = selectors__namespace.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 }) => [ behaviors.execute({ type: "insert.text", text: hrCharacter }) ], (_, { hrObject, hrBlockOffsets }) => [ behaviors.execute({ type: "insert.block", block: { _type: hrObject.name, ...hrObject.value ?? {} }, placement: "before", select: "none" }), behaviors.execute({ type: "delete.text", at: hrBlockOffsets }) ] ] }), automaticHrOnPaste = behaviors.defineBehavior({ on: "clipboard.paste", guard: ({ snapshot, event }) => { const text = event.originEvent.dataTransfer.getData("text/plain"), hrRegExp = /^(---)$|(___)$|(\*\*\*)$/, hrCharacters = text.match(hrRegExp)?.[0], hrObject = config.horizontalRuleObject?.({ schema: snapshot.context.schema }), focusBlock = selectors__namespace.getFocusBlock(snapshot), focusTextBlock = selectors__namespace.getFocusTextBlock(snapshot); return !hrCharacters || !hrObject || !focusBlock ? !1 : { hrCharacters, hrObject, focusBlock, focusTextBlock }; }, actions: [ (_, { hrCharacters }) => [ behaviors.execute({ type: "insert.text", text: hrCharacters }) ], ({ snapshot }, { hrObject, focusBlock, focusTextBlock }) => focusTextBlock ? [ behaviors.execute({ type: "insert.block", block: { _type: snapshot.context.schema.block.name, children: focusTextBlock.node.children }, placement: "after" }), behaviors.execute({ type: "insert.block", block: { _type: hrObject.name, ...hrObject.value ?? {} }, placement: "after" }), behaviors.execute({ type: "delete.block", at: focusBlock.path }) ] : [ behaviors.execute({ type: "insert.block", block: { _type: hrObject.name, ...hrObject.value ?? {} }, placement: "after" }) ] ] }), automaticHeadingOnSpace = behaviors.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selectors__namespace.isSelectionCollapsed(snapshot), focusTextBlock = selectors__namespace.getFocusTextBlock(snapshot), focusSpan = selectors__namespace.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const blockOffset = utils__namespace.spanSelectionPointToBlockOffset({ context: snapshot.context, selectionPoint: { path: [ { _key: focusTextBlock.node._key }, "children", { _key: focusSpan.node._key } ], offset: snapshot.context.selection?.focus.offset ?? 0 } }); if (!blockOffset) return !1; const previousInlineObject = selectors__namespace.getPreviousInlineObject(snapshot), blockText = utils__namespace.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 }) => [behaviors.execute(event)], (_, { focusTextBlock, style, level }) => [ behaviors.execute({ type: "block.unset", props: ["listItem", "level"], at: focusTextBlock.path }), behaviors.execute({ type: "block.set", props: { style }, at: focusTextBlock.path }), behaviors.execute({ type: "delete.text", at: { anchor: { path: focusTextBlock.path, offset: 0 }, focus: { path: focusTextBlock.path, offset: level + 1 } } }) ] ] }), clearStyleOnBackspace = behaviors.defineBehavior({ on: "delete.backward", guard: ({ snapshot }) => { const selectionCollapsed = selectors__namespace.isSelectionCollapsed(snapshot), focusTextBlock = selectors__namespace.getFocusTextBlock(snapshot), focusSpan = selectors__namespace.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?.({ schema: snapshot.context.schema }); return atTheBeginningOfBLock && defaultStyle && focusTextBlock.node.style !== defaultStyle ? { defaultStyle, focusTextBlock } : !1; }, actions: [ (_, { defaultStyle, focusTextBlock }) => [ behaviors.execute({ type: "block.set", props: { style: defaultStyle }, at: focusTextBlock.path }) ] ] }), automaticListOnSpace = behaviors.defineBehavior({ on: "insert.text", guard: ({ snapshot, event }) => { if (event.text !== " ") return !1; const selectionCollapsed = selectors__namespace.isSelectionCollapsed(snapshot), focusTextBlock = selectors__namespace.getFocusTextBlock(snapshot), focusSpan = selectors__namespace.getFocusSpan(snapshot); if (!selectionCollapsed || !focusTextBlock || !focusSpan) return !1; const previousInlineObject = selectors__namespace.getPreviousInlineObject(snapshot), blockOffset = utils__namespace.spanSelectionPointToBlockOffset({ context: snapshot.context, 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 = utils__namespace.getTextBlockText(focusTextBlock.node), defaultStyle = config.defaultStyle?.({ schema: snapshot.context.schema }), looksLikeUnorderedList = /^(-|\*)/.test(blockText), unorderedList = config.unorderedList?.({ schema: snapshot.context.schema }), caretAtTheEndOfUnorderedList = blockOffset.offset === 1; if (defaultStyle && caretAtTheEndOfUnorderedList && looksLikeUnorderedList && unorderedList !== void 0) return { focusTextBlock, listItem: unorderedList, listItemLength: 1, style: defaultStyle }; const looksLikeOrderedList = /^1\./.test(blockText), orderedList = config.orderedList?.({ schema: snapshot.context.schema }), caretAtTheEndOfOrderedList = blockOffset.offset === 2; return defaultStyle && caretAtTheEndOfOrderedList && looksLikeOrderedList && orderedList !== void 0 ? { focusTextBlock, listItem: orderedList, listItemLength: 2, style: defaultStyle } : !1; }, actions: [ ({ event }) => [behaviors.execute(event)], (_, { focusTextBlock, style, listItem, listItemLength }) => [ behaviors.execute({ type: "block.set", props: { listItem, level: 1, style }, at: focusTextBlock.path }), behaviors.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 ]; } function MarkdownShortcutsPlugin(props) { const editor$1 = editor.useEditor(); return react.useEffect(() => { const unregisterBehaviors = createMarkdownBehaviors(props).map( (behavior) => editor$1.registerBehavior({ behavior }) ); return () => { for (const unregisterBehavior of unregisterBehaviors) unregisterBehavior(); }; }, [editor$1, props]), /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ props.boldDecorator ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.boldDecorator, pair: { char: "*", amount: 2 } } ), /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.boldDecorator, pair: { char: "_", amount: 2 } } ) ] }) : null, props.codeDecorator ? /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.codeDecorator, pair: { char: "`", amount: 1 } } ) : null, props.italicDecorator ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.italicDecorator, pair: { char: "*", amount: 1 } } ), /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.italicDecorator, pair: { char: "_", amount: 1 } } ) ] }) : null, props.strikeThroughDecorator ? /* @__PURE__ */ jsxRuntime.jsx( pluginCharacterPairDecorator.CharacterPairDecoratorPlugin, { decorator: props.strikeThroughDecorator, pair: { char: "~", amount: 2 } } ) : null ] }); } exports.MarkdownShortcutsPlugin = MarkdownShortcutsPlugin; //# sourceMappingURL=index.cjs.map