braid-text 0.5.9 → 0.5.12
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/editor.html +1 -1
- package/client/simpleton.js +2 -2
- package/client/syncarea.js +10 -7
- package/package.json +5 -2
- package/server.js +39 -33
package/client/editor.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<body style="margin: 0; padding: 0; box-sizing: border-box">
|
|
2
|
-
<
|
|
2
|
+
<sync-area id="the_editor" style="width: 100%; height: 100vh;"></sync-area>
|
|
3
3
|
</body>
|
|
4
4
|
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
5
5
|
<script src="/myers-diff.js"></script>
|
package/client/simpleton.js
CHANGED
package/client/syncarea.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
// <
|
|
2
|
+
// <sync-area> — a textarea wired to a braid-http resource
|
|
3
3
|
// ============================================================
|
|
4
4
|
//
|
|
5
5
|
// A drop-in replacement for <textarea> that stays in sync with a
|
|
@@ -12,19 +12,20 @@
|
|
|
12
12
|
// src — the URL to sync to
|
|
13
13
|
// cursors — sync cursors too (default: true; set cursors="false" to disable)
|
|
14
14
|
// bearer — Bearer token for servers using "Authorization: Bearer X" headers
|
|
15
|
+
// accept — Accept (and Content-Type) for the resource (e.g. "text/markdown")
|
|
15
16
|
//
|
|
16
17
|
// The following attributes are relayed to the inner textarea:
|
|
17
18
|
// placeholder, readonly, rows, cols, wrap, spellcheck, autofocus,
|
|
18
19
|
// disabled, aria-label, aria-labelledby, aria-describedby
|
|
19
20
|
//
|
|
20
|
-
// Changes to src, cursors, or
|
|
21
|
+
// Changes to src, cursors, bearer, or accept restart the sync.
|
|
21
22
|
// Changes to any other attribute are just forwarded to the inner textarea.
|
|
22
23
|
//
|
|
23
24
|
// You can always manipulate the inner textarea directly via the .textarea property.
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
// Attributes in this list restart the sync when they change.
|
|
27
|
-
const CONNECTION_ATTRS = ['src', 'bearer', 'cursors']
|
|
28
|
+
const CONNECTION_ATTRS = ['src', 'bearer', 'cursors', 'accept']
|
|
28
29
|
|
|
29
30
|
// Attributes in this list are relayed straight to the inner textarea.
|
|
30
31
|
const FORWARD_ATTRS = ['placeholder', 'readonly', 'rows', 'cols',
|
|
@@ -32,7 +33,7 @@ const FORWARD_ATTRS = ['placeholder', 'readonly', 'rows', 'cols',
|
|
|
32
33
|
'aria-label', 'aria-labelledby', 'aria-describedby']
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
class
|
|
36
|
+
class SyncArea extends HTMLElement {
|
|
36
37
|
|
|
37
38
|
static observedAttributes = [...CONNECTION_ATTRS, ...FORWARD_ATTRS, 'disabled']
|
|
38
39
|
|
|
@@ -58,8 +59,9 @@ class SyncedTextarea extends HTMLElement {
|
|
|
58
59
|
// Builds the inner textarea, wires up events, starts simpleton_client
|
|
59
60
|
// and cursor_highlights if enabled.
|
|
60
61
|
connect() {
|
|
62
|
+
console.log('DBG sync-area: connect', {src: this.getAttribute('src'), accept: this.getAttribute('accept')})
|
|
61
63
|
if (this.firstChild)
|
|
62
|
-
console.warn('<
|
|
64
|
+
console.warn('<sync-area> ignoring existing child content')
|
|
63
65
|
while (this.firstChild) this.removeChild(this.firstChild)
|
|
64
66
|
|
|
65
67
|
// Create the inner textarea, and fill the outer element with it.
|
|
@@ -80,7 +82,7 @@ class SyncedTextarea extends HTMLElement {
|
|
|
80
82
|
this.update_disabled()
|
|
81
83
|
|
|
82
84
|
// Re-dispatch the inner textarea's focus/blur events on the outer,
|
|
83
|
-
// so listeners on <
|
|
85
|
+
// so listeners on <sync-area> see them.
|
|
84
86
|
textarea.addEventListener('focus', () =>
|
|
85
87
|
this.dispatchEvent(new FocusEvent('focus')))
|
|
86
88
|
textarea.addEventListener('blur', () =>
|
|
@@ -109,6 +111,7 @@ class SyncedTextarea extends HTMLElement {
|
|
|
109
111
|
// The main sync client.
|
|
110
112
|
this.client = simpleton_client(url, {
|
|
111
113
|
headers,
|
|
114
|
+
content_type: this.getAttribute('accept') || undefined,
|
|
112
115
|
on_online: (online) => { online ? cursors?.online() : cursors?.offline() },
|
|
113
116
|
on_update: (update) => {
|
|
114
117
|
this.waiting_for_first_update = false
|
|
@@ -218,4 +221,4 @@ class SyncedTextarea extends HTMLElement {
|
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
|
|
221
|
-
customElements.define('
|
|
224
|
+
customElements.define('sync-area', SyncArea)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braid-text",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "Library for collaborative text over http using braid.",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braid-text",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@braid.org/diamond-types-node": "^2.0.1",
|
|
30
|
-
"braid-http": "~1.3.
|
|
30
|
+
"braid-http": "~1.3.125"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"yjs": "^13.6.0"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"yjs": "^13.6.0"
|
package/server.js
CHANGED
|
@@ -329,10 +329,16 @@ function create_braid_text() {
|
|
|
329
329
|
braid_text.serve = async (req, res, options = {}) => {
|
|
330
330
|
options = {
|
|
331
331
|
key: req.url.split('?')[0],
|
|
332
|
+
content_type: 'text/plain',
|
|
332
333
|
put_cb: (key, val, params) => { },
|
|
333
334
|
...options
|
|
334
335
|
}
|
|
335
336
|
|
|
337
|
+
// Ensure charset=utf-8 on the content type
|
|
338
|
+
var content_type = options.content_type
|
|
339
|
+
if (!content_type.includes('charset='))
|
|
340
|
+
content_type += '; charset=utf-8'
|
|
341
|
+
|
|
336
342
|
// ── Setup: prepare the response and load the resource ──
|
|
337
343
|
|
|
338
344
|
if (braid_text.cors !== false) braid_text.free_cors(res)
|
|
@@ -370,19 +376,6 @@ function create_braid_text() {
|
|
|
370
376
|
is_write = req.method === 'PUT' || req.method === 'POST' || req.method === 'PATCH',
|
|
371
377
|
is_head = req.method === 'HEAD'
|
|
372
378
|
|
|
373
|
-
// ── Ensure the response is labeled as utf-8 text ──
|
|
374
|
-
|
|
375
|
-
if (!res.getHeader('content-type')) res.setHeader('Content-Type', 'text/plain')
|
|
376
|
-
var ct = res.getHeader('Content-Type'),
|
|
377
|
-
ct_parts = ct.split(';').map(p => p.trim())
|
|
378
|
-
var charset = ct_parts.find(p => p.toLowerCase().startsWith('charset='))
|
|
379
|
-
if (!charset)
|
|
380
|
-
res.setHeader('Content-Type', `${ct}; charset=utf-8`)
|
|
381
|
-
else if (charset.toLowerCase() !== 'charset=utf-8')
|
|
382
|
-
res.setHeader('Content-Type', ct_parts
|
|
383
|
-
.map(p => p.toLowerCase().startsWith('charset=') ? 'charset=utf-8' : p)
|
|
384
|
-
.join('; '))
|
|
385
|
-
|
|
386
379
|
// ── Handle simple methods that don't need further processing ──
|
|
387
380
|
|
|
388
381
|
if (req.method === 'OPTIONS') return my_end(200)
|
|
@@ -408,8 +401,8 @@ function create_braid_text() {
|
|
|
408
401
|
Version: ascii_ify(unknowns.map(e => JSON.stringify(e)).join(', '))
|
|
409
402
|
})
|
|
410
403
|
|
|
411
|
-
var has_parents =
|
|
412
|
-
var has_version =
|
|
404
|
+
var has_parents = Array.isArray(req.parents)
|
|
405
|
+
var has_version = Array.isArray(req.version)
|
|
413
406
|
|
|
414
407
|
if (req.subscribe && has_version)
|
|
415
408
|
return my_end(400, 'Version header is not allowed with Subscribe — use Parents instead')
|
|
@@ -442,7 +435,7 @@ function create_braid_text() {
|
|
|
442
435
|
|
|
443
436
|
// HEAD: headers only, no body needed
|
|
444
437
|
if (is_head) {
|
|
445
|
-
|
|
438
|
+
res.setHeader('Content-Type', content_type)
|
|
446
439
|
if (!getting.history)
|
|
447
440
|
res.setHeader('Version', current_version())
|
|
448
441
|
return my_end(200)
|
|
@@ -469,13 +462,25 @@ function create_braid_text() {
|
|
|
469
462
|
// Range of history: send as 209 Multiresponse
|
|
470
463
|
res.startSubscription()
|
|
471
464
|
for (var u of result)
|
|
472
|
-
res.
|
|
465
|
+
res.sendUpdate({
|
|
473
466
|
version: [u.version],
|
|
474
467
|
parents: u.parents,
|
|
475
468
|
patches: [{ unit: u.unit, range: u.range, content: u.content }],
|
|
469
|
+
content_type,
|
|
476
470
|
})
|
|
477
471
|
return res.end()
|
|
472
|
+
} else if (false) {
|
|
473
|
+
// New path: use sendUpdate (currently broken, investigating)
|
|
474
|
+
res.sendUpdate({
|
|
475
|
+
version: result.version,
|
|
476
|
+
'Repr-Digest': get_digest(result.body),
|
|
477
|
+
content_type,
|
|
478
|
+
body: result.body,
|
|
479
|
+
})
|
|
480
|
+
return res.end()
|
|
478
481
|
} else {
|
|
482
|
+
// Old path: explicit headers
|
|
483
|
+
res.setHeader('Content-Type', content_type)
|
|
479
484
|
res.setHeader('Version', ascii_ify(result.version
|
|
480
485
|
.map(v => JSON.stringify(v))
|
|
481
486
|
.join(', ')))
|
|
@@ -514,7 +519,9 @@ function create_braid_text() {
|
|
|
514
519
|
update.patch = update.patches[0]
|
|
515
520
|
delete update.patches
|
|
516
521
|
}
|
|
517
|
-
|
|
522
|
+
if (!update.encoding)
|
|
523
|
+
update.content_type = content_type
|
|
524
|
+
res.sendUpdate(update)
|
|
518
525
|
},
|
|
519
526
|
})
|
|
520
527
|
// Ensure headers are sent even if .get() didn't send
|
|
@@ -685,8 +692,8 @@ function create_braid_text() {
|
|
|
685
692
|
var version = resource.version
|
|
686
693
|
var merge_type = options.range_unit === 'yjs-text' ? 'yjs'
|
|
687
694
|
: (options.merge_type || 'simpleton')
|
|
688
|
-
var has_parents =
|
|
689
|
-
var has_version =
|
|
695
|
+
var has_parents = Array.isArray(options.parents)
|
|
696
|
+
var has_version = Array.isArray(options.version)
|
|
690
697
|
|
|
691
698
|
if (options.subscribe && has_version)
|
|
692
699
|
throw new Error('version is not allowed with subscribe — use parents instead')
|
|
@@ -704,21 +711,9 @@ function create_braid_text() {
|
|
|
704
711
|
}
|
|
705
712
|
getting.single_snapshot = !getting.subscribe && !getting.history
|
|
706
713
|
|
|
707
|
-
// Single snapshot: return the text (optionally at a specific version)
|
|
708
|
-
if (getting.single_snapshot) {
|
|
709
|
-
if (has_version) {
|
|
710
|
-
await ensure_dt_exists(resource)
|
|
711
|
-
return options.full_response
|
|
712
|
-
? { version: options.version, body: dt_get_string(resource.dt.doc, options.version) }
|
|
713
|
-
: dt_get_string(resource.dt.doc, options.version)
|
|
714
|
-
}
|
|
715
|
-
return options.full_response ? { version, body: resource.val } : resource.val
|
|
716
|
-
}
|
|
717
|
-
|
|
718
714
|
// DT binary encoding: a transport optimization usable by any merge type.
|
|
719
715
|
// Returns raw DT bytes instead of text.
|
|
720
|
-
if (
|
|
721
|
-
// TODO: move this into the dt/simpleton merge_type cases below
|
|
716
|
+
if (!getting.subscribe && getting.transfer_encoding === 'dt') {
|
|
722
717
|
await ensure_dt_exists(resource)
|
|
723
718
|
// If requesting the current version, skip the version lookup
|
|
724
719
|
// (faster than asking DT about a version we already have)
|
|
@@ -742,6 +737,17 @@ function create_braid_text() {
|
|
|
742
737
|
return { body: bytes }
|
|
743
738
|
}
|
|
744
739
|
|
|
740
|
+
// Single snapshot: return the text (optionally at a specific version)
|
|
741
|
+
if (getting.single_snapshot) {
|
|
742
|
+
if (has_version) {
|
|
743
|
+
await ensure_dt_exists(resource)
|
|
744
|
+
return options.full_response
|
|
745
|
+
? { version: options.version, body: dt_get_string(resource.dt.doc, options.version) }
|
|
746
|
+
: dt_get_string(resource.dt.doc, options.version)
|
|
747
|
+
}
|
|
748
|
+
return options.full_response ? { version, body: resource.val } : resource.val
|
|
749
|
+
}
|
|
750
|
+
|
|
745
751
|
// Each merge-type has a different way of getting history
|
|
746
752
|
switch (merge_type) {
|
|
747
753
|
|