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
117 changes: 107 additions & 10 deletions src/lib/Svedit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -1175,16 +1175,52 @@ ${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') {
// 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 (let i = 0; i < node.childNodes.length; i++) {
processNode(node.childNodes[i], node, i);
}
}
}
return (focus_offset !== 0);
}

// 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
Expand Down Expand Up @@ -1338,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);
Expand Down Expand Up @@ -1375,8 +1411,69 @@ ${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) {
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) {
if (current_offset === end_offset) {
// Selection ends before the inline node
anchor_node = parent;
anchor_node_offset = indexInParent;
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
}
}
} else {
// For forward selection, check anchor_node first (start_offset)
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) {
if (current_offset === end_offset) {
// Selection ends before the inline node
focus_node = parent;
focus_node_offset = indexInParent;
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
}
}
}

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
Expand All @@ -1392,8 +1489,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;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, PropertyDefinition>;
};

Expand Down
15 changes: 15 additions & 0 deletions src/routes/components/Dino.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
import { getContext } from 'svelte';
const svedit = getContext('svedit');
let { path } = $props();
let node = $derived(svedit.doc.get(path));
</script>

<!-- TEMPORARY HACK: Mark the node as inline node, so selection mapping can consider it as an inline node with a char length of 1 -->
<img data-annotation-type="inline" data-node-id={node.id} src="https://prosemirror.net/img/dino/{node.kind}.png" alt="Tyrannosaurus" />

<style>
img {
display: inline-block;
}
</style>
25 changes: 24 additions & 1 deletion src/routes/create_demo_doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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';
Expand All @@ -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',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -441,6 +463,7 @@ const document_config = {
ImageGrid,
ImageGridItem,
Hero,
Dino,
Strong,
Emphasis,
Highlight,
Expand Down