From d7c66033240c22273b7b7c172338b0751ef00e14 Mon Sep 17 00:00:00 2001 From: Michael Aufreiter Date: Tue, 14 Oct 2025 13:40:39 +0200 Subject: [PATCH 1/4] Experimental support for inline nodes. --- src/lib/Svedit.svelte | 9 +++++++-- src/lib/types.d.ts | 2 +- src/routes/components/Dino.svelte | 15 +++++++++++++++ src/routes/create_demo_doc.js | 25 ++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 src/routes/components/Dino.svelte diff --git a/src/lib/Svedit.svelte b/src/lib/Svedit.svelte index 926653f1..ee250e6b 100644 --- a/src/lib/Svedit.svelte +++ b/src/lib/Svedit.svelte @@ -1175,8 +1175,13 @@ ${fallback_html}`; } current_offset += nodeCharLength; } else if (node.nodeType === Node.ELEMENT_NODE) { - for (const childNode of node.childNodes) { - processNode(childNode); + if (node.dataset.annotationType === 'inline') { + console.log('YOYO'); + current_offset += 1; + } else { + for (const childNode of node.childNodes) { + processNode(childNode); + } } } return (focus_offset !== 0); diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts index e7b8345b..1933b297 100644 --- a/src/lib/types.d.ts +++ b/src/lib/types.d.ts @@ -173,7 +173,7 @@ export type TextNodeSchema = { * Schema for non-text nodes */ export type NonTextNodeSchema = { - kind: 'document' | 'block' | 'annotation'; + kind: 'document' | 'block' | 'annotation' | 'inline'; properties: Record; }; diff --git a/src/routes/components/Dino.svelte b/src/routes/components/Dino.svelte new file mode 100644 index 00000000..22cd9dca --- /dev/null +++ b/src/routes/components/Dino.svelte @@ -0,0 +1,15 @@ + + + +Tyrannosaurus + + diff --git a/src/routes/create_demo_doc.js b/src/routes/create_demo_doc.js index c242e3a1..7332b06f 100644 --- a/src/routes/create_demo_doc.js +++ b/src/routes/create_demo_doc.js @@ -15,6 +15,7 @@ import ListItem from './components/ListItem.svelte'; import ImageGrid from './components/ImageGrid.svelte'; import ImageGridItem from './components/ImageGridItem.svelte'; import Hero from './components/Hero.svelte'; +import Dino from './components/Dino.svelte'; import Strong from './components/Strong.svelte'; import Emphasis from './components/Emphasis.svelte'; import Highlight from './components/Highlight.svelte'; @@ -155,6 +156,12 @@ const document_schema = define_document_schema({ layout: { type: 'integer', default: 1 }, } }, + dino: { + kind: 'inline', + properties: { + kind: { type: 'string' } + } + }, link: { kind: 'annotation', properties: { @@ -193,6 +200,7 @@ const list_item_1_id = 'list_item_1'; const list_item_2_id = 'list_item_2'; const list_item_3_id = 'list_item_3'; const list_item_4_id = 'list_item_4'; +const dino_1_id = 'dino_1'; const emphasis_1_id = 'emphasis_1'; const link_1_id = 'link_1'; const link_2_id = 'link_2'; @@ -210,6 +218,11 @@ const image_grid_item_5_id = 'image_grid_item_5'; const image_grid_item_6_id = 'image_grid_item_6'; const serialized_doc = [ + { + id: dino_1_id, + type: 'dino', + kind: 'tyrannosaurus' + }, { id: emphasis_1_id, type: 'emphasis', @@ -251,7 +264,16 @@ const serialized_doc = [ id: heading_1_id, type: 'text', layout: 2, - content: {text: 'Text and structured content in symbiosis', annotations: []} + content: { + text: 'Text and structured content in symbiosis', + annotations: [ + { + "start_offset": 3, + "end_offset": 4, + "node_id": dino_1_id + } + ] + } }, { id: paragraph_1_id, @@ -441,6 +463,7 @@ const document_config = { ImageGrid, ImageGridItem, Hero, + Dino, Strong, Emphasis, Highlight, From f2cdd2b39cd8df9d3ce8d5fbd4dc78b1a9ebfcc9 Mon Sep 17 00:00:00 2001 From: Michael Aufreiter Date: Tue, 14 Oct 2025 13:52:01 +0200 Subject: [PATCH 2/4] Make __get_text_selection consider inline nodes with arbitaray structure. --- src/lib/Svedit.svelte | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/lib/Svedit.svelte b/src/lib/Svedit.svelte index ee250e6b..761e8c3f 100644 --- a/src/lib/Svedit.svelte +++ b/src/lib/Svedit.svelte @@ -1159,7 +1159,7 @@ ${fallback_html}`; let focus_offset = 0; let current_offset = 0; - function processNode(node) { + function processNode(node, parent = null, indexInParent = -1) { if (node.nodeType === Node.TEXT_NODE) { const nodeText = node.textContent; const nodeCharLength = get_char_length(nodeText); @@ -1176,11 +1176,42 @@ ${fallback_html}`; current_offset += nodeCharLength; } else if (node.nodeType === Node.ELEMENT_NODE) { if (node.dataset.annotationType === 'inline') { - console.log('YOYO'); + // Inline node is treated as a single character + // Check if selection boundaries are related to this inline node + + // Case 1: startContainer is the parent and startOffset points to this node + if (range.startContainer === parent && range.startOffset === indexInParent) { + anchor_offset = current_offset; + } + // Case 2: endContainer is the parent and endOffset points to this node + if (range.endContainer === parent && range.endOffset === indexInParent) { + focus_offset = current_offset; + } + // Case 3: startContainer is the parent and startOffset points after this node + if (range.startContainer === parent && range.startOffset === indexInParent + 1) { + anchor_offset = current_offset + 1; + } + // Case 4: endContainer is the parent and endOffset points after this node + if (range.endContainer === parent && range.endOffset === indexInParent + 1) { + focus_offset = current_offset + 1; + } + // Case 5: startContainer is the inline node itself or a child + if (range.startContainer === node || node.contains(range.startContainer)) { + // If offset is 0 or at the start, position before the inline node + // Otherwise position after it + anchor_offset = range.startOffset === 0 ? current_offset : current_offset + 1; + } + // Case 6: endContainer is the inline node itself or a child + if (range.endContainer === node || node.contains(range.endContainer)) { + // If offset is 0 or at the start, position before the inline node + // Otherwise position after it + focus_offset = range.endOffset === 0 ? current_offset : current_offset + 1; + } + current_offset += 1; } else { - for (const childNode of node.childNodes) { - processNode(childNode); + for (let i = 0; i < node.childNodes.length; i++) { + processNode(node.childNodes[i], node, i); } } } @@ -1188,8 +1219,8 @@ ${fallback_html}`; } // Process nodes to find offsets - for (const childNode of focus_root.childNodes) { - if (processNode(childNode)) break; + for (let i = 0; i < focus_root.childNodes.length; i++) { + if (processNode(focus_root.childNodes[i], focus_root, i)) break; } // Check if it's a backward selection From a3483ce5c7312846a479c256062a81c4d24a405c Mon Sep 17 00:00:00 2001 From: Michael Aufreiter Date: Tue, 14 Oct 2025 14:01:47 +0200 Subject: [PATCH 3/4] Make __render_text_selection consider inline nodes. --- src/lib/Svedit.svelte | 59 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/lib/Svedit.svelte b/src/lib/Svedit.svelte index 761e8c3f..662e244b 100644 --- a/src/lib/Svedit.svelte +++ b/src/lib/Svedit.svelte @@ -1374,7 +1374,7 @@ ${fallback_html}`; const end_offset = Math.max(selection.anchor_offset, selection.focus_offset); // Helper function to process each node - function process_node(node) { + function process_node(node, parent = null, indexInParent = -1) { if (node.nodeType === Node.TEXT_NODE) { const node_text = node.textContent; const node_char_length = get_char_length(node_text); @@ -1411,8 +1411,57 @@ ${fallback_html}`; } current_offset += node_char_length; } else if (node.nodeType === Node.ELEMENT_NODE) { - for (const child_node of node.childNodes) { - if (process_node(child_node)) return true; // Stop iteration if end found + if (node.dataset.annotationType === 'inline') { + // Inline node is treated as a single character + // Check if selection boundaries fall on this inline node + + if (is_backward) { + // For backward selection, check focus_node first (start_offset) + if (!focus_node && current_offset === start_offset) { + // Selection starts before the inline node + focus_node = parent; + focus_node_offset = indexInParent; + } + if (!anchor_node && current_offset + 1 >= end_offset) { + // Selection ends at or after the inline node + if (current_offset === end_offset) { + // Ends before the inline node + anchor_node = parent; + anchor_node_offset = indexInParent; + } else { + // Ends after the inline node + anchor_node = parent; + anchor_node_offset = indexInParent + 1; + } + return true; // Stop iteration + } + } else { + // For forward selection, check anchor_node first (start_offset) + if (!anchor_node && current_offset === start_offset) { + // Selection starts before the inline node + anchor_node = parent; + anchor_node_offset = indexInParent; + } + if (!focus_node && current_offset + 1 >= end_offset) { + // Selection ends at or after the inline node + if (current_offset === end_offset) { + // Ends before the inline node + focus_node = parent; + focus_node_offset = indexInParent; + } else { + // Ends after the inline node + focus_node = parent; + focus_node_offset = indexInParent + 1; + } + return true; // Stop iteration + } + } + + current_offset += 1; + } else { + for (let i = 0; i < node.childNodes.length; i++) { + if (process_node(node.childNodes[i], node, i)) return true; // Stop iteration if end found + } } } return false; // Continue iteration @@ -1428,8 +1477,8 @@ ${fallback_html}`; focus_node_offset = 1; } else { // DEFAULT CASE - for (const child_node of el.childNodes) { - if (process_node(child_node)) break; + for (let i = 0; i < el.childNodes.length; i++) { + if (process_node(el.childNodes[i], el, i)) break; } } From a65ab5c880e95028e15730fc73df8225da566e78 Mon Sep 17 00:00:00 2001 From: Michael Aufreiter Date: Tue, 14 Oct 2025 14:09:42 +0200 Subject: [PATCH 4/4] Fix selection rendering for inline nodes. --- src/lib/Svedit.svelte | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/lib/Svedit.svelte b/src/lib/Svedit.svelte index 662e244b..aa0542e3 100644 --- a/src/lib/Svedit.svelte +++ b/src/lib/Svedit.svelte @@ -1417,43 +1417,55 @@ ${fallback_html}`; if (is_backward) { // For backward selection, check focus_node first (start_offset) - if (!focus_node && current_offset === start_offset) { - // Selection starts before the inline node - focus_node = parent; - focus_node_offset = indexInParent; + if (!focus_node) { + if (current_offset === start_offset) { + // Selection starts before the inline node + focus_node = parent; + focus_node_offset = indexInParent; + } else if (current_offset + 1 === start_offset) { + // Selection starts after the inline node + focus_node = parent; + focus_node_offset = indexInParent + 1; + } } - if (!anchor_node && current_offset + 1 >= end_offset) { - // Selection ends at or after the inline node + if (!anchor_node) { if (current_offset === end_offset) { - // Ends before the inline node + // Selection ends before the inline node anchor_node = parent; anchor_node_offset = indexInParent; - } else { - // Ends after the inline node + return true; // Stop iteration + } else if (current_offset + 1 === end_offset) { + // Selection ends after the inline node anchor_node = parent; anchor_node_offset = indexInParent + 1; + return true; // Stop iteration } - return true; // Stop iteration } } else { // For forward selection, check anchor_node first (start_offset) - if (!anchor_node && current_offset === start_offset) { - // Selection starts before the inline node - anchor_node = parent; - anchor_node_offset = indexInParent; + if (!anchor_node) { + if (current_offset === start_offset) { + // Selection starts before the inline node + anchor_node = parent; + anchor_node_offset = indexInParent; + } else if (current_offset + 1 === start_offset) { + // Selection starts after the inline node + anchor_node = parent; + anchor_node_offset = indexInParent + 1; + } } - if (!focus_node && current_offset + 1 >= end_offset) { - // Selection ends at or after the inline node + if (!focus_node) { if (current_offset === end_offset) { - // Ends before the inline node + // Selection ends before the inline node focus_node = parent; focus_node_offset = indexInParent; - } else { - // Ends after the inline node + return true; // Stop iteration + } else if (current_offset + 1 === end_offset) { + // Selection ends after the inline node focus_node = parent; focus_node_offset = indexInParent + 1; + return true; // Stop iteration } - return true; // Stop iteration } }