@yorkie-js/sdk 0.6.47 → 0.6.49

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.
@@ -1,567 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <title>ProseMirror Example</title>
6
- <link rel="stylesheet" href="prosemirror.css" />
7
- </head>
8
- <body>
9
- <div class="layout">
10
- <div class="application">
11
- <div class="editor-area">
12
- <h2>ProseMirror</h2>
13
- <div id="app"></div>
14
- </div>
15
- </div>
16
- <div class="log">
17
- <div class="data-container">
18
- <div class="data-item tr">
19
- <h2>ProseMirror.Transaction</h2>
20
- <div class="tr-container">
21
- <div id="tr-info"></div>
22
- <div id="tr-log" class="log-view"></div>
23
- </div>
24
- </div>
25
-
26
- <div class="data-item">
27
- <h2>ProseMirror.Doc</h2>
28
- <div id="pm-log"></div>
29
- </div>
30
- </div>
31
- </div>
32
- </div>
33
-
34
- <script type="importmap">
35
- {
36
- "imports": {
37
- "prosemirror-model": "https://cdn.jsdelivr.net/npm/prosemirror-model@1.23.0/+esm",
38
- "prosemirror-state": "https://cdn.jsdelivr.net/npm/prosemirror-state@1.4.3/+esm",
39
- "prosemirror-view": "https://cdn.jsdelivr.net/npm/prosemirror-view@1.37.0/+esm",
40
- "prosemirror-transform": "https://cdn.jsdelivr.net/npm/prosemirror-transform@1.10.2/+esm",
41
- "prosemirror-keymap": "https://cdn.jsdelivr.net/npm/prosemirror-keymap@1.2.2/+esm",
42
- "prosemirror-example-setup": "https://cdn.jsdelivr.net/npm/prosemirror-example-setup@1.2.2/+esm",
43
- "prosemirror-commands": "https://cdn.jsdelivr.net/npm/prosemirror-commands@1.6.2/+esm"
44
- }
45
- }
46
- </script>
47
- <script type="module">
48
- import './src/yorkie.ts';
49
- import {
50
- EditorState,
51
- Transaction,
52
- TextSelection,
53
- Plugin,
54
- } from 'prosemirror-state';
55
- import { EditorView } from 'prosemirror-view';
56
- import { Schema, Node } from 'prosemirror-model';
57
- import { exampleSetup } from 'prosemirror-example-setup';
58
- import { toggleMark } from 'prosemirror-commands';
59
- import { keymap } from 'prosemirror-keymap';
60
-
61
- const colors = ['#FECEEA', '#FEF1D2', '#A9FDD8', '#D7F8FF', '#CEC5FA'];
62
- let nextColorIdx = 0;
63
- let transactions = [];
64
-
65
- const selectionMap = new Map();
66
-
67
- /**
68
- * `updateSelectionLayer` updates the selection layer of the given actor.
69
- */
70
- function updateSelectionLayer(view, tree, actor) {
71
- const { layer, fromPos, toPos } = selectionMap.get(actor);
72
- const [fromIndex, toIndex] = tree.posRangeToIndexRange([
73
- fromPos,
74
- toPos,
75
- ]);
76
- const coords = view.coordsAtPos(Math.min(fromIndex, toIndex));
77
-
78
- layer.style.left = `${coords.left - 10}px`;
79
- if (coords.top < 130) {
80
- layer.classList.add('first-top');
81
- layer.style.top = `${coords.top + 10}px`;
82
- } else {
83
- layer.classList.remove('first-top');
84
- layer.style.top = `${coords.top - 30}px`;
85
- }
86
- }
87
-
88
- function displayRemoteSelection(view, tree, fromPos, toPos, actor) {
89
- if (!selectionMap.has(actor)) {
90
- const color = colors[nextColorIdx];
91
- nextColorIdx = (nextColorIdx + 1) % colors.length;
92
-
93
- const layer = document.createElement('div');
94
- layer.className = 'username-layer';
95
- layer.textContent = actor.substr(-2);
96
- layer.style.position = 'absolute';
97
- layer.style.backgroundColor = color;
98
- layer.style.color = 'black';
99
- selectionMap.set(actor, { color, layer });
100
-
101
- view.dom.parentNode.appendChild(layer);
102
- }
103
- selectionMap.get(actor).fromPos = fromPos;
104
- selectionMap.get(actor).toPos = toPos;
105
-
106
- for (var [actor] of selectionMap) {
107
- updateSelectionLayer(view, tree, actor);
108
- }
109
- }
110
-
111
- const mySchema = new Schema({
112
- nodes: {
113
- text: { group: 'inline' },
114
- star: {
115
- inline: true,
116
- group: 'inline',
117
- toDOM() {
118
- return ['star', '<🌟>'];
119
- },
120
- parseDOM: [{ tag: 'star' }],
121
- },
122
- paragraph: {
123
- group: 'block',
124
- content: 'inline*',
125
- toDOM() {
126
- return ['p', 0];
127
- },
128
- parseDOM: [{ tag: 'p' }],
129
- },
130
- boring_paragraph: {
131
- group: 'block',
132
- content: 'text*',
133
- marks: '',
134
- toDOM() {
135
- return ['p', { class: 'boring' }, 0];
136
- },
137
- parseDOM: [{ tag: 'p.boring', priority: 60 }],
138
- },
139
- note: {
140
- group: 'block',
141
- content: 'text*',
142
- toDOM() {
143
- return ['note', 0];
144
- },
145
- parseDOM: [{ tag: 'note' }],
146
- },
147
- notegroup: {
148
- group: 'block',
149
- content: 'note+',
150
- toDOM() {
151
- return ['notegroup', 0];
152
- },
153
- parseDOM: [{ tag: 'notegroup' }],
154
- },
155
- doc: { content: 'block+' },
156
- },
157
- marks: {
158
- shouting: {
159
- toDOM() {
160
- return ['shouting', 0];
161
- },
162
- parseDOM: [{ tag: 'shouting' }],
163
- },
164
- },
165
- });
166
-
167
- const initialDoc = {
168
- type: 'doc',
169
- content: [
170
- {
171
- type: 'paragraph',
172
- content: [{ type: 'text', text: 'ab' }],
173
- },
174
- {
175
- type: 'notegroup',
176
- content: [
177
- { type: 'note', content: [{ type: 'text', text: 'cd' }] },
178
- { type: 'note', content: [{ type: 'text', text: 'ef' }] },
179
- ],
180
- },
181
- {
182
- type: 'boring_paragraph',
183
- content: [{ type: 'text', text: 'gh' }],
184
- },
185
- ],
186
- };
187
-
188
- /**
189
- * `docToTreeNode` converts ProseMirror's document to Yorkie.TreeNode.
190
- */
191
- function docToTreeNode(pmNode) {
192
- if (pmNode.type === 'text') {
193
- return {
194
- type: 'text',
195
- value: pmNode.text,
196
- };
197
- }
198
-
199
- const node = {
200
- type: pmNode.type,
201
- children: [],
202
- };
203
- const content = pmNode.content || [];
204
- for (const child of content) {
205
- node.children.push(docToTreeNode(child));
206
- }
207
- return node;
208
- }
209
-
210
- /**
211
- * `treeNodeToDoc` converts Yorkie.TreeNode to ProseMirror's document.
212
- */
213
- function treeNodeToDoc(treeNode) {
214
- if (treeNode.type === 'text') {
215
- return {
216
- type: 'text',
217
- text: treeNode.value,
218
- };
219
- }
220
-
221
- const node = {
222
- type: treeNode.type,
223
- content: [],
224
- };
225
- for (const child of treeNode.children) {
226
- node.content.push(treeNodeToDoc(child));
227
- }
228
- return node;
229
- }
230
-
231
- /**
232
- * Insert a star at the current cursor position.
233
- */
234
- function insertStar(state, dispatch) {
235
- const type = mySchema.nodes.star;
236
- const { $from } = state.selection;
237
- if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
238
- return false;
239
- }
240
-
241
- dispatch(state.tr.replaceSelectionWith(type.create()));
242
- return true;
243
- }
244
-
245
- /**
246
- * main is the entry point of the example.
247
- */
248
- async function main() {
249
- const client = new yorkie.Client({ rpcAddr: 'http://localhost:8080' });
250
- await client.activate();
251
-
252
- // 01. Build yorkie.Text from ProseMirror doc.
253
- const doc = new yorkie.Document('prosemirror', {
254
- enableDevtools: true,
255
- });
256
- window.doc = doc;
257
- await client.attach(doc);
258
- doc.update((root) => {
259
- if (!root.tree) {
260
- root.tree = new yorkie.Tree(docToTreeNode(initialDoc));
261
- }
262
- });
263
-
264
- // 02. Create ProseMirror Editor.
265
- const state = EditorState.create({
266
- doc: Node.fromJSON(
267
- mySchema,
268
- treeNodeToDoc(JSON.parse(doc.getRoot().tree.toJSON())),
269
- ),
270
- plugins: [
271
- ...exampleSetup({ schema: mySchema }).reduce(
272
- (uniquePlugins, plugin) => {
273
- if (!uniquePlugins.some((p) => p.key === plugin.key)) {
274
- uniquePlugins.push(plugin);
275
- }
276
- return uniquePlugins;
277
- },
278
- [],
279
- ),
280
- keymap({
281
- 'Ctrl-b': toggleMark(mySchema.marks.shouting),
282
- 'Ctrl-u': insertStar,
283
- }),
284
- ],
285
- });
286
-
287
- // 03. Upstream: ProseMirror to yorkie.Text.
288
- const view = new EditorView(document.querySelector('#app'), {
289
- state,
290
- dispatchTransaction: (transaction) => {
291
- view.updateState(view.state.apply(transaction));
292
-
293
- // If the steps are empty, it means the transaction is not applied to the document.
294
- // Only the selection is changed.
295
- if (!transaction.steps.length) {
296
- transactions.unshift({
297
- type: 'selection',
298
- selection: transaction.curSelection,
299
- });
300
-
301
- doc.update((root, presence) => {
302
- presence.set({
303
- selection: root.tree.indexRangeToPosRange([
304
- transaction.curSelection.from,
305
- transaction.curSelection.to,
306
- ]),
307
- });
308
- });
309
- paintData();
310
- return;
311
- }
312
-
313
- transactions.unshift({
314
- type: 'transaction',
315
- transaction: transaction,
316
- });
317
-
318
- doc.update((root, presence) => {
319
- for (const step of transaction.steps) {
320
- const {
321
- jsonID: stepType,
322
- from,
323
- to,
324
- gapFrom,
325
- gapTo,
326
- structure,
327
- slice: { content, openStart, openEnd },
328
- } = step;
329
-
330
- // 01. Move: level up/down, move left/right.
331
- if (stepType === 'replaceAround') {
332
- // TODO(hackerwins): replaceAround replaces the given range with given gap.
333
- // root.tree.move(from, to, gapFrom, gapTo);
334
- continue;
335
- }
336
-
337
- // 02. Split: split the given range.
338
- if (
339
- stepType === 'replace' &&
340
- openStart &&
341
- openStart === openEnd &&
342
- structure
343
- ) {
344
- // TODO(hackerwins): we need to handle more step cases.
345
- // - 1. openStart and openEnd can be assymetric.
346
- // - 2. content.content could be more than three.
347
- root.tree.edit(
348
- from,
349
- to,
350
- content.content.length == 3
351
- ? docToTreeNode(content.content[1])
352
- : undefined,
353
- openStart,
354
- );
355
- console.log(
356
- `%c local: ${from}-${to}: split(${openStart})`,
357
- 'color: green',
358
- );
359
- continue;
360
- }
361
-
362
- // 03. Edit: Delete the given range.
363
- if (!content.content.length) {
364
- root.tree.edit(from, to);
365
- console.log(
366
- `%c local: ${from}-${to}: delete)`,
367
- 'color: green',
368
- );
369
- continue;
370
- }
371
-
372
- // 04. Edit: Replace the given range with the given content.
373
- //
374
- // TODO(hackerwins): We need to handle unary element.
375
- // Unary element is a node that has no children. For example, <star></star>.
376
- //
377
- // TODO(hackerwins): We need to handle select all and replace.
378
- // There is an issue when we replace the whole document in ProseMirror.
379
- root.tree.editBulk(
380
- from,
381
- to,
382
- content.content.map((node) => docToTreeNode(node.toJSON())),
383
- );
384
- console.log(
385
- `%c local: ${from}-${to}: ${JSON.stringify(
386
- content.content,
387
- )})`,
388
- 'color: green',
389
- );
390
- }
391
-
392
- presence.set({
393
- selection: root.tree.indexRangeToPosRange([
394
- transaction.curSelection.from,
395
- transaction.curSelection.to,
396
- ]),
397
- });
398
- });
399
- paintData();
400
- },
401
- });
402
-
403
- doc.subscribe('others', (event) => {
404
- if (event.type === 'presence-changed') {
405
- const { clientID, presence } = event.value;
406
- displayRemoteSelection(
407
- view,
408
- doc.getRoot().tree,
409
- presence.selection[0],
410
- presence.selection[1],
411
- clientID,
412
- );
413
- }
414
- });
415
-
416
- // 03. Downstream: yorkie.Tree to ProseMirror.
417
- doc.subscribe((event) => {
418
- if (event.type !== 'remote-change') {
419
- return;
420
- }
421
-
422
- const root = doc.getRoot();
423
- const { operations, actor } = event.value;
424
- let fromPos = -1;
425
- let toPos = -1;
426
- for (const op of operations) {
427
- if (op.type !== 'tree-edit') {
428
- continue;
429
- }
430
- const { from, to, value: contents, splitLevel } = op;
431
- const transform = view.state.tr;
432
- if (splitLevel) {
433
- view.selection;
434
- transform.split(from, splitLevel);
435
- console.log(
436
- `%c remote: ${from}-${to}: split(${splitLevel})`,
437
- 'color: skyblue',
438
- );
439
- } else if (contents) {
440
- console.log(
441
- `%c remote: ${from}-${to}: ${JSON.stringify(contents)}`,
442
- 'color: skyblue',
443
- );
444
- transform.replaceWith(
445
- from,
446
- to,
447
- Array.from(contents).map((content) =>
448
- Node.fromJSON(mySchema, {
449
- type: content.type,
450
- text: content.value,
451
- }),
452
- ),
453
- );
454
- } else {
455
- console.log(`%c remote: ${from}-${to}: delete`, 'color: skyblue');
456
- transform.replace(from, to);
457
- }
458
- const newState = view.state.apply(transform);
459
- view.updateState(newState);
460
- }
461
- paintData();
462
- });
463
-
464
- window.view = view;
465
- view.focus();
466
- paintData();
467
- }
468
-
469
- document.getElementById('tr-log').onclick = (e) => {
470
- const index = e.target.getAttribute('data-index');
471
- if (index) {
472
- paintTransaction(+index);
473
- }
474
- };
475
-
476
- function paintTransaction(index) {
477
- const transaction = transactions[index || 0];
478
-
479
- if (transaction) {
480
- if (transaction.type === 'selection') {
481
- document.getElementById(
482
- 'tr-info',
483
- ).innerHTML = `<pre>selection: ${JSON.stringify(
484
- transaction.selection.toJSON(),
485
- null,
486
- 2,
487
- )}</pre>`;
488
- } else {
489
- document.getElementById(
490
- 'tr-info',
491
- ).innerHTML = `<pre>selection: ${JSON.stringify(
492
- transaction.transaction.curSelection.toJSON(),
493
- null,
494
- 2,
495
- )},\nsteps: ${JSON.stringify(
496
- transaction.transaction.steps,
497
- null,
498
- 2,
499
- )}
500
- </pre>`;
501
- }
502
- }
503
- }
504
-
505
- function paintData() {
506
- document.getElementById('tr-log').innerHTML = transactions
507
- .slice(0, 100)
508
- .map((transaction, index) => {
509
- return transaction.type === 'selection'
510
- ? ''
511
- : `<span data-index="${index}" class="tr-item">T</span>`;
512
- })
513
- .join('');
514
-
515
- paintTree(view.state.doc, 'pm-log');
516
- paintTransaction();
517
- }
518
-
519
- function buildNodes(node, depth, nodes, index) {
520
- const nodeType = node.type.name || node.type;
521
- if (nodeType === 'text') {
522
- nodes.push({
523
- type: nodeType,
524
- depth,
525
- index,
526
- value: node.text || node.value,
527
- });
528
- return;
529
- }
530
-
531
- nodes.push({
532
- type: nodeType,
533
- depth,
534
- index,
535
- size: node.content?.size || node.size,
536
- });
537
- const children = node.content?.content || node.children || [];
538
- for (let i = 0; i < children.length; i++) {
539
- buildNodes(children[i], depth + 1, nodes, i);
540
- }
541
- }
542
-
543
- function paintTree(doc, id) {
544
- const nodes = [];
545
- buildNodes(doc, 0, nodes);
546
-
547
- const html = nodes
548
- .map((node) => {
549
- if (node.type === 'text') {
550
- return `<div class="inline" style="padding-left: ${
551
- node.index === 0 ? node.depth * 14 : 0
552
- }px">${node.value === ' ' ? '&nbsp;&nbsp;' : node.value}</div>`;
553
- }
554
- return `<div class="block" style="padding-left: ${
555
- node.depth * 14
556
- }px;">${node.type}${
557
- node.size ? `<span class="size">(${node.size})</span>` : ''
558
- }</div>`;
559
- })
560
- .join('');
561
- document.getElementById(id).innerHTML = html;
562
- }
563
-
564
- main();
565
- </script>
566
- </body>
567
- </html>