svedit 0.11.1 → 0.13.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.
Files changed (93) hide show
  1. package/README.md +260 -82
  2. package/dist/Command.svelte.d.ts +54 -29
  3. package/dist/Command.svelte.d.ts.map +1 -1
  4. package/dist/Command.svelte.js +286 -283
  5. package/dist/CustomProperty.svelte +3 -5
  6. package/dist/CustomProperty.svelte.d.ts +3 -6
  7. package/dist/CustomProperty.svelte.d.ts.map +1 -1
  8. package/dist/KeyMapper.svelte.d.ts +19 -6
  9. package/dist/KeyMapper.svelte.d.ts.map +1 -1
  10. package/dist/KeyMapper.svelte.js +95 -112
  11. package/dist/Node.svelte +40 -9
  12. package/dist/Node.svelte.d.ts +3 -5
  13. package/dist/Node.svelte.d.ts.map +1 -1
  14. package/dist/NodeArrayProperty.svelte +142 -18
  15. package/dist/NodeArrayProperty.svelte.d.ts +3 -5
  16. package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
  17. package/dist/NodeCaret.svelte +1 -1
  18. package/dist/NodeCaret.svelte.d.ts +6 -14
  19. package/dist/NodeCaret.svelte.d.ts.map +1 -1
  20. package/dist/NodeGap.svelte +60 -12
  21. package/dist/NodeGap.svelte.d.ts +7 -14
  22. package/dist/NodeGap.svelte.d.ts.map +1 -1
  23. package/dist/NodeGapMarkers.svelte +19 -10
  24. package/dist/NodeGapMarkers.svelte.d.ts +6 -11
  25. package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
  26. package/dist/NodeSelectionMarkers.svelte +20 -6
  27. package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
  28. package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
  29. package/dist/Session.svelte.d.ts +85 -116
  30. package/dist/Session.svelte.d.ts.map +1 -1
  31. package/dist/Session.svelte.js +543 -571
  32. package/dist/Svedit.svelte +243 -216
  33. package/dist/Svedit.svelte.d.ts +30 -10
  34. package/dist/Svedit.svelte.d.ts.map +1 -1
  35. package/dist/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
  36. package/dist/TextProperty.svelte.d.ts +5 -0
  37. package/dist/TextProperty.svelte.d.ts.map +1 -0
  38. package/dist/Transaction.svelte.d.ts +101 -126
  39. package/dist/Transaction.svelte.d.ts.map +1 -1
  40. package/dist/Transaction.svelte.js +843 -827
  41. package/dist/UnknownNode.svelte +4 -3
  42. package/dist/UnknownNode.svelte.d.ts +5 -9
  43. package/dist/UnknownNode.svelte.d.ts.map +1 -1
  44. package/dist/doc_utils.d.ts +109 -122
  45. package/dist/doc_utils.d.ts.map +1 -1
  46. package/dist/doc_utils.js +726 -698
  47. package/dist/index.d.ts +20 -17
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -18
  50. package/dist/node_visibility.svelte.d.ts +132 -5
  51. package/dist/node_visibility.svelte.d.ts.map +1 -1
  52. package/dist/node_visibility.svelte.js +535 -642
  53. package/dist/paste_utils.d.ts +11 -43
  54. package/dist/paste_utils.d.ts.map +1 -1
  55. package/dist/paste_utils.js +111 -149
  56. package/dist/transforms.svelte.d.ts +9 -7
  57. package/dist/transforms.svelte.d.ts.map +1 -1
  58. package/dist/transforms.svelte.js +126 -152
  59. package/dist/types.d.ts +329 -291
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +1 -0
  62. package/dist/utils.d.ts +79 -95
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +392 -305
  65. package/package.json +69 -67
  66. package/src/lib/{Command.svelte.js → Command.svelte.ts} +111 -41
  67. package/src/lib/CustomProperty.svelte +3 -5
  68. package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
  69. package/src/lib/Node.svelte +40 -9
  70. package/src/lib/NodeArrayProperty.svelte +142 -18
  71. package/src/lib/NodeCaret.svelte +1 -1
  72. package/src/lib/NodeGap.svelte +60 -12
  73. package/src/lib/NodeGapMarkers.svelte +19 -10
  74. package/src/lib/NodeSelectionMarkers.svelte +20 -6
  75. package/src/lib/Session.svelte.ts +656 -0
  76. package/src/lib/Svedit.svelte +243 -216
  77. package/src/lib/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
  78. package/src/lib/Transaction.svelte.ts +1020 -0
  79. package/src/lib/UnknownNode.svelte +4 -3
  80. package/src/lib/doc_utils.ts +1120 -0
  81. package/src/lib/{index.js → index.ts} +48 -1
  82. package/src/lib/node_visibility.svelte.ts +658 -0
  83. package/src/lib/{paste_utils.js → paste_utils.ts} +51 -59
  84. package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +44 -27
  85. package/src/lib/{types.d.ts → types.ts} +250 -103
  86. package/src/lib/utils.ts +660 -0
  87. package/dist/AnnotatedTextProperty.svelte.d.ts +0 -7
  88. package/dist/AnnotatedTextProperty.svelte.d.ts.map +0 -1
  89. package/src/lib/Session.svelte.js +0 -575
  90. package/src/lib/Transaction.svelte.js +0 -846
  91. package/src/lib/doc_utils.js +0 -807
  92. package/src/lib/node_visibility.svelte.js +0 -718
  93. package/src/lib/utils.js +0 -465
@@ -1,12 +1,23 @@
1
- /**
2
- * @import { DocumentSchema } from './types.d.ts'
3
- */
1
+ import type { Text, DocumentNode, DocumentSchema } from './types.js';
4
2
 
5
- export function normalize_line_endings(text) {
3
+ function is_record(value: unknown): value is Record<string, unknown> {
4
+ return typeof value === 'object' && value !== null;
5
+ }
6
+
7
+ function is_text(value: unknown): value is Text {
8
+ return (
9
+ is_record(value) &&
10
+ typeof value.content === 'string' &&
11
+ Array.isArray(value.marks) &&
12
+ Array.isArray(value.annotations)
13
+ );
14
+ }
15
+
16
+ export function normalize_line_endings(text: string): string {
6
17
  return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
7
18
  }
8
19
 
9
- export function dedent_plain_text(plain_text) {
20
+ export function dedent_plain_text(plain_text: string): string {
10
21
  const lines = normalize_line_endings(plain_text).split('\n');
11
22
  if (lines.length < 2) return plain_text;
12
23
 
@@ -42,101 +53,81 @@ export function dedent_plain_text(plain_text) {
42
53
  .join('\n');
43
54
  }
44
55
 
45
- export function split_plain_text_paragraphs(plain_text) {
56
+ export function split_plain_text_paragraphs(plain_text: string): string[] {
46
57
  return normalize_line_endings(plain_text)
47
58
  .split(/\n{2,}/)
48
59
  .map((fragment) => fragment.trim())
49
60
  .filter(Boolean);
50
61
  }
51
62
 
52
- export function normalize_plain_text_for_single_line_property(plain_text) {
63
+ export function normalize_plain_text_for_single_line_property(plain_text: string): string {
53
64
  return normalize_line_endings(plain_text).replace(/\s*\n+\s*/g, ' ');
54
65
  }
55
66
 
56
- /**
57
- * @param {string | null | undefined} node_type
58
- * @param {DocumentSchema} schema
59
- * @returns {string | null}
60
- */
61
- export function get_text_property_name(node_type, schema) {
67
+ export function get_text_property_name(
68
+ node_type: string | null | undefined,
69
+ schema: DocumentSchema
70
+ ): string | null {
62
71
  if (!node_type) return null;
63
72
  const node_schema = schema[node_type];
64
73
  if (!node_schema || node_schema.kind !== 'text') return null;
65
- if (node_schema.properties?.content?.type === 'annotated_text') return 'content';
74
+ if (node_schema.properties?.content?.type === 'text') return 'content';
66
75
  return (
67
76
  Object.entries(node_schema.properties).find(([, property_definition]) => {
68
- return property_definition.type === 'annotated_text';
77
+ return property_definition.type === 'text';
69
78
  })?.[0] || null
70
79
  );
71
80
  }
72
81
 
73
- /**
74
- * @param {any} node
75
- * @param {DocumentSchema} schema
76
- * @returns {{ text: string, annotations: any[] } | null}
77
- */
78
- export function get_text_content(node, schema) {
79
- if (!node || typeof node !== 'object') return null;
82
+ export function get_text_content(node: unknown, schema: DocumentSchema): Text | null {
83
+ if (!is_record(node)) return null;
80
84
 
81
- const text_property_name = get_text_property_name(node.type, schema);
82
- if (
83
- text_property_name &&
84
- node[text_property_name] &&
85
- typeof node[text_property_name].text === 'string' &&
86
- Array.isArray(node[text_property_name].annotations)
87
- ) {
85
+ const node_type = typeof node.type === 'string' ? node.type : null;
86
+ const text_property_name = get_text_property_name(node_type, schema);
87
+ if (text_property_name && is_text(node[text_property_name])) {
88
88
  return node[text_property_name];
89
89
  }
90
90
 
91
- if (node.content && typeof node.content.text === 'string' && Array.isArray(node.content.annotations)) {
91
+ if (is_text(node.content)) {
92
92
  return node.content;
93
93
  }
94
94
 
95
95
  return null;
96
96
  }
97
97
 
98
- /**
99
- * @param {any} node
100
- * @param {DocumentSchema} schema
101
- * @returns {boolean}
102
- */
103
- export function is_text_like_node_payload(node, schema) {
104
- if (!node || typeof node !== 'object') return false;
105
- if (schema[node.type]?.kind === 'text') return true;
98
+ export function is_text_like_node_payload(node: unknown, schema: DocumentSchema): boolean {
99
+ if (!is_record(node)) return false;
100
+ if (typeof node.type === 'string' && schema[node.type]?.kind === 'text') return true;
106
101
  return !!get_text_content(node, schema);
107
102
  }
108
103
 
109
- /**
110
- * @param {any} node_array_property_definition
111
- * @param {DocumentSchema} schema
112
- * @returns {string | null}
113
- */
114
- export function get_default_text_node(node_array_property_definition, schema) {
104
+ export function get_default_text_node(
105
+ node_array_property_definition: unknown,
106
+ schema: DocumentSchema
107
+ ): string | null {
115
108
  if (
116
- node_array_property_definition?.type !== 'node_array' ||
109
+ !is_record(node_array_property_definition) ||
110
+ node_array_property_definition.type !== 'node_array' ||
117
111
  !Array.isArray(node_array_property_definition.node_types)
118
112
  ) {
119
113
  return null;
120
114
  }
121
115
 
122
116
  const default_node_type = node_array_property_definition.default_node_type;
123
- if (default_node_type && schema[default_node_type]?.kind === 'text') {
117
+ if (typeof default_node_type === 'string' && schema[default_node_type]?.kind === 'text') {
124
118
  return default_node_type;
125
119
  }
126
120
 
127
- return (
128
- node_array_property_definition.node_types.find((node_type) => schema[node_type]?.kind === 'text') ||
129
- null
130
- );
121
+ return (node_array_property_definition.node_types.find(
122
+ (node_type: unknown) => typeof node_type === 'string' && schema[node_type]?.kind === 'text'
123
+ ) || null) as string | null;
131
124
  }
132
125
 
133
- /**
134
- * @param {string[]} paragraph_fragments
135
- * @param {string | null | undefined} node_type
136
- * @param {DocumentSchema} schema
137
- * @returns {{ main_nodes: string[], nodes: Record<string, any> } | null}
138
- */
139
- export function create_plain_text_nodes_payload(paragraph_fragments, node_type, schema) {
126
+ export function create_plain_text_nodes_payload(
127
+ paragraph_fragments: string[],
128
+ node_type: string | null | undefined,
129
+ schema: DocumentSchema
130
+ ): { main_nodes: string[]; nodes: Record<string, DocumentNode> } | null {
140
131
  if (!Array.isArray(paragraph_fragments) || paragraph_fragments.length === 0 || !node_type) {
141
132
  return null;
142
133
  }
@@ -144,7 +135,7 @@ export function create_plain_text_nodes_payload(paragraph_fragments, node_type,
144
135
  const text_property_name = get_text_property_name(node_type, schema);
145
136
  if (!text_property_name) return null;
146
137
 
147
- const payload = {
138
+ const payload: { main_nodes: string[]; nodes: Record<string, DocumentNode> } = {
148
139
  main_nodes: [],
149
140
  nodes: {}
150
141
  };
@@ -156,7 +147,8 @@ export function create_plain_text_nodes_payload(paragraph_fragments, node_type,
156
147
  id: node_id,
157
148
  type: node_type,
158
149
  [text_property_name]: {
159
- text: fragment,
150
+ content: fragment,
151
+ marks: [],
160
152
  annotations: []
161
153
  }
162
154
  };
@@ -1,20 +1,33 @@
1
- import { split_annotated_text, join_annotated_text, get_char_length } from './utils.js';
1
+ import { split_text, join_text, get_char_length } from './utils.js';
2
2
  import { get_default_node_type } from './doc_utils.js';
3
+ import type Transaction from './Transaction.svelte.js';
4
+ import type {
5
+ DocumentNode,
6
+ DocumentPath,
7
+ NodeArrayProperty,
8
+ NodeProperty,
9
+ Selection,
10
+ TextSelection
11
+ } from './types.js';
3
12
 
4
13
  /**
5
14
  * Set multiple properties on a node via a transaction.
6
15
  *
7
- * @param {object} tr - The transaction
8
- * @param {Array} path - Path to the node
9
- * @param {Record<string, any>} properties - Key/value pairs to set
16
+ * @param tr - The transaction
17
+ * @param path - Path to the node
18
+ * @param properties - Key/value pairs to set
10
19
  */
11
- export function set_properties(tr, path, properties) {
20
+ export function set_properties(
21
+ tr: Transaction,
22
+ path: DocumentPath,
23
+ properties: Record<string, unknown>
24
+ ): void {
12
25
  for (const [key, value] of Object.entries(properties)) {
13
26
  tr.set([...path, key], value);
14
27
  }
15
28
  }
16
29
 
17
- export function break_text_node(tr) {
30
+ export function break_text_node(tr: Transaction): boolean {
18
31
  // Keep a reference of the original selection (before any transforms are applied)
19
32
  const selection = tr.selection;
20
33
  // First we need to ensure we have a text selection
@@ -28,33 +41,35 @@ export function break_text_node(tr) {
28
41
  if (tr.kind(node) !== 'text') return false;
29
42
  const is_inside_node_array = tr.inspect(selection.path.slice(0, -2))?.type === 'node_array';
30
43
  if (!is_inside_node_array) return false; // Do nothing if we're not inside a node_array
31
- const node_array_prop = selection.path.at(-3);
44
+ const node_array_prop = String(selection.path.at(-3));
32
45
  // Get the node that owns the node_array property (e.g. a page.body)
33
46
  const node_array_node = tr.get(selection.path.slice(0, -3));
34
47
 
35
-
36
48
  // Delete selection unless collapsed
37
49
  if (selection.anchor_offset !== selection.focus_offset) {
38
50
  tr.delete_selection();
39
51
  }
40
52
 
41
- const split_at_position = tr.selection.anchor_offset;
53
+ // After delete_selection the selection is still a (collapsed) text selection
54
+ const split_at_position = (tr.selection as TextSelection).anchor_offset;
42
55
  const content = tr.get(selection.path);
43
- const [left_text, right_text] = split_annotated_text(content, split_at_position);
56
+ const [left_text, right_text] = split_text(content, split_at_position);
44
57
 
45
58
  tr.set([node.id, 'content'], left_text);
46
59
 
47
- const node_insert_position = {
60
+ const node_insert_position: Selection = {
48
61
  type: 'node',
49
62
  path: tr.selection.path.slice(0, -2),
50
- anchor_offset: parseInt(tr.selection.path.at(-2), 10) + 1,
51
- focus_offset: parseInt(tr.selection.path.at(-2), 10) + 1
63
+ anchor_offset: (tr.selection.path.at(-2) as number) + 1,
64
+ focus_offset: (tr.selection.path.at(-2) as number) + 1
52
65
  };
53
66
 
54
67
  // TODO: Only use default_node_type when caret is at the end of
55
68
  const node_array_property_definition =
56
69
  tr.schema[node_array_node.type].properties[node_array_prop];
57
- const target_node_type = get_default_node_type(node_array_property_definition);
70
+ const target_node_type = get_default_node_type(
71
+ node_array_property_definition as NodeProperty | NodeArrayProperty
72
+ );
58
73
 
59
74
  if (!target_node_type) {
60
75
  console.warn(
@@ -69,7 +84,7 @@ export function break_text_node(tr) {
69
84
  return true;
70
85
  }
71
86
 
72
- export function join_text_node(tr) {
87
+ export function join_text_node(tr: Transaction): boolean {
73
88
  // Keep a reference of the original selection (before any transforms are applied)
74
89
  const selection = tr.selection;
75
90
  // First we need to ensure we have a text selection
@@ -80,20 +95,20 @@ export function join_text_node(tr) {
80
95
  const is_inside_node_array = tr.inspect(selection.path.slice(0, -2))?.type === 'node_array';
81
96
  if (!is_inside_node_array) return false; // Do nothing if we're not inside a node_array
82
97
 
83
- const node_index = parseInt(tr.selection.path.at(-2), 10);
98
+ const node_index = tr.selection.path.at(-2) as number;
84
99
 
85
100
  // Determine if we can join with the previous node
86
101
  let can_join = false;
87
- let predecessor_node = null;
102
+ let predecessor_node: DocumentNode | null = null;
88
103
 
89
104
  if (node_index > 0) {
90
105
  const previous_text_path = [...tr.selection.path.slice(0, -2), node_index - 1];
91
106
  predecessor_node = tr.get(previous_text_path);
92
- can_join = tr.kind(predecessor_node) === 'text';
107
+ can_join = predecessor_node !== null && tr.kind(predecessor_node) === 'text';
93
108
  }
94
109
 
95
110
  // Special behavior: if we can't join and current node is empty, delete it
96
- if (!can_join && node.content.text === '') {
111
+ if (!can_join && node.content.content === '') {
97
112
  tr.set_selection({
98
113
  type: 'node',
99
114
  path: tr.selection.path.slice(0, -2),
@@ -105,16 +120,16 @@ export function join_text_node(tr) {
105
120
  }
106
121
 
107
122
  // If we can't join for any reason, return false
108
- if (!can_join) {
123
+ if (!can_join || !predecessor_node) {
109
124
  return false;
110
125
  }
111
126
 
112
127
  // Normal joining logic - both nodes are text nodes
113
128
  const previous_text_path = [...tr.selection.path.slice(0, -2), node_index - 1];
114
- const joined_text = join_annotated_text(predecessor_node.content, node.content);
129
+ const joined_text = join_text(predecessor_node.content, node.content);
115
130
 
116
131
  // Calculate caret position based on original predecessor content length
117
- const caret_position = get_char_length(predecessor_node.content.text);
132
+ const caret_position = get_char_length(predecessor_node.content.content);
118
133
 
119
134
  // First set the joined content on the predecessor node (preserves annotations)
120
135
  tr.set([predecessor_node.id, 'content'], joined_text);
@@ -139,7 +154,7 @@ export function join_text_node(tr) {
139
154
  return true;
140
155
  }
141
156
 
142
- export function insert_default_node(tr) {
157
+ export function insert_default_node(tr: Transaction): boolean {
143
158
  const selection = tr.selection;
144
159
 
145
160
  // Only work with collapsed node selections
@@ -149,15 +164,17 @@ export function insert_default_node(tr) {
149
164
 
150
165
  const path = selection.path;
151
166
  const node_array_node = tr.get(path.slice(0, -1));
152
- const property_name = path.at(-1);
167
+ const property_name = String(path.at(-1));
153
168
 
154
169
  // Get the definition for this property
155
170
  const property_definition = tr.schema[node_array_node.type].properties[property_name];
156
- const default_type = get_default_node_type(property_definition);
171
+ const default_type = get_default_node_type(
172
+ property_definition as NodeProperty | NodeArrayProperty
173
+ );
157
174
 
158
175
  // Use the inserter function if available
159
- if (tr.config?.inserters?.[default_type]) {
160
- tr.config.inserters[default_type](tr);
176
+ if (tr.config?.inserters?.[default_type as string]) {
177
+ tr.config.inserters[default_type as string](tr);
161
178
  return true;
162
179
  } else {
163
180
  throw new Error(`No inserter function available for default node type '${default_type}'`);