@portabletext/plugin-markdown-shortcuts
Version:
Adds helpful Markdown shortcuts to the editor
393 lines (392 loc) • 14.8 kB
JavaScript
"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