@portabletext/editor
Version:
Portable Text Editor made in React
322 lines (321 loc) • 11.7 kB
JavaScript
"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