svedit 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { split_annotated_string, join_annotated_string, get_char_length } from './util.js';
1
+ import { split_annotated_text, join_annotated_text, get_char_length } from './util.js';
2
2
  import { get_default_node_type } from './Document.svelte.js';
3
3
 
4
4
  export function break_text_node(tr) {
@@ -30,7 +30,7 @@ export function break_text_node(tr) {
30
30
 
31
31
  const split_at_position = tr.doc.selection.anchor_offset;
32
32
  const content = tr.doc.get(selection.path);
33
- const [left_text, right_text] = split_annotated_string(content, split_at_position);
33
+ const [left_text, right_text] = split_annotated_text(content, split_at_position);
34
34
 
35
35
  tr.set([node.id, 'content'], left_text);
36
36
 
@@ -42,7 +42,7 @@ export function break_text_node(tr) {
42
42
  };
43
43
 
44
44
  // TODO: Only use default_node_type when cursor is at the end of
45
- const node_array_property_definition = doc.schema[node_array_node.type][node_array_prop];
45
+ const node_array_property_definition = doc.schema[node_array_node.type].properties[node_array_prop];
46
46
  const target_node_type = get_default_node_type(node_array_property_definition);
47
47
 
48
48
  if (!target_node_type) {
@@ -82,7 +82,7 @@ export function join_text_node(tr) {
82
82
  }
83
83
 
84
84
  // Special behavior: if we can't join and current node is empty, delete it
85
- if (!can_join && node.content[0] === '') {
85
+ if (!can_join && node.content.text === '') {
86
86
  tr.set_selection({
87
87
  type: 'node',
88
88
  path: doc.selection.path.slice(0, -2),
@@ -100,8 +100,15 @@ export function join_text_node(tr) {
100
100
 
101
101
  // Normal joining logic - both nodes are text nodes
102
102
  const previous_text_path = [...doc.selection.path.slice(0, -2), node_index - 1];
103
- const joined_text = join_annotated_string(predecessor_node.content, node.content);
103
+ const joined_text = join_annotated_text(predecessor_node.content, node.content);
104
104
 
105
+ // Calculate cursor position based on original predecessor content length
106
+ const cursor_position = get_char_length(predecessor_node.content.text);
107
+
108
+ // First set the joined content on the predecessor node (preserves annotations)
109
+ tr.set([predecessor_node.id, 'content'], joined_text);
110
+
111
+ // Then delete the current node
105
112
  tr.set_selection({
106
113
  type: 'node',
107
114
  path: doc.selection.path.slice(0, -2),
@@ -110,13 +117,14 @@ export function join_text_node(tr) {
110
117
  });
111
118
 
112
119
  tr.delete_selection();
120
+
121
+ // Finally set the cursor position at the join point using the pre-calculated position
113
122
  tr.set_selection({
114
123
  type: 'text',
115
124
  path: [...previous_text_path, 'content'],
116
- anchor_offset: get_char_length(predecessor_node.content[0]),
117
- focus_offset: get_char_length(predecessor_node.content[0]),
125
+ anchor_offset: cursor_position,
126
+ focus_offset: cursor_position,
118
127
  });
119
- tr.set([predecessor_node.id, 'content'], joined_text);
120
128
  return true;
121
129
  }
122
130
 
@@ -134,7 +142,7 @@ export function insert_default_node(tr) {
134
142
  const property_name = path.at(-1);
135
143
 
136
144
  // Get the definition for this property
137
- const property_definition = doc.schema[node_array_node.type][property_name];
145
+ const property_definition = doc.schema[node_array_node.type].properties[property_name];
138
146
  const default_type = get_default_node_type(property_definition);
139
147
 
140
148
  // Only proceed if there's exactly one allowed node_type
@@ -161,7 +169,7 @@ export function select_all(tr) {
161
169
 
162
170
  if (selection.type === 'text') {
163
171
  const text_content = doc.get(selection.path);
164
- const text_length = get_char_length(text_content[0]);
172
+ const text_length = get_char_length(text_content.text);
165
173
 
166
174
  // Check if all text is already selected
167
175
  const is_all_text_selected =
package/src/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Main exports for the svedit library
2
2
  export { default as Svedit } from './Svedit.svelte';
3
- export { default as AnnotatedStringProperty } from './AnnotatedStringProperty.svelte';
3
+ export { default as AnnotatedTextProperty } from './AnnotatedTextProperty.svelte';
4
4
  export { default as CustomProperty } from './CustomProperty.svelte';
5
5
  export { default as Node } from './Node.svelte';
6
6
  export { default as NodeArrayProperty } from './NodeArrayProperty.svelte';
@@ -68,7 +68,7 @@ export type ArrayType = "string_array" | "number_array" | "boolean_array" | "int
68
68
  /**
69
69
  * Special types for rich content.
70
70
  */
71
- export type RichType = "annotated_string";
71
+ export type RichType = "annotated_text";
72
72
 
73
73
  /**
74
74
  * Node reference types for linking to other nodes.
@@ -88,7 +88,7 @@ export type PropertyType = PrimitiveType | ReferenceType;
88
88
  /**
89
89
  * Document schema primitive types - all possible property types in document schemas.
90
90
  */
91
- export type DocumentSchemaPrimitive = "string" | "number" | "boolean" | "integer" | "datetime" | "string_array" | "number_array" | "boolean_array" | "integer_array" | "annotated_string" | "node" | "node_array";
91
+ export type DocumentSchemaPrimitive = "string" | "number" | "boolean" | "integer" | "datetime" | "string_array" | "number_array" | "boolean_array" | "integer_array" | "annotated_text" | "node" | "node_array";
92
92
 
93
93
  /**
94
94
  * Maps document schema types to their JavaScript runtime types.
@@ -103,7 +103,7 @@ export type DocumentSchemaValueToJs<T> =
103
103
  T extends "number_array" ? Array<number> :
104
104
  T extends "boolean_array" ? Array<boolean> :
105
105
  T extends "integer_array" ? Array<number> :
106
- T extends "annotated_string" ? [string, Array<any>] :
106
+ T extends "annotated_text" ? AnnotatedText :
107
107
  T extends "node" ? string :
108
108
  T extends "node_array" ? Array<string> :
109
109
  never;
@@ -112,15 +112,24 @@ export type DocumentSchemaValueToJs<T> =
112
112
  * Converts a document node schema definition to its inferred JS type.
113
113
  * Handles the {type: "..."} wrapper structure used in document schemas.
114
114
  */
115
- export type DocumentNodeToJs<S extends Record<string, {type: DocumentSchemaPrimitive}>> =
116
- { id: string, type: string } & { [K in keyof S]: DocumentSchemaValueToJs<S[K]["type"]> };
115
+ export type DocumentNodeToJs<S extends NodeSchema> =
116
+ { id: string, type: string } & { [K in keyof S["properties"]]: DocumentSchemaValueToJs<S["properties"][K]["type"]> };
117
117
 
118
118
  /**
119
- * A property that stores a primitive value.
119
+ * A property that stores an annotated text with required allow_newlines setting.
120
+ */
121
+ export type AnnotatedTextProperty = {
122
+ type: 'annotated_text';
123
+ node_types?: string[];
124
+ allow_newlines: boolean;
125
+ };
126
+
127
+ /**
128
+ * A property that stores a primitive value (excluding annotated_text).
120
129
  */
121
130
  export type PrimitiveProperty = {
122
- [K in PrimitiveType]: { type: K }
123
- }[PrimitiveType];
131
+ [K in Exclude<PrimitiveType, 'annotated_text'>]: { type: K }
132
+ }[Exclude<PrimitiveType, 'annotated_text'>];
124
133
 
125
134
  /**
126
135
  * A property that stores a reference to a single node.
@@ -143,13 +152,36 @@ export type NodeArrayProperty = {
143
152
  /**
144
153
  * Union type for all possible property definitions.
145
154
  */
146
- export type PropertyDefinition = PrimitiveProperty | NodeProperty | NodeArrayProperty;
155
+ export type PropertyDefinition = PrimitiveProperty | AnnotatedTextProperty | NodeProperty | NodeArrayProperty;
156
+
157
+ /**
158
+ * Node kind values for different types of content nodes
159
+ */
160
+ export type NodeKind = 'document' | 'block' | 'text' | 'annotation';
161
+
162
+ /**
163
+ * Schema for text nodes - must have a content property of type annotated_text
164
+ */
165
+ export type TextNodeSchema = {
166
+ kind: 'text';
167
+ properties: {
168
+ content: AnnotatedTextProperty;
169
+ } & Record<string, PropertyDefinition>;
170
+ };
171
+
172
+ /**
173
+ * Schema for non-text nodes
174
+ */
175
+ export type NonTextNodeSchema = {
176
+ kind: 'document' | 'block' | 'annotation';
177
+ properties: Record<string, PropertyDefinition>;
178
+ };
147
179
 
148
180
  /**
149
181
  * A node schema defines the structure of a specific node type.
150
- * Maps property names to their definitions.
182
+ * Contains a kind and properties object that maps property names to their definitions.
151
183
  */
152
- export type NodeSchema = Record<string, PropertyDefinition>;
184
+ export type NodeSchema = TextNodeSchema | NonTextNodeSchema;
153
185
 
154
186
  /**
155
187
  * A document schema defines all node types available in a document.
@@ -174,9 +206,9 @@ export type SerializedNode = {
174
206
  export type SerializedDocument = SerializedNode[];
175
207
 
176
208
  /**
177
- * Props for the AnnotatedStringProperty component
209
+ * Props for the AnnotatedTextProperty component
178
210
  */
179
- export type AnnotatedStringPropertyProps = {
211
+ export type AnnotatedTextPropertyProps = {
180
212
  /** The full path to the property */
181
213
  path: DocumentPath;
182
214
  /** The `class` attribute on the content element */
@@ -239,26 +271,39 @@ export type SveditProps = {
239
271
  editable?: boolean,
240
272
  /** The path to the root element (e.g. ['page_1']) */
241
273
  path: DocumentPath,
242
- /** The `class` attribute on the content element */
274
+ /** The `class` attribute on the canvas element */
243
275
  class?: string;
276
+ /** The `autocapitalize` attribute on the canvas element */
277
+ autocapitalize?: 'on' | 'off';
278
+ /** The `spellcheck` attribute on the canvas element */
279
+ spellcheck?: 'true' | 'false';
244
280
  };
245
281
 
246
282
  /**
247
283
  * Represents an annotation in an annotated string
248
- * Format: [start_offset, end_offset, type, options?]
249
284
  */
250
- export type Annotation = [number, number, string, Record<string, any>?];
285
+ export type Annotation = {
286
+ start_offset: number;
287
+ end_offset: number;
288
+ node_id: NodeId;
289
+ };
290
+
291
+ /**
292
+ * Represents an annotated text with text and annotations
293
+ */
294
+ export type AnnotatedText = {
295
+ text: string;
296
+ annotations: Array<Annotation>;
297
+ };
251
298
 
252
299
  /**
253
300
  * Represents a fragment of annotated text content
254
301
  */
255
- export type AnnotationFragment = {
256
- /** The annotation type (e.g., 'link', 'emphasis', 'strong') */
257
- type: string;
302
+ export type AnnotationFragment = string | {
303
+ /** NodeId that has annotation type and details */
304
+ node: any;
258
305
  /** The text content of the annotation */
259
306
  content: string;
260
307
  /** Index of the annotation in the original array */
261
308
  annotation_index: number;
262
- /** Optional properties from the annotation's options object */
263
- data?: any;
264
309
  };
package/src/lib/util.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import { Annotation } from './types.d.ts';
2
+ * @import { Annotation, AnnotatedText } from './types.d.ts';
3
3
  */
4
4
 
5
5
  /**
@@ -120,25 +120,25 @@ export function char_to_utf16_offset(str, char_offset) {
120
120
  }
121
121
 
122
122
  /**
123
- * Splits an annotated string at the specified character position.
123
+ * Splits an annotated text at the specified character position.
124
124
  *
125
125
  * Annotations that span the split point will be divided appropriately,
126
126
  * with offsets adjusted for each resulting part.
127
127
  *
128
- * @param {[string, Array<Annotation>]} text_with_annotations - Tuple of [text, annotations] where annotations follow the Annotation type: [start, end, type, data?]
128
+ * @param {AnnotatedText} text_with_annotations - Annotated text object
129
129
  * @param {number} at_position - Character position where to split (0-based)
130
- * @returns {[[string, Array<Annotation>], [string, Array<Annotation>]]} Tuple of [left_part, right_part] where each part is [text, annotations]
130
+ * @returns {[AnnotatedText, AnnotatedText]} Tuple of [left_part, right_part]
131
131
  *
132
132
  * @example
133
- * split_annotated_string(["Hello world", [[6,11, "strong"]]], 8)
133
+ * split_annotated_text({text: "Hello world", annotations: [{start_offset: 6, end_offset: 11, node_id: "strong"}]}, 8)
134
134
  * // Returns:
135
135
  * // [
136
- * // ["Hello wo", [[6,8, "strong"]]]
137
- * // ["rld", [[0,3, "strong"]]]
136
+ * // {text: "Hello wo", annotations: [{start_offset: 6, end_offset: 8, node_id: "strong"}]},
137
+ * // {text: "rld", annotations: [{start_offset: 0, end_offset: 3, node_id: "strong"}]}
138
138
  * // ]
139
139
  */
140
- export function split_annotated_string(text_with_annotations, at_position) {
141
- const [text, annotations] = text_with_annotations;
140
+ export function split_annotated_text(text_with_annotations, at_position) {
141
+ const {text, annotations} = text_with_annotations;
142
142
 
143
143
  // Split the text using character-aware slicing
144
144
  const left_text = char_slice(text, 0, at_position);
@@ -150,44 +150,44 @@ export function split_annotated_string(text_with_annotations, at_position) {
150
150
  const right_annotations = [];
151
151
 
152
152
  // Process each annotation
153
- for (const [start, end, type, data] of annotations) {
154
- if (end <= at_position) {
153
+ for (const {start_offset, end_offset, node_id} of annotations) {
154
+ if (end_offset <= at_position) {
155
155
  // Annotation is entirely in the left part
156
- left_annotations.push([start, end, type, data]);
157
- } else if (start >= at_position) {
156
+ left_annotations.push({start_offset, end_offset, node_id});
157
+ } else if (start_offset >= at_position) {
158
158
  // Annotation is entirely in the right part - shift offsets
159
- right_annotations.push([start - at_position, end - at_position, type, data]);
159
+ right_annotations.push({start_offset: start_offset - at_position, end_offset: end_offset - at_position, node_id});
160
160
  } else {
161
161
  // Annotation spans the split point - split it
162
- left_annotations.push([start, at_position, type, data]);
163
- right_annotations.push([0, end - at_position, type, data]);
162
+ left_annotations.push({start_offset, end_offset: at_position, node_id});
163
+ right_annotations.push({start_offset: 0, end_offset: end_offset - at_position, node_id});
164
164
  }
165
165
  }
166
166
 
167
167
  return [
168
- [left_text, left_annotations],
169
- [right_text, right_annotations]
168
+ {text: left_text, annotations: left_annotations},
169
+ {text: right_text, annotations: right_annotations}
170
170
  ];
171
171
  }
172
172
 
173
173
 
174
174
  /**
175
- * Joins two annotated strings into a single annotated string.
175
+ * Joins two annotated texts into a single annotated text.
176
176
  *
177
177
  * Annotations from the second text will have their offsets shifted by the length
178
178
  * of the first text. Adjacent annotations of the same type and data will be merged.
179
179
  *
180
- * @param {[string, Array<Annotation>]} first_text - First annotated string tuple where annotations follow the Annotation type: [start, end, type, data?]
181
- * @param {[string, Array<Annotation>]} second_text - Second annotated string tuple where annotations follow the Annotation type: [start, end, type, data?]
182
- * @returns {[string, Array<Annotation>]} Combined annotated string tuple [text, annotations]
180
+ * @param {AnnotatedText} first_text - First annotated text object
181
+ * @param {AnnotatedText} second_text - Second annotated text object
182
+ * @returns {AnnotatedText} Combined annotated text object
183
183
  *
184
184
  * @example
185
- * join_annotated_string(["Hello wo", [[6,8, "strong"]]], ["rld", [[0,3, "strong"]]])
186
- * // Returns: ["Hello world", [[6,11, "strong"]]]
185
+ * join_annotated_text({text: "Hello wo", annotations: [{start_offset: 6, end_offset: 8, node_id: "strong"}]}, {text: "rld", annotations: [{start_offset: 0, end_offset: 3, node_id: "strong"}]})
186
+ * // Returns: {text: "Hello world", annotations: [{start_offset: 6, end_offset: 11, node_id: "strong"}]}
187
187
  */
188
- export function join_annotated_string(first_text, second_text) {
189
- const [first_text_content, first_annotations] = first_text;
190
- const [second_text_content, second_annotations] = second_text;
188
+ export function join_annotated_text(first_text, second_text) {
189
+ const {text: first_text_content, annotations: first_annotations} = first_text;
190
+ const {text: second_text_content, annotations: second_annotations} = second_text;
191
191
 
192
192
  // Join the text content
193
193
  const joined_text = first_text_content + second_text_content;
@@ -198,25 +198,24 @@ export function join_annotated_string(first_text, second_text) {
198
198
 
199
199
  // Add annotations from the second text, shifting their offsets
200
200
  const offset = get_char_length(first_text_content);
201
- for (const [start, end, type, data] of second_annotations) {
201
+ for (const {start_offset, end_offset, node_id} of second_annotations) {
202
202
  /** @type {Annotation} */
203
- const shifted_annotation = [start + offset, end + offset, type, data];
203
+ const shifted_annotation = {start_offset: start_offset + offset, end_offset: end_offset + offset, node_id};
204
204
 
205
205
  // Check if this annotation can be merged with the last annotation from first text
206
206
  const last_annotation = joined_annotations[joined_annotations.length - 1];
207
207
  if (last_annotation &&
208
- last_annotation[1] === shifted_annotation[0] && // annotations are adjacent
209
- last_annotation[2] === shifted_annotation[2] && // same type
210
- JSON.stringify(last_annotation[3]) === JSON.stringify(shifted_annotation[3])) { // same data
208
+ last_annotation.end_offset === shifted_annotation.start_offset && // annotations are adjacent
209
+ last_annotation.node_id === shifted_annotation.node_id) { // same node_id
211
210
  // Merge by extending the end position of the last annotation
212
- last_annotation[1] = shifted_annotation[1];
211
+ last_annotation.end_offset = shifted_annotation.end_offset;
213
212
  } else {
214
213
  // Add as separate annotation
215
214
  joined_annotations.push(shifted_annotation);
216
215
  }
217
216
  }
218
217
 
219
- return [joined_text, joined_annotations];
218
+ return {text: joined_text, annotations: joined_annotations};
220
219
  }
221
220
 
222
221
  /**
@@ -1,7 +0,0 @@
1
- export default AnnotatedStringProperty;
2
- type AnnotatedStringProperty = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<AnnotatedStringPropertyProps>): void;
5
- };
6
- declare const AnnotatedStringProperty: import("svelte").Component<import("./types.d.ts").AnnotatedStringPropertyProps, {}, "">;
7
- //# sourceMappingURL=AnnotatedStringProperty.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AnnotatedStringProperty.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AnnotatedStringProperty.svelte.js"],"names":[],"mappings":";;;;;AAwFA,+HAAsE"}