braid-text 0.4.0 → 0.5.3
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/client/cursor-sync.js +24 -3
- package/client/editor.html +1 -1
- package/client/markdown-editor.html +1 -1
- package/client/simpleton-sync.js +22 -0
- package/client/textarea-highlights.js +21 -26
- package/package.json +9 -2
- package/server-demo.js +1 -1
- package/server.js +823 -120
package/client/cursor-sync.js
CHANGED
|
@@ -343,7 +343,8 @@ function cursor_highlights(textarea, url, options) {
|
|
|
343
343
|
if (!ranges.length) { hl.remove(id); continue }
|
|
344
344
|
hl.set(id, ranges.map(r => ({
|
|
345
345
|
from: r.from, to: r.to,
|
|
346
|
-
color:
|
|
346
|
+
color: peer_color(id),
|
|
347
|
+
bg_color: peer_bg_color(id)
|
|
347
348
|
})))
|
|
348
349
|
}
|
|
349
350
|
hl.render()
|
|
@@ -354,16 +355,36 @@ function cursor_highlights(textarea, url, options) {
|
|
|
354
355
|
if (destroyed) client.destroy()
|
|
355
356
|
})
|
|
356
357
|
|
|
358
|
+
// Track the anchor (non-moving end) of selections. We record the cursor
|
|
359
|
+
// position whenever the selection is collapsed; when it later expands,
|
|
360
|
+
// the anchor tells us which end the caret is on. This works for both
|
|
361
|
+
// mouse drags and keyboard selections, and avoids relying on
|
|
362
|
+
// selectionDirection which returns "none" for mouse drags on macOS.
|
|
363
|
+
var anchor = null
|
|
364
|
+
|
|
365
|
+
function get_selection_range() {
|
|
366
|
+
var start = textarea.selectionStart
|
|
367
|
+
var end = textarea.selectionEnd
|
|
368
|
+
if (textarea.selectionDirection === 'backward')
|
|
369
|
+
return [end, start]
|
|
370
|
+
if (anchor === start || anchor === end)
|
|
371
|
+
return [anchor, (anchor === start) ? end : start]
|
|
372
|
+
return [start, end]
|
|
373
|
+
}
|
|
374
|
+
|
|
357
375
|
function on_selectionchange() {
|
|
358
376
|
if (applying_remote) return
|
|
359
377
|
if (document.activeElement !== textarea) return
|
|
360
|
-
|
|
378
|
+
var start = textarea.selectionStart
|
|
379
|
+
var end = textarea.selectionEnd
|
|
380
|
+
if (start === end) anchor = start
|
|
381
|
+
if (client) client.set(...get_selection_range())
|
|
361
382
|
}
|
|
362
383
|
document.addEventListener('selectionchange', on_selectionchange)
|
|
363
384
|
|
|
364
385
|
// Show own cursor when blurred, hide when focused
|
|
365
386
|
function on_blur() {
|
|
366
|
-
if (client) client.set(
|
|
387
|
+
if (client) client.set(...get_selection_range())
|
|
367
388
|
}
|
|
368
389
|
function on_focus() {
|
|
369
390
|
hl.remove(peer)
|
package/client/editor.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
6
6
|
<script src="/web-utils.js"></script>
|
|
7
7
|
<script src="/simpleton-sync.js"></script>
|
|
8
|
-
<script src="/
|
|
8
|
+
<script src="/textarea-highlights.js"></script>
|
|
9
9
|
<script src="/cursor-sync.js"></script>
|
|
10
10
|
<script>
|
|
11
11
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
5
5
|
<script src="/web-utils.js"></script>
|
|
6
6
|
<script src="/simpleton-sync.js"></script>
|
|
7
|
-
<script src="/
|
|
7
|
+
<script src="/textarea-highlights.js"></script>
|
|
8
8
|
<script src="/cursor-sync.js"></script>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="markdown_container" style="max-width: 750px; padding: 10px 60px"></div>
|
package/client/simpleton-sync.js
CHANGED
|
@@ -447,14 +447,36 @@ function simpleton_client(url, {
|
|
|
447
447
|
var min_len = Math.min(old_str.length, new_str.length)
|
|
448
448
|
while (prefix_len < min_len && old_str[prefix_len] === new_str[prefix_len]) prefix_len++
|
|
449
449
|
|
|
450
|
+
// Don't split a surrogate pair: if prefix ends on a low surrogate,
|
|
451
|
+
// the preceding high surrogate only matched by coincidence (same
|
|
452
|
+
// Unicode block), so back up to include the whole pair in the diff.
|
|
453
|
+
if (prefix_len > 0 && is_low_surrogate(old_str, prefix_len))
|
|
454
|
+
prefix_len--
|
|
455
|
+
|
|
450
456
|
// Find common suffix length (from what remains after prefix)
|
|
451
457
|
var suffix_len = 0
|
|
452
458
|
min_len -= prefix_len
|
|
453
459
|
while (suffix_len < min_len && old_str[old_str.length - suffix_len - 1] === new_str[new_str.length - suffix_len - 1]) suffix_len++
|
|
454
460
|
|
|
461
|
+
// Same guard for suffixes: if the range end (old_str.length - suffix_len)
|
|
462
|
+
// lands on a low surrogate, the suffix consumed it without its high
|
|
463
|
+
// surrogate, so back up.
|
|
464
|
+
if (suffix_len > 0 && is_low_surrogate(old_str, old_str.length - suffix_len))
|
|
465
|
+
suffix_len--
|
|
466
|
+
|
|
455
467
|
return {range: [prefix_len, old_str.length - suffix_len], content: new_str.slice(prefix_len, new_str.length - suffix_len)}
|
|
456
468
|
}
|
|
457
469
|
|
|
470
|
+
function is_low_surrogate(str, i) {
|
|
471
|
+
var c = str.charCodeAt(i)
|
|
472
|
+
return c >= 0xdc00 && c <= 0xdfff
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function is_high_surrogate(str, i) {
|
|
476
|
+
var c = str.charCodeAt(i)
|
|
477
|
+
return c >= 0xd800 && c <= 0xdbff
|
|
478
|
+
}
|
|
479
|
+
|
|
458
480
|
// ── apply_patches ───────────────────────────────────────────────────
|
|
459
481
|
// Applies patches to a string, tracking cumulative offset.
|
|
460
482
|
// Used when on_patches is not provided, to update
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Usage:
|
|
10
10
|
// var hl = textarea_highlights(textarea)
|
|
11
|
-
// hl.set('peer-1', [{ from: 5, to: 10, color: 'rgba(97,175,239,0.25)' }])
|
|
11
|
+
// hl.set('peer-1', [{ from: 5, to: 10, color: '#61afef', bg_color: 'rgba(97,175,239,0.25)' }])
|
|
12
12
|
// hl.render()
|
|
13
13
|
// hl.remove('peer-1')
|
|
14
14
|
// hl.destroy()
|
|
@@ -134,38 +134,33 @@ function textarea_highlights(textarea) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
function build_html(text, highlights) {
|
|
137
|
-
var
|
|
138
|
-
|
|
137
|
+
var items = highlights.map(h => ({
|
|
138
|
+
...h,
|
|
139
|
+
start: Math.min(h.from, h.to),
|
|
140
|
+
end: Math.max(h.from, h.to)
|
|
141
|
+
}))
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
for (var s of sels)
|
|
142
|
-
items.push({ type: 'selection', start: s.from, end: s.to, color: s.color })
|
|
143
|
-
for (var c of cursors)
|
|
144
|
-
items.push({ type: 'cursor', pos: c.from, color: c.color })
|
|
145
|
-
|
|
146
|
-
items.sort((a, b) => {
|
|
147
|
-
var pa = a.type === 'cursor' ? a.pos : a.start
|
|
148
|
-
var pb = b.type === 'cursor' ? b.pos : b.start
|
|
149
|
-
return pa - pb
|
|
150
|
-
})
|
|
143
|
+
items.sort((a, b) => a.start - b.start)
|
|
151
144
|
|
|
152
145
|
var result = ''
|
|
153
146
|
var last = 0
|
|
154
147
|
|
|
155
148
|
for (var item of items) {
|
|
156
|
-
if (item.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
149
|
+
if (item.start < last) continue
|
|
150
|
+
result += escape_html(text.substring(last, item.start))
|
|
151
|
+
|
|
152
|
+
var cursor_html = '<span class="cursor" style="--cursor-color:' + item.color + ';"></span>'
|
|
153
|
+
|
|
154
|
+
if (item.start === item.end) {
|
|
155
|
+
result += cursor_html
|
|
163
156
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
var sel_html = escape_html(text.substring(item.start, item.end)).replace(/\n/g, ' \n')
|
|
158
|
+
var before = item.to === item.start ? cursor_html : ''
|
|
159
|
+
var after = item.to === item.start ? '' : cursor_html
|
|
160
|
+
result += '<span class="sel" style="background-color:' + item.bg_color + ';">' + before + sel_html + '</span>' + after
|
|
168
161
|
}
|
|
162
|
+
|
|
163
|
+
last = item.end
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
result += escape_html(text.substring(last))
|
|
@@ -191,9 +186,9 @@ function textarea_highlights(textarea) {
|
|
|
191
186
|
// Render each layer
|
|
192
187
|
for (var id of Object.keys(layer_data)) {
|
|
193
188
|
var highlights = layer_data[id].map(h => ({
|
|
189
|
+
...h,
|
|
194
190
|
from: Math.min(h.from, len),
|
|
195
191
|
to: Math.min(h.to, len),
|
|
196
|
-
color: h.color
|
|
197
192
|
}))
|
|
198
193
|
|
|
199
194
|
if (!layer_divs[id]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braid-text",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Library for collaborative text over http using braid.",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braid-text",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"simpleton-client.js",
|
|
17
17
|
"client/simpleton-sync.js",
|
|
18
18
|
"client/cursor-sync.js",
|
|
19
|
-
"client/cursor-highlights.js",
|
|
20
19
|
"client/textarea-highlights.js",
|
|
21
20
|
"client/web-utils.js",
|
|
22
21
|
"README.md",
|
|
@@ -27,5 +26,13 @@
|
|
|
27
26
|
"dependencies": {
|
|
28
27
|
"@braid.org/diamond-types-node": "^2.0.1",
|
|
29
28
|
"braid-http": "^1.3.106"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"yjs": "^13.6.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"yjs": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
}
|
package/server-demo.js
CHANGED
|
@@ -21,7 +21,7 @@ var server = require("http").createServer(async (req, res) => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (req.url === '/simpleton-sync.js' || req.url === '/web-utils.js'
|
|
24
|
-
|| req.url === '/
|
|
24
|
+
|| req.url === '/textarea-highlights.js' || req.url === '/cursor-sync.js') {
|
|
25
25
|
res.writeHead(200, { "Content-Type": "text/javascript", "Cache-Control": "no-cache" })
|
|
26
26
|
require("fs").createReadStream("./client" + req.url).pipe(res)
|
|
27
27
|
return
|