@moldable-ai/editor 0.1.7 → 0.1.9
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/dist/components/markdown-editor.d.ts +12 -3
- package/dist/components/markdown-editor.d.ts.map +1 -1
- package/dist/components/markdown-editor.js +23 -9
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/lib/lexical/editor-theme.d.ts +2 -0
- package/dist/lib/lexical/editor-theme.d.ts.map +1 -1
- package/dist/lib/lexical/editor-theme.js +2 -0
- package/dist/lib/lexical/file-reference-plugin.d.ts +9 -0
- package/dist/lib/lexical/file-reference-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/file-reference-plugin.js +99 -0
- package/dist/lib/lexical/find-replace-plugin.d.ts +31 -0
- package/dist/lib/lexical/find-replace-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/find-replace-plugin.js +205 -0
- package/dist/lib/lexical/headless-editor.d.ts.map +1 -1
- package/dist/lib/lexical/headless-editor.js +6 -0
- package/dist/lib/lexical/image-component.d.ts +8 -0
- package/dist/lib/lexical/image-component.d.ts.map +1 -0
- package/dist/lib/lexical/image-component.js +99 -0
- package/dist/lib/lexical/image-node.d.ts +36 -0
- package/dist/lib/lexical/image-node.d.ts.map +1 -0
- package/dist/lib/lexical/image-node.js +105 -0
- package/dist/lib/lexical/markdown-transformers.d.ts +4 -1
- package/dist/lib/lexical/markdown-transformers.d.ts.map +1 -1
- package/dist/lib/lexical/markdown-transformers.js +78 -0
- package/dist/lib/lexical/media-context.d.ts +7 -0
- package/dist/lib/lexical/media-context.d.ts.map +1 -0
- package/dist/lib/lexical/media-context.js +7 -0
- package/dist/lib/lexical/media-node.d.ts +32 -0
- package/dist/lib/lexical/media-node.d.ts.map +1 -0
- package/dist/lib/lexical/media-node.js +97 -0
- package/dist/lib/lexical/media-plugin.d.ts +12 -0
- package/dist/lib/lexical/media-plugin.d.ts.map +1 -0
- package/dist/lib/lexical/media-plugin.js +139 -0
- package/dist/lib/lexical/sync-plugin.d.ts +1 -0
- package/dist/lib/lexical/sync-plugin.d.ts.map +1 -1
- package/dist/lib/lexical/sync-plugin.js +13 -5
- package/package.json +19 -18
- package/src/styles/index.css +130 -0
- package/dist/lib/lexical/translation.test.d.ts +0 -2
- package/dist/lib/lexical/translation.test.d.ts.map +0 -1
- package/dist/lib/lexical/translation.test.js +0 -329
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createMoldableHeadlessEditor } from './headless-editor';
|
|
3
|
-
import { $extractTranslatableSegments, $applyTranslations, $getTextSegmentsForTranslation, translateEditorState, } from './translation';
|
|
4
|
-
import { $convertFromMarkdownString, $convertToMarkdownString, markdownTransformers } from './markdown-transformers';
|
|
5
|
-
describe('Translation Utilities', () => {
|
|
6
|
-
describe('$extractTranslatableSegments', () => {
|
|
7
|
-
it('extracts text from a simple paragraph', async () => {
|
|
8
|
-
const editor = createMoldableHeadlessEditor();
|
|
9
|
-
await new Promise((resolve) => {
|
|
10
|
-
editor.update(() => {
|
|
11
|
-
$convertFromMarkdownString({ markdown: 'Hello world' });
|
|
12
|
-
resolve();
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
const segments = await new Promise((resolve) => {
|
|
16
|
-
editor.getEditorState().read(() => {
|
|
17
|
-
resolve($extractTranslatableSegments());
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
expect(segments).toHaveLength(1);
|
|
21
|
-
expect(segments[0].text).toBe('Hello world');
|
|
22
|
-
});
|
|
23
|
-
it('extracts text from multiple paragraphs', async () => {
|
|
24
|
-
const editor = createMoldableHeadlessEditor();
|
|
25
|
-
await new Promise((resolve) => {
|
|
26
|
-
editor.update(() => {
|
|
27
|
-
$convertFromMarkdownString({ markdown: 'First paragraph\n\nSecond paragraph' });
|
|
28
|
-
resolve();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
const segments = await new Promise((resolve) => {
|
|
32
|
-
editor.getEditorState().read(() => {
|
|
33
|
-
resolve($extractTranslatableSegments());
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
expect(segments).toHaveLength(2);
|
|
37
|
-
expect(segments[0].text).toBe('First paragraph');
|
|
38
|
-
expect(segments[1].text).toBe('Second paragraph');
|
|
39
|
-
});
|
|
40
|
-
it('extracts text from headings', async () => {
|
|
41
|
-
const editor = createMoldableHeadlessEditor();
|
|
42
|
-
await new Promise((resolve) => {
|
|
43
|
-
editor.update(() => {
|
|
44
|
-
$convertFromMarkdownString({ markdown: '# Title\n\nContent' });
|
|
45
|
-
resolve();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
const segments = await new Promise((resolve) => {
|
|
49
|
-
editor.getEditorState().read(() => {
|
|
50
|
-
resolve($extractTranslatableSegments());
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
expect(segments).toHaveLength(2);
|
|
54
|
-
expect(segments[0].text).toBe('Title');
|
|
55
|
-
expect(segments[1].text).toBe('Content');
|
|
56
|
-
});
|
|
57
|
-
it('extracts text from list items', async () => {
|
|
58
|
-
const editor = createMoldableHeadlessEditor();
|
|
59
|
-
await new Promise((resolve) => {
|
|
60
|
-
editor.update(() => {
|
|
61
|
-
$convertFromMarkdownString({ markdown: '- Item 1\n- Item 2\n- Item 3' });
|
|
62
|
-
resolve();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
const segments = await new Promise((resolve) => {
|
|
66
|
-
editor.getEditorState().read(() => {
|
|
67
|
-
resolve($extractTranslatableSegments());
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
expect(segments).toHaveLength(3);
|
|
71
|
-
expect(segments[0].text).toBe('Item 1');
|
|
72
|
-
expect(segments[1].text).toBe('Item 2');
|
|
73
|
-
expect(segments[2].text).toBe('Item 3');
|
|
74
|
-
});
|
|
75
|
-
it('handles bold and italic text as separate segments', async () => {
|
|
76
|
-
const editor = createMoldableHeadlessEditor();
|
|
77
|
-
await new Promise((resolve) => {
|
|
78
|
-
editor.update(() => {
|
|
79
|
-
$convertFromMarkdownString({ markdown: 'This is **bold** and *italic* text' });
|
|
80
|
-
resolve();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
const segments = await new Promise((resolve) => {
|
|
84
|
-
editor.getEditorState().read(() => {
|
|
85
|
-
resolve($extractTranslatableSegments());
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
// Bold and italic are separate text nodes
|
|
89
|
-
expect(segments.length).toBeGreaterThanOrEqual(3);
|
|
90
|
-
const allText = segments.map(s => s.text).join('');
|
|
91
|
-
expect(allText).toContain('bold');
|
|
92
|
-
expect(allText).toContain('italic');
|
|
93
|
-
});
|
|
94
|
-
it('extracts text from blockquotes', async () => {
|
|
95
|
-
const editor = createMoldableHeadlessEditor();
|
|
96
|
-
await new Promise((resolve) => {
|
|
97
|
-
editor.update(() => {
|
|
98
|
-
$convertFromMarkdownString({ markdown: '> This is a quote' });
|
|
99
|
-
resolve();
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
const segments = await new Promise((resolve) => {
|
|
103
|
-
editor.getEditorState().read(() => {
|
|
104
|
-
resolve($extractTranslatableSegments());
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
expect(segments).toHaveLength(1);
|
|
108
|
-
expect(segments[0].text).toBe('This is a quote');
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe('$applyTranslations', () => {
|
|
112
|
-
it('applies translations to text nodes', async () => {
|
|
113
|
-
const editor = createMoldableHeadlessEditor();
|
|
114
|
-
// Set up initial content
|
|
115
|
-
await new Promise((resolve) => {
|
|
116
|
-
editor.update(() => {
|
|
117
|
-
$convertFromMarkdownString({ markdown: 'Hello world' });
|
|
118
|
-
resolve();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
// Get segments
|
|
122
|
-
const segments = await new Promise((resolve) => {
|
|
123
|
-
editor.getEditorState().read(() => {
|
|
124
|
-
resolve($extractTranslatableSegments());
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
// Apply translations
|
|
128
|
-
const translationMap = new Map();
|
|
129
|
-
translationMap.set(segments[0].nodeKey, 'Hej verden');
|
|
130
|
-
await new Promise((resolve) => {
|
|
131
|
-
editor.update(() => {
|
|
132
|
-
$applyTranslations(translationMap);
|
|
133
|
-
resolve();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
// Verify translation was applied
|
|
137
|
-
const result = await new Promise((resolve) => {
|
|
138
|
-
editor.getEditorState().read(() => {
|
|
139
|
-
resolve($convertToMarkdownString({ transformers: markdownTransformers }));
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
expect(result).toBe('Hej verden');
|
|
143
|
-
});
|
|
144
|
-
it('preserves structure when applying translations', async () => {
|
|
145
|
-
const editor = createMoldableHeadlessEditor();
|
|
146
|
-
await new Promise((resolve) => {
|
|
147
|
-
editor.update(() => {
|
|
148
|
-
$convertFromMarkdownString({ markdown: '# Title\n\n- Item 1\n- Item 2' });
|
|
149
|
-
resolve();
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
const segments = await new Promise((resolve) => {
|
|
153
|
-
editor.getEditorState().read(() => {
|
|
154
|
-
resolve($extractTranslatableSegments());
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
// Translate each segment
|
|
158
|
-
const translationMap = new Map();
|
|
159
|
-
segments.forEach((seg) => {
|
|
160
|
-
if (seg.text === 'Title')
|
|
161
|
-
translationMap.set(seg.nodeKey, 'Titel');
|
|
162
|
-
if (seg.text === 'Item 1')
|
|
163
|
-
translationMap.set(seg.nodeKey, 'Element 1');
|
|
164
|
-
if (seg.text === 'Item 2')
|
|
165
|
-
translationMap.set(seg.nodeKey, 'Element 2');
|
|
166
|
-
});
|
|
167
|
-
await new Promise((resolve) => {
|
|
168
|
-
editor.update(() => {
|
|
169
|
-
$applyTranslations(translationMap);
|
|
170
|
-
resolve();
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
const result = await new Promise((resolve) => {
|
|
174
|
-
editor.getEditorState().read(() => {
|
|
175
|
-
resolve($convertToMarkdownString({ transformers: markdownTransformers }));
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
expect(result).toContain('# Titel');
|
|
179
|
-
expect(result).toContain('- Element 1');
|
|
180
|
-
expect(result).toContain('- Element 2');
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
describe('$getTextSegmentsForTranslation', () => {
|
|
184
|
-
it('returns parallel arrays of texts and keys', async () => {
|
|
185
|
-
const editor = createMoldableHeadlessEditor();
|
|
186
|
-
await new Promise((resolve) => {
|
|
187
|
-
editor.update(() => {
|
|
188
|
-
$convertFromMarkdownString({ markdown: 'Hello\n\nWorld' });
|
|
189
|
-
resolve();
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
const result = await new Promise((resolve) => {
|
|
193
|
-
editor.getEditorState().read(() => {
|
|
194
|
-
resolve($getTextSegmentsForTranslation());
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
expect(result.texts).toHaveLength(2);
|
|
198
|
-
expect(result.nodeKeys).toHaveLength(2);
|
|
199
|
-
expect(result.texts[0]).toBe('Hello');
|
|
200
|
-
expect(result.texts[1]).toBe('World');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
describe('translateEditorState', () => {
|
|
204
|
-
it('translates content and returns markdown', async () => {
|
|
205
|
-
const editor = createMoldableHeadlessEditor();
|
|
206
|
-
await new Promise((resolve) => {
|
|
207
|
-
editor.update(() => {
|
|
208
|
-
$convertFromMarkdownString({ markdown: '# Hello\n\nWorld' });
|
|
209
|
-
resolve();
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
// Mock translate function
|
|
213
|
-
const translateFn = async (texts) => {
|
|
214
|
-
return texts.map((t) => {
|
|
215
|
-
if (t === 'Hello')
|
|
216
|
-
return 'Bonjour';
|
|
217
|
-
if (t === 'World')
|
|
218
|
-
return 'Monde';
|
|
219
|
-
return t;
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
const result = await translateEditorState(editor, translateFn);
|
|
223
|
-
expect(result).toContain('# Bonjour');
|
|
224
|
-
expect(result).toContain('Monde');
|
|
225
|
-
});
|
|
226
|
-
it('preserves formatting during translation', async () => {
|
|
227
|
-
const editor = createMoldableHeadlessEditor();
|
|
228
|
-
await new Promise((resolve) => {
|
|
229
|
-
editor.update(() => {
|
|
230
|
-
$convertFromMarkdownString({
|
|
231
|
-
markdown: '**Bold text** and *italic text*'
|
|
232
|
-
});
|
|
233
|
-
resolve();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
const translateFn = async (texts) => {
|
|
237
|
-
return texts.map((t) => `[${t}]`); // Just wrap for testing
|
|
238
|
-
};
|
|
239
|
-
const result = await translateEditorState(editor, translateFn);
|
|
240
|
-
// Should preserve bold and italic markers
|
|
241
|
-
expect(result).toContain('**');
|
|
242
|
-
expect(result).toContain('*');
|
|
243
|
-
});
|
|
244
|
-
it('handles complex medical note structure', async () => {
|
|
245
|
-
const editor = createMoldableHeadlessEditor();
|
|
246
|
-
const medicalNote = `# Patient Notes
|
|
247
|
-
|
|
248
|
-
## Chief Complaint
|
|
249
|
-
Patient presents with chest pain.
|
|
250
|
-
|
|
251
|
-
## Assessment
|
|
252
|
-
- Stable condition
|
|
253
|
-
- No immediate concerns
|
|
254
|
-
|
|
255
|
-
> Doctor's note: Follow up in 2 weeks`;
|
|
256
|
-
await new Promise((resolve) => {
|
|
257
|
-
editor.update(() => {
|
|
258
|
-
$convertFromMarkdownString({ markdown: medicalNote });
|
|
259
|
-
resolve();
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
// Simple translation that uppercases everything
|
|
263
|
-
const translateFn = async (texts) => {
|
|
264
|
-
return texts.map((t) => t.toUpperCase());
|
|
265
|
-
};
|
|
266
|
-
const result = await translateEditorState(editor, translateFn);
|
|
267
|
-
// Should preserve structure
|
|
268
|
-
expect(result).toContain('# PATIENT NOTES');
|
|
269
|
-
expect(result).toContain('## CHIEF COMPLAINT');
|
|
270
|
-
expect(result).toContain('- STABLE CONDITION');
|
|
271
|
-
expect(result).toContain('>');
|
|
272
|
-
});
|
|
273
|
-
it('handles empty content', async () => {
|
|
274
|
-
const editor = createMoldableHeadlessEditor();
|
|
275
|
-
await new Promise((resolve) => {
|
|
276
|
-
editor.update(() => {
|
|
277
|
-
$convertFromMarkdownString({ markdown: '' });
|
|
278
|
-
resolve();
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
const translateFn = async (texts) => texts;
|
|
282
|
-
const result = await translateEditorState(editor, translateFn);
|
|
283
|
-
expect(result).toBe('');
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe('round-trip translation', () => {
|
|
287
|
-
const testCases = [
|
|
288
|
-
{ name: 'simple paragraph', markdown: 'Hello world' },
|
|
289
|
-
{ name: 'heading', markdown: '# Title' },
|
|
290
|
-
{ name: 'multiple headings', markdown: '# H1\n\n## H2\n\n### H3' },
|
|
291
|
-
{ name: 'bullet list', markdown: '- Item 1\n- Item 2\n- Item 3' },
|
|
292
|
-
{ name: 'numbered list', markdown: '1. First\n2. Second\n3. Third' },
|
|
293
|
-
{ name: 'blockquote', markdown: '> This is a quote' },
|
|
294
|
-
{ name: 'bold text', markdown: '**bold**' },
|
|
295
|
-
{ name: 'italic text', markdown: '*italic*' },
|
|
296
|
-
{ name: 'mixed formatting', markdown: 'Normal **bold** and *italic*' },
|
|
297
|
-
{ name: 'code inline', markdown: 'Use `code` here' },
|
|
298
|
-
{ name: 'horizontal rule', markdown: 'Before\n\n***\n\nAfter' },
|
|
299
|
-
];
|
|
300
|
-
testCases.forEach(({ name, markdown }) => {
|
|
301
|
-
it(`preserves structure for ${name}`, async () => {
|
|
302
|
-
const editor = createMoldableHeadlessEditor();
|
|
303
|
-
await new Promise((resolve) => {
|
|
304
|
-
editor.update(() => {
|
|
305
|
-
$convertFromMarkdownString({ markdown });
|
|
306
|
-
resolve();
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
// Identity translation (no change)
|
|
310
|
-
const translateFn = async (texts) => texts;
|
|
311
|
-
const result = await translateEditorState(editor, translateFn);
|
|
312
|
-
// Re-import and re-export to normalize
|
|
313
|
-
const editor2 = createMoldableHeadlessEditor();
|
|
314
|
-
await new Promise((resolve) => {
|
|
315
|
-
editor2.update(() => {
|
|
316
|
-
$convertFromMarkdownString({ markdown });
|
|
317
|
-
resolve();
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
const expected = await new Promise((resolve) => {
|
|
321
|
-
editor2.getEditorState().read(() => {
|
|
322
|
-
resolve($convertToMarkdownString({ transformers: markdownTransformers }));
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
expect(result).toBe(expected);
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
});
|