svedit 0.4.0 → 0.5.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.
- package/README.md +4 -3
- package/dist/Document.svelte.d.ts +20 -4
- package/dist/Document.svelte.d.ts.map +1 -1
- package/dist/Document.svelte.js +74 -37
- package/dist/Svedit.svelte +239 -192
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +12 -15
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +137 -27
- package/dist/commands.svelte.d.ts.map +1 -1
- package/dist/commands.svelte.js +1 -6
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +34 -0
- package/package.json +21 -21
- package/src/lib/Document.svelte.js +74 -37
- package/src/lib/Svedit.svelte +239 -192
- package/src/lib/Transaction.svelte.js +137 -27
- package/src/lib/commands.svelte.js +1 -6
- package/src/lib/util.js +34 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Svedit
|
|
2
2
|
|
|
3
|
-
Svedit (think Svelte Edit) is a tiny library for building
|
|
3
|
+
Svedit (think Svelte Edit) is a a tiny library for building editable websites in Svelte. You can model your content in JSON, render it with custom Svelte components, and edit it directly in the layout.
|
|
4
4
|
|
|
5
5
|
Try the [demo](https://svedit.vercel.app).
|
|
6
6
|
|
|
@@ -217,7 +217,8 @@ const document_config = {
|
|
|
217
217
|
layout,
|
|
218
218
|
content
|
|
219
219
|
};
|
|
220
|
-
|
|
220
|
+
tr.create(new_text);
|
|
221
|
+
tr.insert_nodes([new_text.id]);
|
|
221
222
|
},
|
|
222
223
|
}
|
|
223
224
|
};
|
|
@@ -376,4 +377,4 @@ It's early. Expect bugs. Expect missing features. Expect the need for more work
|
|
|
376
377
|
|
|
377
378
|
## Credits
|
|
378
379
|
|
|
379
|
-
Svedit led by [Michael Aufreiter](https://michaelaufreiter.com) with guidance and support from [Johannes Mutter](https://mutter.co).
|
|
380
|
+
Svedit is led by [Michael Aufreiter](https://michaelaufreiter.com) with guidance and support from [Johannes Mutter](https://mutter.co).
|
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
* Selection,
|
|
6
6
|
* Annotation,
|
|
7
7
|
* PrimitiveType,
|
|
8
|
-
* DocumentSchemaPrimitive,
|
|
9
8
|
* NodeProperty,
|
|
10
9
|
* NodeArrayProperty,
|
|
11
10
|
* NodeKind,
|
|
12
11
|
* NodeSchema,
|
|
13
|
-
* TextNodeSchema,
|
|
14
|
-
* NonTextNodeSchema,
|
|
15
12
|
* DocumentSchema,
|
|
16
13
|
* SerializedDocument
|
|
17
14
|
* } from './types.d.ts';
|
|
@@ -168,6 +165,20 @@ export default class Document {
|
|
|
168
165
|
* @returns {NodeKind}
|
|
169
166
|
*/
|
|
170
167
|
kind(node: any): NodeKind;
|
|
168
|
+
/**
|
|
169
|
+
* Determines whether a node type can be inserted at a given selection.
|
|
170
|
+
* @param {string} node_type - The type of node to insert.
|
|
171
|
+
* @param {Selection} [selection] - The selection at which to insert the node.
|
|
172
|
+
* @returns {boolean} True if the node type can be inserted, false otherwise.
|
|
173
|
+
*/
|
|
174
|
+
can_insert(node_type: string, selection?: Selection): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Compute next possible insert position from a given selection
|
|
177
|
+
*
|
|
178
|
+
* @param {Selection} [selection] - Reference selection
|
|
179
|
+
* @returns {Selection} True if the paste operation was successful, false otherwise
|
|
180
|
+
*/
|
|
181
|
+
get_next_node_insert_cursor(selection?: Selection): Selection;
|
|
171
182
|
/**
|
|
172
183
|
* Returns the annotation object that is currently "under the cursor".
|
|
173
184
|
* NOTE: Annotations in Svedit are exclusive, so there can only be one active_annotation
|
|
@@ -176,6 +187,11 @@ export default class Document {
|
|
|
176
187
|
* @returns {Annotation|null}
|
|
177
188
|
*/
|
|
178
189
|
active_annotation(annotation_type: string): Annotation | null;
|
|
190
|
+
get_selected_annotated_text(): {
|
|
191
|
+
text: string;
|
|
192
|
+
annotations: any;
|
|
193
|
+
nodes: {};
|
|
194
|
+
};
|
|
179
195
|
get_selected_plain_text(): string;
|
|
180
196
|
get_selected_nodes(): any;
|
|
181
197
|
select_parent(): void;
|
|
@@ -190,7 +206,7 @@ export default class Document {
|
|
|
190
206
|
* @returns {SerializedDocument} The serialized document array
|
|
191
207
|
*/
|
|
192
208
|
to_json(): SerializedDocument;
|
|
193
|
-
property_type(type: any, property: any): "string" | "number" | "boolean" | "
|
|
209
|
+
property_type(type: any, property: any): "string" | "number" | "boolean" | "node_array" | "node" | "annotated_text" | "integer" | "integer_array" | "number_array" | "string_array" | "datetime" | "boolean_array";
|
|
194
210
|
count_references(node_id: any): number;
|
|
195
211
|
/**
|
|
196
212
|
* @param {NodeId} node_id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Document.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Document.svelte.js"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"Document.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Document.svelte.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,uCAJ0C,CAAC,SAA7B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAE,UAC9B,CAAC,GACC,CAAC,CAIb;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,IAAI,IAAI,aAAa,CAQjC;AAED;;;;GAIG;AACH,2DAHW,YAAY,GAAG,iBAAiB,GAC9B,MAAM,GAAG,IAAI,CASzB;AAED;;;;GAIG;AACH,0DAHW,cAAc,QAexB;AAuGD;IAgGE;;;;;;OAMG;IACH,oBANW,cAAc,kBACd,kBAAkB,0BAE1B;QAA4B,SAAS,GAA7B,SAAS;QACK,MAAM,GAApB,GAAG;KACb,EAqBA;IAxHD,YAAkB;IAClB,iBAAuB;IACvB,WAAiB;IACjB,eAAqB;IACrB,sBAA2B;IAG3B,kBAA6C;IAC7C,kBAAkE;IAGnE,mBAAmD;IACnD,iBAA+C;IAC/C,gCAA6E;IAU5E;;;;OAIG;IACH,qBAHW,SAAS,GAAG,SAAS,EAQ/B;IAlBD;;;OAGG;IACH,iBAFa,SAAS,GAAG,SAAS,CAIjC;IAcD;;;;;;OAMG;IACH,4BAiDC;IAaC,uBAAoB;IAkBtB,mBAMC;IAED,sCAKA;IAGD,yBAkBC;IAID,uBAoBC;IAGA,yBAWC;IAED;;;;OAIG;IACH,oBAHW,GAAG,QAKb;IAED;;;;OAIG;IACH,UAFa,WAAW,CASvB;IAED;;;;OAIG;IACH,mBAFW,WAAW,QAkBrB;IAED,aAWC;IAED,aAaC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAlBW,YAAY,GAAC,MAAM,GACjB,GAAG,CAoFf;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,cAHW,YAAY,GACV;QAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC,CAuBzD;IAED;;;;;OAKG;IACH,WAHW,GAAG,GACD,QAAQ,CAIpB;IAED;;;;;OAKG;IACH,sBAJW,MAAM,cACN,SAAS,GACP,OAAO,CAcnB;IAED;;;;;OAKG;IACH,wCAHW,SAAS,GACP,SAAS,CAerB;IAED;;;;;;OAMG;IACH,mCAHW,MAAM,GACJ,UAAU,GAAC,IAAI,CAqB3B;IAED;;;;MA2BC;IAGD,kCAOC;IAED,0BAOC;IAED,sBAmCC;IAED;;;;MAaC;IAKD,8BAEC;IAED;;;OAGG;IACH,WAFa,kBAAkB,CAI9B;IAID,mNAWC;IAGD,uCAkBC;IAGD;;;OAGG;IACH,8BAHW,MAAM,GACJ,MAAM,EAAE,CASpB;;CACF;gCAhzBS,cAAc;mCAAd,cAAc;kCAAd,cAAc;uCAAd,cAAc;oCAAd,cAAc;+BAAd,cAAc;wBAhBA,yBAAyB;kCAgBvC,cAAc;8BAAd,cAAc;gCAAd,cAAc;wCAAd,cAAc;4BAAd,cAAc"}
|
package/dist/Document.svelte.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Transaction from './Transaction.svelte.js';
|
|
2
|
-
import { char_slice, get_char_length } from './util.js';
|
|
2
|
+
import { char_slice, get_char_length, traverse } from './util.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @import {
|
|
@@ -8,13 +8,10 @@ import { char_slice, get_char_length } from './util.js';
|
|
|
8
8
|
* Selection,
|
|
9
9
|
* Annotation,
|
|
10
10
|
* PrimitiveType,
|
|
11
|
-
* DocumentSchemaPrimitive,
|
|
12
11
|
* NodeProperty,
|
|
13
12
|
* NodeArrayProperty,
|
|
14
13
|
* NodeKind,
|
|
15
14
|
* NodeSchema,
|
|
16
|
-
* TextNodeSchema,
|
|
17
|
-
* NonTextNodeSchema,
|
|
18
15
|
* DocumentSchema,
|
|
19
16
|
* SerializedDocument
|
|
20
17
|
* } from './types.d.ts';
|
|
@@ -315,8 +312,8 @@ export default class Document {
|
|
|
315
312
|
get_available_annotation_types() {
|
|
316
313
|
if (this.selection?.type !== 'text') return [];
|
|
317
314
|
const path = this.selection.path;
|
|
318
|
-
const
|
|
319
|
-
return
|
|
315
|
+
const property_definition = this.inspect(path);
|
|
316
|
+
return property_definition.node_types || [];
|
|
320
317
|
}
|
|
321
318
|
|
|
322
319
|
// Helper function to get the currently selected node
|
|
@@ -600,6 +597,47 @@ export default class Document {
|
|
|
600
597
|
return this.schema[node.type].kind;
|
|
601
598
|
}
|
|
602
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Determines whether a node type can be inserted at a given selection.
|
|
602
|
+
* @param {string} node_type - The type of node to insert.
|
|
603
|
+
* @param {Selection} [selection] - The selection at which to insert the node.
|
|
604
|
+
* @returns {boolean} True if the node type can be inserted, false otherwise.
|
|
605
|
+
*/
|
|
606
|
+
can_insert(node_type, selection = this.selection) {
|
|
607
|
+
if (selection?.type === 'node') {
|
|
608
|
+
const property_definition = this.inspect(selection.path);
|
|
609
|
+
if (property_definition.node_types.includes(node_type)) {
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// No insert position found yet, and root not reached, try one level up if possible
|
|
615
|
+
let next_node_insert_cursor = this.get_next_node_insert_cursor(selection);
|
|
616
|
+
if (!next_node_insert_cursor) return false;
|
|
617
|
+
return this.can_insert(node_type, next_node_insert_cursor);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Compute next possible insert position from a given selection
|
|
622
|
+
*
|
|
623
|
+
* @param {Selection} [selection] - Reference selection
|
|
624
|
+
* @returns {Selection} True if the paste operation was successful, false otherwise
|
|
625
|
+
*/
|
|
626
|
+
get_next_node_insert_cursor(selection = this.selection) {
|
|
627
|
+
// There's no parent path to insert into
|
|
628
|
+
if (!selection || selection.path.length <= 2) {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const node_offset = parseInt(String(selection.path.at(-2)), 10) + 1;
|
|
633
|
+
return {
|
|
634
|
+
type: 'node',
|
|
635
|
+
path: selection.path.slice(0, -2),
|
|
636
|
+
anchor_offset: node_offset,
|
|
637
|
+
focus_offset: node_offset,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
603
641
|
/**
|
|
604
642
|
* Returns the annotation object that is currently "under the cursor".
|
|
605
643
|
* NOTE: Annotations in Svedit are exclusive, so there can only be one active_annotation
|
|
@@ -628,6 +666,35 @@ export default class Document {
|
|
|
628
666
|
}
|
|
629
667
|
}
|
|
630
668
|
|
|
669
|
+
get_selected_annotated_text() {
|
|
670
|
+
if (this.selection?.type !== 'text') return null;
|
|
671
|
+
|
|
672
|
+
const selection_start = Math.min(this.selection.anchor_offset, this.selection.focus_offset);
|
|
673
|
+
const selection_end = Math.max(this.selection.anchor_offset, this.selection.focus_offset);
|
|
674
|
+
const annotated_text = this.get(this.selection.path);
|
|
675
|
+
const text = char_slice(annotated_text.text, selection_start, selection_end);
|
|
676
|
+
const nodes = {};
|
|
677
|
+
const annotations = annotated_text.annotations.map(a => {
|
|
678
|
+
if (selection_start < a.end_offset && selection_end > a.start_offset) {
|
|
679
|
+
const sub_graph = this.traverse(a.node_id);
|
|
680
|
+
for (const node of sub_graph) {
|
|
681
|
+
if (!nodes[node.id]) {
|
|
682
|
+
nodes[node.id] = node;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
start_offset: Math.max(a.start_offset - selection_start, 0),
|
|
687
|
+
end_offset: Math.min(a.end_offset - selection_start, selection_end - selection_start),
|
|
688
|
+
node_id: a.node_id
|
|
689
|
+
};
|
|
690
|
+
} else {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
}).filter(Boolean);
|
|
694
|
+
|
|
695
|
+
return { text, annotations, nodes };
|
|
696
|
+
}
|
|
697
|
+
|
|
631
698
|
// TODO: think about ways how we can also turn a node selection into plain text.
|
|
632
699
|
get_selected_plain_text() {
|
|
633
700
|
if (this.selection?.type !== 'text') return null;
|
|
@@ -703,37 +770,7 @@ export default class Document {
|
|
|
703
770
|
// IMPORTANT: Leaf nodes must go first, branches second and the root node (entry point) last (depth-first traversal)
|
|
704
771
|
// NOTE: Nodes that are not reachable from the entry point node will not be included in the serialization
|
|
705
772
|
traverse(node_id) {
|
|
706
|
-
|
|
707
|
-
const visited = {};
|
|
708
|
-
const visit = (node) => {
|
|
709
|
-
if (!node || visited[node.id]) {
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
visited[node.id] = true;
|
|
713
|
-
for (const [property_name, value] of Object.entries(node)) {
|
|
714
|
-
const property_schema = this.schema[node.type].properties[property_name];
|
|
715
|
-
|
|
716
|
-
if (property_schema?.type === 'node_array') {
|
|
717
|
-
for (const v of value) {
|
|
718
|
-
if (typeof v === 'string') {
|
|
719
|
-
visit($state.snapshot(this.get(v)));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
} else if (property_schema?.type === 'node') {
|
|
723
|
-
visit($state.snapshot(this.get(value)));
|
|
724
|
-
} else if (property_schema?.type === 'annotated_text') {
|
|
725
|
-
for (const annotation of value.annotations) {
|
|
726
|
-
visit($state.snapshot(this.get(annotation.node_id)));
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
// Finally add the node to the result.
|
|
731
|
-
// We use a deep clone, so we make sure nothing of the original document is referenced.
|
|
732
|
-
json.push(structuredClone(node));
|
|
733
|
-
}
|
|
734
|
-
// Start with the root node (document_id)
|
|
735
|
-
visit($state.snapshot(this.get(node_id)));
|
|
736
|
-
return json;
|
|
773
|
+
return traverse(node_id, this.schema, $state.snapshot(this.nodes));
|
|
737
774
|
}
|
|
738
775
|
|
|
739
776
|
/**
|