Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/lib/Command.svelte.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { insert_default_node, break_text_node } from './transforms.svelte.js';
import { insert_default_node, break_text_node, exit_break } 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';

Expand Down Expand Up @@ -327,3 +327,21 @@ export class InsertDefaultNodeCommand extends Command {
this.context.session.apply(tr);
}
}

/**
* Exit-break command: escapes the current node property or nested node_array
* and inserts a new default node after the outermost ancestor in the
* document's top-level node_array.
*/
export class ExitBreakCommand extends Command {
is_enabled() {
return this.context.editable && !!this.context.session.selection;
}

execute() {
const tr = this.context.session.tr;
if (exit_break(tr)) {
this.context.session.apply(tr);
}
}
}
49 changes: 49 additions & 0 deletions src/lib/transforms.svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,52 @@ export function insert_default_node(tr) {
throw new Error(`No inserter function available for default node type '${default_type}'`);
}
}

/**
* Exit-break: leaves the current node property or nested node_array and inserts
* a new default node after the outermost ancestor that's inside the document's
* top-level node_array. Useful for escaping nested content like list items.
*/
export function exit_break(tr) {
const selection = tr.selection;
if (!selection?.path || selection.path.length < 3) return false;

const path = selection.path;

// Walk the path from the root and find the OUTERMOST node_array.
// The path alternates between property names and array indices, so each
// even-length prefix ends on a property name we can inspect.
let outer_array_path = null;
let outer_node_index = null;

for (let i = 1; i < path.length - 1; i++) {
const candidate = path.slice(0, i + 1);
if (tr.inspect(candidate)?.type === 'node_array') {
outer_array_path = candidate;
outer_node_index = parseInt(path[i + 1], 10);
break;
}
}

if (!outer_array_path || Number.isNaN(outer_node_index)) return false;

const insert_offset = outer_node_index + 1;
const node_array_node = tr.get(outer_array_path.slice(0, -1));
const property_name = outer_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: outer_array_path,
anchor_offset: insert_offset,
focus_offset: insert_offset
});

tr.config.inserters[default_type](tr);
return true;
}
3 changes: 3 additions & 0 deletions src/routes/create_demo_session.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
InsertDefaultNodeCommand,
AddNewLineCommand,
BreakTextNodeCommand,
ExitBreakCommand,
ToggleAnnotationCommand,
UndoCommand,
RedoCommand,
Expand Down Expand Up @@ -753,6 +754,7 @@ export const session_config = {
insert_default_node: new InsertDefaultNodeCommand(context),
add_new_line: new AddNewLineCommand(context),
break_text_node: new BreakTextNodeCommand(context),
exit_break: new ExitBreakCommand(context),
toggle_strong: new ToggleAnnotationCommand('strong', context),
toggle_emphasis: new ToggleAnnotationCommand('emphasis', context),
toggle_highlight: new ToggleAnnotationCommand('highlight', context),
Expand All @@ -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+enter,ctrl+enter': [commands.exit_break],
'meta+b,ctrl+b': [commands.toggle_strong],
'meta+i,ctrl+i': [commands.toggle_emphasis],
'meta+u,ctrl+u': [commands.toggle_highlight],
Expand Down