@yorkie-js/sdk 0.6.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 +201 -0
- package/dist/counter.html +93 -0
- package/dist/devtool/object.css +147 -0
- package/dist/devtool/object.js +117 -0
- package/dist/devtool/text.css +452 -0
- package/dist/devtool/text.js +827 -0
- package/dist/drawing.html +181 -0
- package/dist/index.html +471 -0
- package/dist/multi.html +477 -0
- package/dist/prosemirror.css +543 -0
- package/dist/prosemirror.html +566 -0
- package/dist/quill-two-clients.css +97 -0
- package/dist/quill-two-clients.html +504 -0
- package/dist/quill.html +360 -0
- package/dist/style.css +196 -0
- package/dist/util.js +36 -0
- package/dist/whiteboard.css +119 -0
- package/dist/whiteboard.html +384 -0
- package/dist/yorkie-js-sdk.d.ts +6175 -0
- package/dist/yorkie-js-sdk.es.js +21106 -0
- package/dist/yorkie-js-sdk.es.js.map +1 -0
- package/dist/yorkie-js-sdk.js +21110 -0
- package/dist/yorkie-js-sdk.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Drawing Example</title>
|
|
6
|
+
<link rel="stylesheet" href="style.css" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div>
|
|
10
|
+
<div>
|
|
11
|
+
There are currently <span id="online-clients-count"></span> users!
|
|
12
|
+
</div>
|
|
13
|
+
<canvas
|
|
14
|
+
width="500px"
|
|
15
|
+
height="500px"
|
|
16
|
+
id="drawing-panel"
|
|
17
|
+
style="border: 1px solid black"
|
|
18
|
+
></canvas>
|
|
19
|
+
<pre style="white-space: pre-wrap"><code id="online-clients"></code></pre>
|
|
20
|
+
<pre style="white-space: pre-wrap" id="log-holder"></pre>
|
|
21
|
+
</div>
|
|
22
|
+
<script src="./yorkie-js-sdk.js"></script>
|
|
23
|
+
<script>
|
|
24
|
+
const drawingPanel = document.getElementById('drawing-panel');
|
|
25
|
+
const docPanel = document.getElementById('log-holder');
|
|
26
|
+
const onlineClientsPanel = document.getElementById('online-clients');
|
|
27
|
+
const onlineClientsCount = document.getElementById(
|
|
28
|
+
'online-clients-count',
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
function getPoint(e) {
|
|
32
|
+
return {
|
|
33
|
+
x: e.clientX - drawingPanel.offsetLeft + window.scrollX,
|
|
34
|
+
y: e.clientY - drawingPanel.offsetTop + window.scrollY,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
try {
|
|
40
|
+
// 01. create client with RPCAddr then activate it.
|
|
41
|
+
const client = new yorkie.Client('http://localhost:8080', {
|
|
42
|
+
syncLoopDuration: 0,
|
|
43
|
+
reconnectStreamDelay: 1000,
|
|
44
|
+
});
|
|
45
|
+
await client.activate();
|
|
46
|
+
|
|
47
|
+
// 02. create a document then attach it into the client.
|
|
48
|
+
const doc = new yorkie.Document('drawing-panel', {
|
|
49
|
+
enableDevtools: true,
|
|
50
|
+
});
|
|
51
|
+
doc.subscribe('presence', (event) => {
|
|
52
|
+
displayOnlineClients(doc.getPresences(), client.getID());
|
|
53
|
+
if (event.type === 'presence-changed') {
|
|
54
|
+
paintCanvas();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
await client.attach(doc);
|
|
58
|
+
|
|
59
|
+
let draftShape = null;
|
|
60
|
+
function displayLog() {
|
|
61
|
+
docPanel.innerText = JSON.stringify(doc.getRoot().toJS(), null, 2);
|
|
62
|
+
}
|
|
63
|
+
function displayOnlineClients() {
|
|
64
|
+
const clients = doc.getPresences();
|
|
65
|
+
onlineClientsCount.innerHTML = clients.length;
|
|
66
|
+
onlineClientsPanel.innerText = JSON.stringify(clients, null, 2);
|
|
67
|
+
}
|
|
68
|
+
function paintCanvas() {
|
|
69
|
+
// TODO Now repainting the whole thing. Only changed parts should be drawn.
|
|
70
|
+
const context = drawingPanel.getContext('2d');
|
|
71
|
+
context.clearRect(0, 0, 500, 500);
|
|
72
|
+
|
|
73
|
+
const shapes = doc.getRoot().shapes;
|
|
74
|
+
if (!shapes) return;
|
|
75
|
+
for (const shape of shapes) {
|
|
76
|
+
context.beginPath();
|
|
77
|
+
let isMoved = false;
|
|
78
|
+
for (const p of shape.points) {
|
|
79
|
+
if (isMoved === false) {
|
|
80
|
+
isMoved = true;
|
|
81
|
+
context.moveTo(p.x, p.y);
|
|
82
|
+
} else {
|
|
83
|
+
context.lineTo(p.x, p.y);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
context.stroke();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const clients = doc.getPresences();
|
|
90
|
+
for (const client of clients) {
|
|
91
|
+
if (client.presence.draftShape) {
|
|
92
|
+
context.beginPath();
|
|
93
|
+
let isMoved = false;
|
|
94
|
+
for (const p of client.presence.draftShape.points) {
|
|
95
|
+
if (isMoved === false) {
|
|
96
|
+
isMoved = true;
|
|
97
|
+
context.moveTo(p.x, p.y);
|
|
98
|
+
} else {
|
|
99
|
+
context.lineTo(p.x, p.y);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
context.stroke();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
doc.update((root) => {
|
|
108
|
+
if (!root.shapes) {
|
|
109
|
+
root.shapes = [];
|
|
110
|
+
}
|
|
111
|
+
}, 'create points if not exists');
|
|
112
|
+
|
|
113
|
+
doc.subscribe((event) => {
|
|
114
|
+
displayLog();
|
|
115
|
+
paintCanvas();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
document.addEventListener('mousedown', (e) => {
|
|
119
|
+
if (!window.isMouseDown) {
|
|
120
|
+
const point = getPoint(e);
|
|
121
|
+
if (
|
|
122
|
+
point.x < 0 ||
|
|
123
|
+
point.y < 0 ||
|
|
124
|
+
point.x > 500 ||
|
|
125
|
+
point.y > 500
|
|
126
|
+
) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
window.isMouseDown = true;
|
|
130
|
+
|
|
131
|
+
draftShape = { points: [point] };
|
|
132
|
+
doc.update((root, presence) => {
|
|
133
|
+
presence.set({ draftShape });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
document.addEventListener('mousemove', (e) => {
|
|
139
|
+
if (window.isMouseDown) {
|
|
140
|
+
const point = getPoint(e);
|
|
141
|
+
if (
|
|
142
|
+
point.x < 0 ||
|
|
143
|
+
point.y < 0 ||
|
|
144
|
+
point.x > 500 ||
|
|
145
|
+
point.y > 500
|
|
146
|
+
) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
draftShape.points.push(point);
|
|
151
|
+
doc.update((root, presence) => {
|
|
152
|
+
presence.set({ draftShape });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
document.addEventListener('mouseup', (e) => {
|
|
158
|
+
if (window.isMouseDown) {
|
|
159
|
+
window.isMouseDown = false;
|
|
160
|
+
doc.update((root, presence) => {
|
|
161
|
+
if (draftShape) {
|
|
162
|
+
root.shapes.push(draftShape);
|
|
163
|
+
draftShape = null;
|
|
164
|
+
}
|
|
165
|
+
presence.set({ draftShape: null });
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 05. set initial value.
|
|
171
|
+
paintCanvas();
|
|
172
|
+
displayLog();
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error(e);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main();
|
|
179
|
+
</script>
|
|
180
|
+
</body>
|
|
181
|
+
</html>
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>CodeMirror Example</title>
|
|
6
|
+
<link
|
|
7
|
+
rel="stylesheet"
|
|
8
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.css"
|
|
9
|
+
/>
|
|
10
|
+
<link rel="stylesheet" href="style.css" />
|
|
11
|
+
<link rel="stylesheet" href="./devtool/text.css" />
|
|
12
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.js"></script>
|
|
13
|
+
<script src="https://unpkg.com/panzoom@9.4.0/dist/panzoom.min.js"></script>
|
|
14
|
+
<style type="text/css" id="codemirror-custom-style"></style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div class="layout">
|
|
18
|
+
<div class="toolbar">
|
|
19
|
+
<div class="left-tools tools">
|
|
20
|
+
<svg
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
width="100"
|
|
23
|
+
height="38"
|
|
24
|
+
viewBox="0 0 100 38"
|
|
25
|
+
fill="none"
|
|
26
|
+
role="img"
|
|
27
|
+
>
|
|
28
|
+
<mask id="path-1-inside-1_4874_2783" fill="white">
|
|
29
|
+
<path
|
|
30
|
+
d="M11.8574 11.4048L18.8525 21.4507C19.2947 22.086 20.1683 22.2423 20.8036 21.8001C20.9398 21.7052 21.0581 21.5869 21.153 21.4507L28.148 11.4048C29.0327 10.1343 28.7198 8.3872 27.4495 7.5027C26.9794 7.17549 26.4205 7 25.8477 7H14.1577C12.6095 7 11.3545 8.25503 11.3545 9.80322C11.3547 10.3758 11.5302 10.9347 11.8574 11.4048Z"
|
|
31
|
+
></path>
|
|
32
|
+
</mask>
|
|
33
|
+
<path
|
|
34
|
+
d="M11.8574 11.4048L18.8525 21.4507C19.2947 22.086 20.1683 22.2423 20.8036 21.8001C20.9398 21.7052 21.0581 21.5869 21.153 21.4507L28.148 11.4048C29.0327 10.1343 28.7198 8.3872 27.4495 7.5027C26.9794 7.17549 26.4205 7 25.8477 7H14.1577C12.6095 7 11.3545 8.25503 11.3545 9.80322C11.3547 10.3758 11.5302 10.9347 11.8574 11.4048Z"
|
|
35
|
+
fill="#514C49"
|
|
36
|
+
stroke="#FEFDFB"
|
|
37
|
+
stroke-miterlimit="10"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
mask="url(#path-1-inside-1_4874_2783)"
|
|
41
|
+
></path>
|
|
42
|
+
<path
|
|
43
|
+
d="M22.8637 29.5446C23.3612 29.8283 23.9338 29.9528 24.5042 29.9014L37.2991 28.7469C38.3271 28.6542 39.0851 27.7457 38.9924 26.7178C38.9876 26.6636 38.9803 26.6096 38.9706 26.556C38.5862 24.4114 37.8296 22.3507 36.7352 20.4668C35.6407 18.5829 34.2255 16.9048 32.5532 15.5085C31.761 14.8471 30.5825 14.953 29.9211 15.7455C29.8862 15.7872 29.8532 15.8305 29.8219 15.8752L22.4807 26.418C22.1535 26.888 21.978 27.4469 21.978 28.0198V27.9849C21.978 28.3055 22.0604 28.6208 22.2176 28.9002C22.3826 29.1751 22.6155 29.4029 22.8942 29.5617"
|
|
44
|
+
fill="#FDC433"
|
|
45
|
+
></path>
|
|
46
|
+
<path
|
|
47
|
+
d="M17.8492 28.7605C17.6844 29.097 17.4222 29.376 17.0969 29.5616L17.1365 29.539C16.6391 29.8227 16.0665 29.9472 15.4961 29.8959L2.70114 28.7414C2.64694 28.7365 2.59295 28.7293 2.53935 28.7196C1.52348 28.5375 0.847507 27.5663 1.02965 26.5505C1.41407 24.4057 2.17064 22.3451 3.26489 20.4611C4.35914 18.577 5.77455 16.8993 7.44706 15.5028C7.48877 15.4679 7.53208 15.4349 7.57681 15.4037C8.42384 14.8139 9.58841 15.0225 10.1784 15.8695L17.5196 26.4124C17.8468 26.8825 18.0223 27.4414 18.0223 28.0142V27.9685C18.0223 28.343 17.9096 28.7091 17.6991 29.019"
|
|
48
|
+
fill="#FDC433"
|
|
49
|
+
></path>
|
|
50
|
+
<path
|
|
51
|
+
d="M46 13.0243L50.3839 20.7431V24.9463H52.4806V20.7272L56.8645 13.0243H54.6726L51.7658 18.3231L51.4164 19.0237L51.0669 18.3231L48.192 13.0243H46Z"
|
|
52
|
+
fill="#332E2B"
|
|
53
|
+
></path>
|
|
54
|
+
<path
|
|
55
|
+
d="M61.4832 15.7917C58.9259 15.7917 57.0358 17.6863 57.0358 20.4884C57.0358 23.2905 58.9259 25.1851 61.4832 25.1851C64.0087 25.1851 65.9307 23.2905 65.9307 20.4884C65.9307 17.6863 64.0087 15.7917 61.4832 15.7917ZM61.4832 23.4656C60.149 23.4656 59.0689 22.2556 59.0689 20.4884C59.0689 18.7212 60.149 17.5112 61.4832 17.5112C62.8175 17.5112 63.8976 18.7212 63.8976 20.4884C63.8976 22.2556 62.8175 23.4656 61.4832 23.4656Z"
|
|
56
|
+
fill="#332E2B"
|
|
57
|
+
></path>
|
|
58
|
+
<path
|
|
59
|
+
d="M73.6325 15.8076C72.6 15.8076 71.5676 16.0783 70.821 16.7788V16.0305H68.7879V24.9463H70.821V21.2685C70.821 20.3929 70.8846 19.8356 70.964 19.5013C71.234 18.3391 72.2982 17.543 73.6325 17.6067V15.8076Z"
|
|
60
|
+
fill="#332E2B"
|
|
61
|
+
></path>
|
|
62
|
+
<path
|
|
63
|
+
d="M82.1798 24.9463H84.6259L80.6232 20.2177L84.2288 16.0305H81.7827L78.6059 19.8516V13.0779H76.5728V24.9463H78.6059V20.6476L82.1798 24.9463Z"
|
|
64
|
+
fill="#332E2B"
|
|
65
|
+
></path>
|
|
66
|
+
<path
|
|
67
|
+
d="M88.6995 16.0305H86.6664V24.9463H88.6995V16.0305ZM86.5711 15.0434H88.7948V12.8145H86.5711V15.0434Z"
|
|
68
|
+
fill="#332E2B"
|
|
69
|
+
></path>
|
|
70
|
+
<path
|
|
71
|
+
d="M98.3957 21.8258C97.8875 22.781 96.9344 23.4656 95.8543 23.4656C94.6313 23.4656 93.7259 22.4626 93.5512 21.2526H100V20.2814C100 17.6704 98.2051 15.7917 95.7114 15.7917C93.3288 15.7917 91.4228 17.6863 91.4228 20.4884C91.4228 23.2905 93.2176 25.1851 95.8543 25.1851C97.5062 25.1851 98.9834 24.3095 99.8729 22.9562L98.3957 21.8258ZM93.5353 19.565C93.7894 18.2913 94.6313 17.5112 95.7114 17.5112C96.8709 17.5112 97.6651 18.3709 97.9033 19.565H93.5353Z"
|
|
72
|
+
fill="#332E2B"
|
|
73
|
+
></path>
|
|
74
|
+
</svg>
|
|
75
|
+
<div><span id="network-status"></span></div>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="center-tools tools">
|
|
78
|
+
<label style="display: flex; align-items: center">
|
|
79
|
+
<input
|
|
80
|
+
type="checkbox"
|
|
81
|
+
id="hide-deleted-node"
|
|
82
|
+
onClick="devtool.toggleDeletedNodeShow()"
|
|
83
|
+
/>
|
|
84
|
+
<span>Hide deleted node</span>
|
|
85
|
+
</label>
|
|
86
|
+
<label style="display: flex; align-items: center">
|
|
87
|
+
<input
|
|
88
|
+
type="checkbox"
|
|
89
|
+
id="hide-text"
|
|
90
|
+
onClick="devtool.toggleText()"
|
|
91
|
+
/>
|
|
92
|
+
<span>Text</span>
|
|
93
|
+
</label>
|
|
94
|
+
<label style="display: flex; align-items: center">
|
|
95
|
+
<input
|
|
96
|
+
type="checkbox"
|
|
97
|
+
id="hide-splay-tree"
|
|
98
|
+
onClick="devtool.toggleSplayTree()"
|
|
99
|
+
/>
|
|
100
|
+
<span>SplayTree</span>
|
|
101
|
+
</label>
|
|
102
|
+
<label style="display: flex; align-items: center">
|
|
103
|
+
<input
|
|
104
|
+
type="checkbox"
|
|
105
|
+
id="hide-llrb-tree"
|
|
106
|
+
onClick="devtool.toggleLLRBTree()"
|
|
107
|
+
/>
|
|
108
|
+
<span>LLRBTree</span>
|
|
109
|
+
</label>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="right-tools tools">
|
|
112
|
+
<div style="padding: 10px 0px" id="users-holder"></div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div id="real-view" class="content">
|
|
116
|
+
<div class="editor-area">
|
|
117
|
+
<div class="codemirror-area">
|
|
118
|
+
<textarea id="textarea" cols="30" rows="10"></textarea>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="data-area" id="view-text">
|
|
121
|
+
<div class="text-view-area">
|
|
122
|
+
<div id="tab-container" class="tab">
|
|
123
|
+
<div class="tab-header">
|
|
124
|
+
<button
|
|
125
|
+
class="tab-button active"
|
|
126
|
+
onclick="devtool.openTab(event, 'text')"
|
|
127
|
+
>
|
|
128
|
+
Text
|
|
129
|
+
</button>
|
|
130
|
+
<button
|
|
131
|
+
class="tab-button"
|
|
132
|
+
onclick="devtool.openTab(event, 'structure')"
|
|
133
|
+
>
|
|
134
|
+
Structure Data
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
class="tab-button"
|
|
138
|
+
onclick="devtool.openTab(event, 'structure-text')"
|
|
139
|
+
>
|
|
140
|
+
Structure Text
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
<div id="structure-info-holder" class="tab-body">
|
|
144
|
+
<div id="text" class="tabcontent active">
|
|
145
|
+
<div id="text-log-holder"></div>
|
|
146
|
+
</div>
|
|
147
|
+
<div id="structure" class="tabcontent">
|
|
148
|
+
<div id="structure-log-holder"></div>
|
|
149
|
+
</div>
|
|
150
|
+
<div id="structure-text" class="tabcontent">
|
|
151
|
+
<div id="structure-text-holder"></div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="selected-node-info-area">
|
|
156
|
+
<h4 class="title">Selected SplayTree Node</h4>
|
|
157
|
+
<div class="selected-node-info"></div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="graph-view">
|
|
163
|
+
<div class="graph-area" id="view-splay-tree">
|
|
164
|
+
<h4 class="title">
|
|
165
|
+
<span>SplayTree</span>
|
|
166
|
+
<span class="badge" id="splaytree-info"></span>
|
|
167
|
+
</h4>
|
|
168
|
+
<div class="tree-area">
|
|
169
|
+
<div id="splaytree-log-holder"></div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="graph-area" id="view-llrb-tree">
|
|
173
|
+
<h4 class="title">
|
|
174
|
+
<span>LLRBTree</span>
|
|
175
|
+
<span class="badge" id="llrbtree-info"></span>
|
|
176
|
+
</h4>
|
|
177
|
+
<div class="tree-area">
|
|
178
|
+
<div id="llrbtree-log-holder"></div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<script src="./yorkie-js-sdk.js"></script>
|
|
185
|
+
<script src="./util.js"></script>
|
|
186
|
+
<script src="./devtool/text.js"></script>
|
|
187
|
+
<script>
|
|
188
|
+
const textarea = document.getElementById('textarea');
|
|
189
|
+
const statusHolder = document.getElementById('network-status');
|
|
190
|
+
const usersHolder = document.getElementById('users-holder');
|
|
191
|
+
const selectionMap = new Map();
|
|
192
|
+
|
|
193
|
+
function getRandomColor() {
|
|
194
|
+
const colors = ['#FFC75F', '#FF6F91', '#00C9A7', '#F3C5FF', '#009EFA'];
|
|
195
|
+
const randomIndex = Math.floor(Math.random() * colors.length);
|
|
196
|
+
return colors[randomIndex];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function displayUsers(users, myClientID) {
|
|
200
|
+
const usersInfo = Object.fromEntries(
|
|
201
|
+
users.map((user) => {
|
|
202
|
+
const id = user.clientID;
|
|
203
|
+
return [
|
|
204
|
+
id,
|
|
205
|
+
{
|
|
206
|
+
id,
|
|
207
|
+
ticker: id.substr(-2),
|
|
208
|
+
user,
|
|
209
|
+
color: user.presence.color,
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
devtool.setUsersInfo(usersInfo);
|
|
215
|
+
|
|
216
|
+
usersHolder.innerHTML = users
|
|
217
|
+
.map(({ clientID: id }) => {
|
|
218
|
+
const color = usersInfo[id].color;
|
|
219
|
+
const ticker = usersInfo[id].ticker;
|
|
220
|
+
|
|
221
|
+
const idString = `<span class="user-info ${
|
|
222
|
+
id === myClientID ? 'me' : ''
|
|
223
|
+
}" style="--client-color: ${color}" data-id="${id}" title="${id}"> ${ticker}</span>`;
|
|
224
|
+
|
|
225
|
+
return idString;
|
|
226
|
+
})
|
|
227
|
+
.join('');
|
|
228
|
+
|
|
229
|
+
document.head.querySelector('#codemirror-custom-style').textContent = `
|
|
230
|
+
.CodeMirror-selected {
|
|
231
|
+
background-color: ${usersInfo[myClientID]?.color} !important;
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// https://github.com/codemirror/CodeMirror/pull/5619
|
|
237
|
+
function replaceRangeFix(cm, text, from, to, origin) {
|
|
238
|
+
const adjust = cm.listSelections().findIndex(({ anchor, head }) => {
|
|
239
|
+
return (
|
|
240
|
+
CodeMirror.cmpPos(anchor, head) === 0 &&
|
|
241
|
+
CodeMirror.cmpPos(anchor, from) === 0
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
cm.operation(() => {
|
|
245
|
+
cm.replaceRange(text, from, to, origin);
|
|
246
|
+
if (adjust > -1) {
|
|
247
|
+
const range = cm.listSelections()[adjust];
|
|
248
|
+
if (
|
|
249
|
+
range &&
|
|
250
|
+
CodeMirror.cmpPos(
|
|
251
|
+
range.head,
|
|
252
|
+
CodeMirror.changeEnd({ from, to, text }),
|
|
253
|
+
) === 0
|
|
254
|
+
) {
|
|
255
|
+
const ranges = cm.listSelections().slice();
|
|
256
|
+
ranges[adjust] = { anchor: from, head: from };
|
|
257
|
+
cm.setSelections(ranges);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function displayRemoteSelection(cm, doc, user) {
|
|
264
|
+
const { clientID, presence } = user;
|
|
265
|
+
if (!presence.selection) return;
|
|
266
|
+
if (selectionMap.has(clientID)) {
|
|
267
|
+
const selection = selectionMap.get(clientID);
|
|
268
|
+
selection.marker.clear();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const [from, to] = doc
|
|
272
|
+
.getRoot()
|
|
273
|
+
.content.posRangeToIndexRange(presence.selection);
|
|
274
|
+
|
|
275
|
+
console.log(
|
|
276
|
+
`%c remote selection from:${from} to:${to}`,
|
|
277
|
+
'color: skyblue',
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (from === to) {
|
|
281
|
+
const pos = cm.posFromIndex(from);
|
|
282
|
+
const cursorCoords = cm.cursorCoords(pos);
|
|
283
|
+
const cursorElement = document.createElement('span');
|
|
284
|
+
cursorElement.style.borderLeftWidth = '2px';
|
|
285
|
+
cursorElement.style.borderLeftStyle = 'solid';
|
|
286
|
+
cursorElement.style.borderLeftColor = presence.color;
|
|
287
|
+
cursorElement.style.marginLeft = cursorElement.style.marginRight =
|
|
288
|
+
'-1px';
|
|
289
|
+
cursorElement.style.height =
|
|
290
|
+
(cursorCoords.bottom - cursorCoords.top) * 0.9 + 'px';
|
|
291
|
+
cursorElement.setAttribute('data-actor-id', clientID);
|
|
292
|
+
cursorElement.style.zIndex = 0;
|
|
293
|
+
|
|
294
|
+
selectionMap.set(clientID, {
|
|
295
|
+
marker: cm.setBookmark(pos, {
|
|
296
|
+
widget: cursorElement,
|
|
297
|
+
insertLeft: true,
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
const fromPos = cm.posFromIndex(Math.min(from, to));
|
|
302
|
+
const toPos = cm.posFromIndex(Math.max(from, to));
|
|
303
|
+
|
|
304
|
+
selectionMap.set(clientID, {
|
|
305
|
+
marker: cm.markText(fromPos, toPos, {
|
|
306
|
+
css: `background: ${presence.color}`,
|
|
307
|
+
insertLeft: true,
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function main() {
|
|
314
|
+
try {
|
|
315
|
+
// 01. create an instance of codemirror.
|
|
316
|
+
const codemirror = CodeMirror.fromTextArea(textarea, {
|
|
317
|
+
lineNumbers: true,
|
|
318
|
+
});
|
|
319
|
+
yorkie.setLogLevel(yorkie.LogLevel.Debug);
|
|
320
|
+
devtool.setCodeMirror(codemirror);
|
|
321
|
+
|
|
322
|
+
// 02-1. create client with RPCAddr.
|
|
323
|
+
const client = new yorkie.Client('http://localhost:8080');
|
|
324
|
+
// 02-2. activate client
|
|
325
|
+
await client.activate();
|
|
326
|
+
|
|
327
|
+
// 03-1. create a document then attach it into the client.
|
|
328
|
+
const doc = new yorkie.Document('codemirror', {
|
|
329
|
+
enableDevtools: true,
|
|
330
|
+
});
|
|
331
|
+
window.doc = doc;
|
|
332
|
+
|
|
333
|
+
doc.subscribe('connection', new Network(statusHolder).statusListener);
|
|
334
|
+
doc.subscribe('presence', (event) => {
|
|
335
|
+
if (event.type === 'presence-changed') return;
|
|
336
|
+
displayUsers(doc.getPresences(), client.getID());
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await client.attach(doc, {
|
|
340
|
+
initialPresence: { color: getRandomColor() },
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
doc.update((root) => {
|
|
344
|
+
if (!root.content) {
|
|
345
|
+
root.content = new yorkie.Text();
|
|
346
|
+
}
|
|
347
|
+
}, 'create content if not exists');
|
|
348
|
+
|
|
349
|
+
// 03-2. subscribe document event.
|
|
350
|
+
doc.subscribe((event) => {
|
|
351
|
+
if (event.type === 'snapshot') {
|
|
352
|
+
codemirror.setValue(doc.getRoot().content.toString());
|
|
353
|
+
}
|
|
354
|
+
devtool.displayLog(doc, codemirror);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
doc.subscribe('others', (event) => {
|
|
358
|
+
if (event.type === 'unwatched') {
|
|
359
|
+
const { clientID } = event.value;
|
|
360
|
+
if (selectionMap.has(clientID)) {
|
|
361
|
+
const selection = selectionMap.get(clientID);
|
|
362
|
+
selection.marker.clear();
|
|
363
|
+
}
|
|
364
|
+
} else if (event.type === 'presence-changed') {
|
|
365
|
+
displayRemoteSelection(codemirror, doc, event.value);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
doc.subscribe('$.content', (event) => {
|
|
370
|
+
if (event.type === 'remote-change') {
|
|
371
|
+
const { actor, operations } = event.value;
|
|
372
|
+
handleOperations(operations, actor);
|
|
373
|
+
|
|
374
|
+
const textLength = codemirror.getValue().length;
|
|
375
|
+
if (
|
|
376
|
+
doc.getRoot().content.length !=
|
|
377
|
+
doc.getRoot().content.toString().length ||
|
|
378
|
+
(textLength != doc.getRoot().content.length && textLength != 0)
|
|
379
|
+
) {
|
|
380
|
+
debugger;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// 04. bind the document with the codemirror.
|
|
386
|
+
// 04-1. codemirror to document(applying local).
|
|
387
|
+
codemirror.on('beforeChange', (cm, change) => {
|
|
388
|
+
if (change.origin === 'yorkie' || change.origin === 'setValue') {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const from = cm.indexFromPos(change.from);
|
|
393
|
+
const to = cm.indexFromPos(change.to);
|
|
394
|
+
const content = change.text.join('\n');
|
|
395
|
+
|
|
396
|
+
doc.update((root, presence) => {
|
|
397
|
+
const range = root.content.edit(from, to, content);
|
|
398
|
+
presence.set({
|
|
399
|
+
selection: root.content.indexRangeToPosRange(range),
|
|
400
|
+
});
|
|
401
|
+
}, `update content by ${client.getID()}`);
|
|
402
|
+
console.log(`%c local: ${from}-${to}: ${content}`, 'color: green');
|
|
403
|
+
});
|
|
404
|
+
codemirror.on('change', (cm, change) => {
|
|
405
|
+
if (change.origin === 'yorkie' || change.origin === 'setValue') {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const textLength = codemirror.getValue().length;
|
|
410
|
+
if (
|
|
411
|
+
doc.getRoot().content.length !=
|
|
412
|
+
doc.getRoot().content.toString().length ||
|
|
413
|
+
(textLength != doc.getRoot().content.length && textLength != 0)
|
|
414
|
+
) {
|
|
415
|
+
debugger;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
codemirror.on('beforeSelectionChange', (cm, change) => {
|
|
420
|
+
// NOTE: The following conditional statement ignores cursor changes
|
|
421
|
+
// that occur while applying remote changes to CodeMirror
|
|
422
|
+
// and handles only movement by keyboard and mouse.
|
|
423
|
+
if (change.origin === undefined) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const from = cm.indexFromPos(change.ranges[0].anchor);
|
|
428
|
+
const to = cm.indexFromPos(change.ranges[0].head);
|
|
429
|
+
|
|
430
|
+
doc.update((root, presence) => {
|
|
431
|
+
presence.set({
|
|
432
|
+
selection: root.content.indexRangeToPosRange([from, to]),
|
|
433
|
+
});
|
|
434
|
+
}, `update selection by ${client.getID()}`);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// 04-2. document to codemirror(applying remote).
|
|
438
|
+
function handleOperations(ops, actor) {
|
|
439
|
+
for (const op of ops) {
|
|
440
|
+
if (op.type === 'edit') {
|
|
441
|
+
const from = op.from;
|
|
442
|
+
const to = op.to;
|
|
443
|
+
const content = op.value.content || '';
|
|
444
|
+
|
|
445
|
+
console.log(
|
|
446
|
+
`%c remote: ${from}-${to}: ${content}`,
|
|
447
|
+
'color: skyblue',
|
|
448
|
+
);
|
|
449
|
+
const fromIdx = codemirror.posFromIndex(from);
|
|
450
|
+
const toIdx = codemirror.posFromIndex(to);
|
|
451
|
+
replaceRangeFix(codemirror, content, fromIdx, toIdx, 'yorkie');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 05. synchronize text of document and codemirror.
|
|
457
|
+
codemirror.setValue(doc.getRoot().content.toString());
|
|
458
|
+
devtool.displayLog(doc, codemirror);
|
|
459
|
+
for (const user of doc.getPresences()) {
|
|
460
|
+
if (user.clientID === client.getID()) continue;
|
|
461
|
+
displayRemoteSelection(codemirror, doc, user);
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
console.error(e);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
main();
|
|
469
|
+
</script>
|
|
470
|
+
</body>
|
|
471
|
+
</html>
|