svedit 0.10.0 → 0.11.1
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 +387 -384
- package/dist/AnnotatedTextProperty.svelte +2 -5
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.d.ts +0 -1
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +30 -5
- package/dist/KeyMapper.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.js +18 -6
- package/dist/NodeArrayProperty.svelte +13 -25
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeGap.svelte +49 -57
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/Svedit.svelte +189 -85
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/doc_utils.d.ts +5 -0
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/paste_utils.d.ts +46 -0
- package/dist/paste_utils.d.ts.map +1 -0
- package/dist/paste_utils.js +167 -0
- package/dist/utils.d.ts +14 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -7
- package/package.json +1 -1
- package/src/lib/AnnotatedTextProperty.svelte +2 -5
- package/src/lib/Command.svelte.js +30 -5
- package/src/lib/KeyMapper.svelte.js +18 -6
- package/src/lib/NodeArrayProperty.svelte +13 -25
- package/src/lib/NodeGap.svelte +49 -57
- package/src/lib/Svedit.svelte +189 -85
- package/src/lib/doc_utils.js +1 -1
- package/src/lib/index.js +1 -0
- package/src/lib/paste_utils.js +167 -0
- package/src/lib/utils.js +28 -7
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { DocumentSchema } from './types.d.ts'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function normalize_line_endings(text) {
|
|
6
|
+
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function dedent_plain_text(plain_text) {
|
|
10
|
+
const lines = normalize_line_endings(plain_text).split('\n');
|
|
11
|
+
if (lines.length < 2) return plain_text;
|
|
12
|
+
|
|
13
|
+
const non_empty_lines = lines.filter((line) => line.trim().length > 0);
|
|
14
|
+
if (non_empty_lines.length === 0) return plain_text;
|
|
15
|
+
|
|
16
|
+
const indented_non_empty_lines = non_empty_lines.filter((line) => /^[\t ]+/.test(line));
|
|
17
|
+
const indented_line_ratio = indented_non_empty_lines.length / non_empty_lines.length;
|
|
18
|
+
if (indented_line_ratio < 0.8) return plain_text;
|
|
19
|
+
|
|
20
|
+
const leading_whitespace_lengths = indented_non_empty_lines
|
|
21
|
+
.map((line) => line.match(/^[\t ]+/)?.[0].length || 0)
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
if (leading_whitespace_lengths.length === 0) return plain_text;
|
|
24
|
+
|
|
25
|
+
const dedent_size = Math.min(...leading_whitespace_lengths);
|
|
26
|
+
if (dedent_size <= 0) return plain_text;
|
|
27
|
+
|
|
28
|
+
return lines
|
|
29
|
+
.map((line) => {
|
|
30
|
+
if (line.trim().length === 0) return line;
|
|
31
|
+
let removable_count = 0;
|
|
32
|
+
while (removable_count < dedent_size && removable_count < line.length) {
|
|
33
|
+
const char = line[removable_count];
|
|
34
|
+
if (char === ' ' || char === '\t') {
|
|
35
|
+
removable_count += 1;
|
|
36
|
+
} else {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return line.slice(removable_count);
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function split_plain_text_paragraphs(plain_text) {
|
|
46
|
+
return normalize_line_endings(plain_text)
|
|
47
|
+
.split(/\n{2,}/)
|
|
48
|
+
.map((fragment) => fragment.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normalize_plain_text_for_single_line_property(plain_text) {
|
|
53
|
+
return normalize_line_endings(plain_text).replace(/\s*\n+\s*/g, ' ');
|
|
54
|
+
}
|
|
55
|
+
|
|
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) {
|
|
62
|
+
if (!node_type) return null;
|
|
63
|
+
const node_schema = schema[node_type];
|
|
64
|
+
if (!node_schema || node_schema.kind !== 'text') return null;
|
|
65
|
+
if (node_schema.properties?.content?.type === 'annotated_text') return 'content';
|
|
66
|
+
return (
|
|
67
|
+
Object.entries(node_schema.properties).find(([, property_definition]) => {
|
|
68
|
+
return property_definition.type === 'annotated_text';
|
|
69
|
+
})?.[0] || null
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
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;
|
|
80
|
+
|
|
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
|
+
) {
|
|
88
|
+
return node[text_property_name];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (node.content && typeof node.content.text === 'string' && Array.isArray(node.content.annotations)) {
|
|
92
|
+
return node.content;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
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;
|
|
106
|
+
return !!get_text_content(node, schema);
|
|
107
|
+
}
|
|
108
|
+
|
|
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) {
|
|
115
|
+
if (
|
|
116
|
+
node_array_property_definition?.type !== 'node_array' ||
|
|
117
|
+
!Array.isArray(node_array_property_definition.node_types)
|
|
118
|
+
) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const default_node_type = node_array_property_definition.default_node_type;
|
|
123
|
+
if (default_node_type && schema[default_node_type]?.kind === 'text') {
|
|
124
|
+
return default_node_type;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
node_array_property_definition.node_types.find((node_type) => schema[node_type]?.kind === 'text') ||
|
|
129
|
+
null
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
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) {
|
|
140
|
+
if (!Array.isArray(paragraph_fragments) || paragraph_fragments.length === 0 || !node_type) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const text_property_name = get_text_property_name(node_type, schema);
|
|
145
|
+
if (!text_property_name) return null;
|
|
146
|
+
|
|
147
|
+
const payload = {
|
|
148
|
+
main_nodes: [],
|
|
149
|
+
nodes: {}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < paragraph_fragments.length; i++) {
|
|
153
|
+
const fragment = paragraph_fragments[i];
|
|
154
|
+
const node_id = 'fragment_' + i;
|
|
155
|
+
payload.nodes[node_id] = {
|
|
156
|
+
id: node_id,
|
|
157
|
+
type: node_type,
|
|
158
|
+
[text_property_name]: {
|
|
159
|
+
text: fragment,
|
|
160
|
+
annotations: []
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
payload.main_nodes.push(node_id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return payload;
|
|
167
|
+
}
|
package/src/lib/utils.js
CHANGED
|
@@ -5,20 +5,41 @@
|
|
|
5
5
|
const SEGMENTER = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Detect if the
|
|
8
|
+
* Detect if the virtual keyboard is likely visible.
|
|
9
|
+
*
|
|
10
|
+
* This is a heuristic based on the visual viewport becoming smaller than the
|
|
11
|
+
* layout viewport.
|
|
12
|
+
*
|
|
13
|
+
* @returns {boolean} true if the virtual keyboard is likely active, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
export function is_virtual_keyboard_active() {
|
|
16
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const visual_viewport = window.visualViewport;
|
|
21
|
+
if (!visual_viewport) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return visual_viewport.height < document.documentElement.clientHeight;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Detect if the current browser is likely on a mobile device.
|
|
30
|
+
*
|
|
31
|
+
* This uses the user agent only so touch-capable laptops are not treated as
|
|
32
|
+
* mobile browsers.
|
|
33
|
+
*
|
|
9
34
|
* @returns {boolean} true if mobile browser, false otherwise
|
|
10
35
|
*/
|
|
11
36
|
export function is_mobile_browser() {
|
|
12
|
-
if (typeof
|
|
37
|
+
if (typeof navigator === 'undefined') {
|
|
13
38
|
return false;
|
|
14
39
|
}
|
|
15
40
|
|
|
16
41
|
const user_agent = navigator.userAgent;
|
|
17
|
-
return (
|
|
18
|
-
/iPhone|iPad|iPod|Android|Mobile/i.test(user_agent) ||
|
|
19
|
-
'ontouchstart' in window ||
|
|
20
|
-
navigator.maxTouchPoints > 0
|
|
21
|
-
);
|
|
42
|
+
return /iPhone|iPad|iPod|Android|Mobile/i.test(user_agent);
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
// ‼️‼️‼️‼️‼️‼️ UNUSED UTILITY BELOW ‼️‼️‼️‼️‼️‼️
|