svedit 0.1.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/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/AnnotatedStringProperty.svelte +126 -0
- package/dist/AnnotatedStringProperty.svelte.d.ts +7 -0
- package/dist/AnnotatedStringProperty.svelte.d.ts.map +1 -0
- package/dist/CustomProperty.svelte +40 -0
- package/dist/CustomProperty.svelte.d.ts +8 -0
- package/dist/CustomProperty.svelte.d.ts.map +1 -0
- package/dist/Document.svelte.d.ts +191 -0
- package/dist/Document.svelte.d.ts.map +1 -0
- package/dist/Document.svelte.js +681 -0
- package/dist/Node.svelte +41 -0
- package/dist/Node.svelte.d.ts +7 -0
- package/dist/Node.svelte.d.ts.map +1 -0
- package/dist/NodeArrayProperty.svelte +45 -0
- package/dist/NodeArrayProperty.svelte.d.ts +7 -0
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -0
- package/dist/Svedit.svelte +936 -0
- package/dist/Svedit.svelte.d.ts +12 -0
- package/dist/Svedit.svelte.d.ts.map +1 -0
- package/dist/Transaction.svelte.d.ts +198 -0
- package/dist/Transaction.svelte.d.ts.map +1 -0
- package/dist/Transaction.svelte.js +577 -0
- package/dist/UnknownNode.svelte +12 -0
- package/dist/UnknownNode.svelte.d.ts +12 -0
- package/dist/UnknownNode.svelte.d.ts.map +1 -0
- package/dist/commands.svelte.d.ts +5 -0
- package/dist/commands.svelte.d.ts.map +1 -0
- package/dist/commands.svelte.js +261 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/types.d.ts +264 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +78 -0
- package/package.json +65 -0
- package/src/lib/AnnotatedStringProperty.svelte +126 -0
- package/src/lib/CustomProperty.svelte +40 -0
- package/src/lib/Document.svelte.js +681 -0
- package/src/lib/Node.svelte +41 -0
- package/src/lib/NodeArrayProperty.svelte +45 -0
- package/src/lib/Svedit.svelte +936 -0
- package/src/lib/Transaction.svelte.js +577 -0
- package/src/lib/UnknownNode.svelte +12 -0
- package/src/lib/commands.svelte.js +261 -0
- package/src/lib/index.js +14 -0
- package/src/lib/types.d.ts +264 -0
- package/src/lib/util.js +78 -0
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svedit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite dev",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview",
|
|
9
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
10
|
+
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
11
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
12
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"lint": "prettier --check . && eslint .",
|
|
15
|
+
"test:unit": "vitest",
|
|
16
|
+
"test": "npm run test:unit -- --run"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"!dist/**/*.test.*",
|
|
21
|
+
"!dist/**/*.spec.*",
|
|
22
|
+
"src/lib",
|
|
23
|
+
"!src/lib/**/*.test.*",
|
|
24
|
+
"!src/lib/**/*.spec.*"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"**/*.css"
|
|
28
|
+
],
|
|
29
|
+
"svelte": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"svelte": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"svelte": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@eslint/compat": "^1.2.5",
|
|
43
|
+
"@eslint/js": "^9.18.0",
|
|
44
|
+
"@sveltejs/adapter-auto": "^6.0.0",
|
|
45
|
+
"@sveltejs/kit": "^2.21.2",
|
|
46
|
+
"@sveltejs/package": "^2.0.0",
|
|
47
|
+
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
49
|
+
"@typescript-eslint/parser": "^8.39.1",
|
|
50
|
+
"@vitest/browser": "^3.2.4",
|
|
51
|
+
"eslint": "^9.18.0",
|
|
52
|
+
"eslint-config-prettier": "^10.0.1",
|
|
53
|
+
"eslint-plugin-svelte": "^3.0.0",
|
|
54
|
+
"globals": "^16.0.0",
|
|
55
|
+
"prettier": "^3.4.2",
|
|
56
|
+
"prettier-plugin-svelte": "^3.3.3",
|
|
57
|
+
"publint": "^0.3.2",
|
|
58
|
+
"svelte": "^5.0.0",
|
|
59
|
+
"svelte-check": "^4.0.0",
|
|
60
|
+
"vite": "^7.0.4",
|
|
61
|
+
"vitest": "^3.2.3",
|
|
62
|
+
"vitest-browser-svelte": "^1.1.0",
|
|
63
|
+
"nanoid": "^5.1.5"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/** @import { AnnotatedStringPropertyProps, AnnotationFragment } from './types.d.ts'; */
|
|
5
|
+
|
|
6
|
+
const svedit = getContext('svedit');
|
|
7
|
+
|
|
8
|
+
/** @type {AnnotatedStringPropertyProps} */
|
|
9
|
+
let {
|
|
10
|
+
path,
|
|
11
|
+
class: css_class,
|
|
12
|
+
placeholder = '',
|
|
13
|
+
} = $props();
|
|
14
|
+
|
|
15
|
+
let is_focused = $derived.by(() => {
|
|
16
|
+
return svedit.doc.selection?.type === 'text' && path.join('.') === svedit.doc.selection?.path.join('.');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts text with annotations into renderable fragments for display.
|
|
21
|
+
* @param {string} text - The plain text content
|
|
22
|
+
* @param {Array<[number, number, string, ...any]>} annotations - Array of annotations where each is [start_offset, end_offset, type, options?] (minimum 3 elements)
|
|
23
|
+
* @returns {Array<string|AnnotationFragment>} Array of fragments - strings for plain text, AnnotationFragment objects for annotated content
|
|
24
|
+
*/
|
|
25
|
+
function render_annotated_string(text, annotations) {
|
|
26
|
+
let fragments = [];
|
|
27
|
+
let last_index = 0;
|
|
28
|
+
|
|
29
|
+
// Sort annotations by start_offset
|
|
30
|
+
const sorted_annotations = $state.snapshot(annotations).sort((a, b) => a[0] - b[0]);
|
|
31
|
+
|
|
32
|
+
for (let [index, annotation] of sorted_annotations.entries()) {
|
|
33
|
+
// Add text before the annotation
|
|
34
|
+
if (annotation[0] > last_index) {
|
|
35
|
+
fragments.push(text.slice(last_index, annotation[0]));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Add the annotated string
|
|
39
|
+
const annotated_content = text.slice(annotation[0], annotation[1]);
|
|
40
|
+
fragments.push({
|
|
41
|
+
type: annotation[2],
|
|
42
|
+
content: annotated_content,
|
|
43
|
+
annotation_index: index,
|
|
44
|
+
data: annotation[3]
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
last_index = annotation[1];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add any remaining text after the last annotation
|
|
51
|
+
if (last_index < text.length) {
|
|
52
|
+
fragments.push(text.slice(last_index));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return fragments;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let fragments = $derived(render_annotated_string(svedit.doc.get(path)[0], svedit.doc.get(path)[1]));
|
|
59
|
+
let plain_text = $derived(svedit.doc.get(path)[0]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {MouseEvent} e - The click event
|
|
63
|
+
*/
|
|
64
|
+
function handle_link_click(e) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<!-- ATTENTION: The comments are needed to prevent unwanted text nodes with whitespace. -->
|
|
70
|
+
<div
|
|
71
|
+
data-type="text"
|
|
72
|
+
data-path={path.join('.')}
|
|
73
|
+
style="anchor-name: --{path.join('-')};"
|
|
74
|
+
class="text svedit-selectable {css_class}"
|
|
75
|
+
class:empty={plain_text.length === 0}
|
|
76
|
+
class:focused={is_focused}
|
|
77
|
+
placeholder={placeholder}
|
|
78
|
+
>
|
|
79
|
+
{#each fragments as fragment, index (index)}
|
|
80
|
+
{#if typeof fragment === 'string'}<!--
|
|
81
|
+
-->{fragment}<!--
|
|
82
|
+
-->{:else if fragment.type === 'emphasis'}<!--
|
|
83
|
+
--><em>{fragment.content}</em><!--
|
|
84
|
+
-->{:else if fragment.type === 'strong'}<!--
|
|
85
|
+
--><strong>{fragment.content}</strong><!--
|
|
86
|
+
-->{:else if fragment.type === 'link'}<!--
|
|
87
|
+
--><a
|
|
88
|
+
onclick={handle_link_click}
|
|
89
|
+
style="anchor-name: --{path.join('-') + '-' + fragment.annotation_index};"
|
|
90
|
+
href={fragment.data.href}
|
|
91
|
+
target={fragment.data.target || '_self'}>{fragment.content}</a><!--
|
|
92
|
+
-->{:else}<!--
|
|
93
|
+
-->{fragment.content}<!--
|
|
94
|
+
-->{/if}
|
|
95
|
+
{/each}<!--
|
|
96
|
+
--><br>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
.text {
|
|
101
|
+
white-space: pre-wrap;
|
|
102
|
+
overflow-wrap: anywhere;
|
|
103
|
+
box-sizing: content-box;
|
|
104
|
+
|
|
105
|
+
&.heading1 {
|
|
106
|
+
text-wrap: var(--text-wrap);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[placeholder].empty::before {
|
|
111
|
+
content: attr(placeholder);
|
|
112
|
+
pointer-events: none;
|
|
113
|
+
color: color-mix(in oklch, currentcolor 50%, transparent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* We can safely hide the <br> element when the placeholder is empty and focused. */
|
|
117
|
+
/* No longer the cursor will be rendered after the placeholder when focused. */
|
|
118
|
+
[placeholder].empty.focused br {
|
|
119
|
+
display:none;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.text.focused {
|
|
123
|
+
background: none;
|
|
124
|
+
outline: 1px dashed var(--editing-stroke-color);
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/** @import { CustomPropertyProps } from './types.d.ts'; */
|
|
3
|
+
|
|
4
|
+
/** @type {CustomPropertyProps} */
|
|
5
|
+
let {
|
|
6
|
+
path,
|
|
7
|
+
class: css_class,
|
|
8
|
+
children,
|
|
9
|
+
} = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<!-- NOTE: It's super crucial that there is no extra whitespace, to not introduce additional cursor positions in contenteditable -->
|
|
13
|
+
<div
|
|
14
|
+
class="property {css_class}"
|
|
15
|
+
data-type="property"
|
|
16
|
+
data-path={path.join('.')}
|
|
17
|
+
style="anchor-name: --{path.join('-')};"
|
|
18
|
+
>
|
|
19
|
+
<div class="cursor-trap svedit-selectable"><br/></div>
|
|
20
|
+
<div class="property-content" contenteditable="false">{@render children()}</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
.property {
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
.cursor-trap {
|
|
28
|
+
position: absolute;
|
|
29
|
+
top: 0;
|
|
30
|
+
left: 0;
|
|
31
|
+
right: 0;
|
|
32
|
+
bottom: 0;
|
|
33
|
+
z-index: 1;
|
|
34
|
+
outline: none;
|
|
35
|
+
}
|
|
36
|
+
.property-content {
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
}
|
|
40
|
+
</style>
|