diff --git a/src/lib/Command.svelte.js b/src/lib/Command.svelte.js index e90d05a6..9a1b62bd 100644 --- a/src/lib/Command.svelte.js +++ b/src/lib/Command.svelte.js @@ -1,4 +1,8 @@ -import { insert_default_node, break_text_node } from './transforms.svelte.js'; +import { + insert_default_node, + insert_default_node_before, + break_text_node +} from './transforms.svelte.js'; import { can_switch_annotation_type } from './doc_utils.js'; import { is_selection_collapsed, is_mobile_browser, get_char_length } from './utils.js'; @@ -327,3 +331,26 @@ export class InsertDefaultNodeCommand extends Command { this.context.session.apply(tr); } } + +/** + * Command that inserts a default node before the current node. + * Only works in text selections, where "the current node" is well defined. + */ +export class InsertDefaultNodeBeforeCommand extends Command { + is_enabled() { + const selection = this.context.session.selection; + if (!this.context.editable) return false; + if (selection?.type === 'text') return true; + if (selection?.type === 'node' && selection.anchor_offset === selection.focus_offset) { + return selection.anchor_offset > 0; + } + return false; + } + + execute() { + const tr = this.context.session.tr; + if (insert_default_node_before(tr)) { + this.context.session.apply(tr); + } + } +} diff --git a/src/lib/transforms.svelte.js b/src/lib/transforms.svelte.js index 09822729..9b557349 100644 --- a/src/lib/transforms.svelte.js +++ b/src/lib/transforms.svelte.js @@ -163,3 +163,45 @@ export function insert_default_node(tr) { throw new Error(`No inserter function available for default node type '${default_type}'`); } } + +export function insert_default_node_before(tr) { + const selection = tr.selection; + + let node_array_path; + let insert_offset; + + if (selection?.type === 'text') { + // "Current node" is the node containing the text. Insert at its index. + const node_path = selection.path.slice(0, -1); + node_array_path = node_path.slice(0, -1); + if (tr.inspect(node_array_path)?.type !== 'node_array') return false; + insert_offset = parseInt(node_path.at(-1), 10); + } else if (selection?.type === 'node' && selection.anchor_offset === selection.focus_offset) { + // In a node gap, "current node" is the node above the caret (offset - 1). + // Inserting before it means inserting at offset - 1. + if (selection.anchor_offset === 0) return false; + node_array_path = selection.path; + insert_offset = selection.anchor_offset - 1; + } else { + return false; + } + + const node_array_node = tr.get(node_array_path.slice(0, -1)); + const property_name = node_array_path.at(-1); + const property_definition = tr.schema[node_array_node.type].properties[property_name]; + const default_type = get_default_node_type(property_definition); + + if (!tr.config?.inserters?.[default_type]) { + throw new Error(`No inserter function available for default node type '${default_type}'`); + } + + tr.set_selection({ + type: 'node', + path: node_array_path, + anchor_offset: insert_offset, + focus_offset: insert_offset + }); + + tr.config.inserters[default_type](tr); + return true; +} diff --git a/src/routes/create_demo_session.js b/src/routes/create_demo_session.js index 3faa50eb..d5c40c3f 100644 --- a/src/routes/create_demo_session.js +++ b/src/routes/create_demo_session.js @@ -2,6 +2,7 @@ import { Session, SelectAllCommand, InsertDefaultNodeCommand, + InsertDefaultNodeBeforeCommand, AddNewLineCommand, BreakTextNodeCommand, ToggleAnnotationCommand, @@ -751,6 +752,7 @@ export const session_config = { const commands = { select_all: new SelectAllCommand(context), insert_default_node: new InsertDefaultNodeCommand(context), + insert_default_node_before: new InsertDefaultNodeBeforeCommand(context), add_new_line: new AddNewLineCommand(context), break_text_node: new BreakTextNodeCommand(context), toggle_strong: new ToggleAnnotationCommand('strong', context), @@ -773,6 +775,7 @@ export const session_config = { // In case of a node caret, fall back to inserting a default node. This is needed // because on iOS selecting a node caret triggers auto capitalization (shift pressed) 'shift+enter': [commands.add_new_line, commands.insert_default_node], + 'meta+shift+enter,ctrl+shift+enter': [commands.insert_default_node_before], 'meta+b,ctrl+b': [commands.toggle_strong], 'meta+i,ctrl+i': [commands.toggle_emphasis], 'meta+u,ctrl+u': [commands.toggle_highlight],