braid-text 0.5.4 → 0.5.6
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/package.json +1 -1
- package/server-demo.js +3 -2
- package/server.js +74 -64
package/package.json
CHANGED
package/server-demo.js
CHANGED
|
@@ -14,14 +14,15 @@ var server = require("http").createServer(async (req, res) => {
|
|
|
14
14
|
if (req.method === 'OPTIONS') return res.end()
|
|
15
15
|
|
|
16
16
|
var q = req.url.split('?').slice(-1)[0]
|
|
17
|
-
if (q === 'editor' || q === 'markdown-editor') {
|
|
17
|
+
if (q === 'editor' || q === 'markdown-editor' || q === 'yjs-editor' || q === 'demo') {
|
|
18
18
|
res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" })
|
|
19
19
|
require("fs").createReadStream(`./client/${q}.html`).pipe(res)
|
|
20
20
|
return
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (req.url === '/simpleton-sync.js' || req.url === '/web-utils.js'
|
|
24
|
-
|| req.url === '/textarea-highlights.js' || req.url === '/cursor-sync.js'
|
|
24
|
+
|| req.url === '/textarea-highlights.js' || req.url === '/cursor-sync.js'
|
|
25
|
+
|| req.url === '/yjs-sync.js') {
|
|
25
26
|
res.writeHead(200, { "Content-Type": "text/javascript", "Cache-Control": "no-cache" })
|
|
26
27
|
require("fs").createReadStream("./client" + req.url).pipe(res)
|
|
27
28
|
return
|
package/server.js
CHANGED
|
@@ -353,8 +353,7 @@ function create_braid_text() {
|
|
|
353
353
|
|
|
354
354
|
var peer = req.headers['peer'],
|
|
355
355
|
merge_type = req.headers['merge-type'] || 'simpleton'
|
|
356
|
-
|
|
357
|
-
if (merge_type !== 'simpleton' && merge_type !== 'dt')
|
|
356
|
+
if (merge_type !== 'simpleton' && merge_type !== 'dt' && merge_type !== 'yjs')
|
|
358
357
|
return my_end(400, `Unknown merge type: ${merge_type}`)
|
|
359
358
|
|
|
360
359
|
var is_read = req.method === 'GET' || req.method === 'HEAD',
|
|
@@ -744,16 +743,14 @@ function create_braid_text() {
|
|
|
744
743
|
if (getting.history === 'since-parents')
|
|
745
744
|
throw new Error('yjs-text from arbitrary parents not yet implemented')
|
|
746
745
|
|
|
747
|
-
var
|
|
746
|
+
var yjs_updates = braid_text.from_yjs_binary(
|
|
748
747
|
Y.encodeStateAsUpdate(resource.yjs.doc))
|
|
749
748
|
|
|
750
749
|
if (!getting.subscribe)
|
|
751
|
-
return
|
|
750
|
+
return yjs_updates
|
|
752
751
|
|
|
753
|
-
|
|
754
|
-
options.subscribe(
|
|
755
|
-
version: resource.version, parents: [], patches
|
|
756
|
-
})
|
|
752
|
+
for (var u of yjs_updates)
|
|
753
|
+
options.subscribe(u)
|
|
757
754
|
}
|
|
758
755
|
|
|
759
756
|
if (getting.subscribe) {
|
|
@@ -911,7 +908,10 @@ function create_braid_text() {
|
|
|
911
908
|
? options.yjs_update : new Uint8Array(options.yjs_update)
|
|
912
909
|
} else if (patches && patches.length && patches[0].unit === 'yjs-text') {
|
|
913
910
|
yjs_text_patches = patches
|
|
914
|
-
yjs_binary = braid_text.to_yjs_binary(
|
|
911
|
+
yjs_binary = braid_text.to_yjs_binary([{
|
|
912
|
+
version: options.version?.[0],
|
|
913
|
+
patches
|
|
914
|
+
}])
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
if (yjs_binary) {
|
|
@@ -986,18 +986,15 @@ function create_braid_text() {
|
|
|
986
986
|
|
|
987
987
|
// Broadcast to yjs-text subscribers (skip sender)
|
|
988
988
|
if (resource.yjs) {
|
|
989
|
-
// If we received yjs-text
|
|
989
|
+
// If we received yjs-text updates, reuse them; otherwise
|
|
990
990
|
// derive them from the binary update
|
|
991
|
-
var
|
|
992
|
-
|
|
993
|
-
|
|
991
|
+
var yjs_updates = yjs_text_patches
|
|
992
|
+
? [{version: options.version, patches: yjs_text_patches}]
|
|
993
|
+
: braid_text.from_yjs_binary(yjs_binary)
|
|
994
|
+
for (var yjs_update of yjs_updates) {
|
|
994
995
|
for (var client of resource.yjs.clients) {
|
|
995
|
-
if (!peer || client.peer !== peer)
|
|
996
|
-
await client.send_update(
|
|
997
|
-
version: resource.version,
|
|
998
|
-
patches: yjs_patches
|
|
999
|
-
})
|
|
1000
|
-
}
|
|
996
|
+
if (!peer || client.peer !== peer)
|
|
997
|
+
await client.send_update(yjs_update)
|
|
1001
998
|
}
|
|
1002
999
|
}
|
|
1003
1000
|
}
|
|
@@ -1233,15 +1230,12 @@ function create_braid_text() {
|
|
|
1233
1230
|
// and we are mixing them. The update-level .version is the DT frontier.
|
|
1234
1231
|
// Each patch's .version is a Yjs item ID (clientID-clock).
|
|
1235
1232
|
if (resource.yjs && captured_yjs_update) {
|
|
1236
|
-
var
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
})
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1233
|
+
var yjs_updates = braid_text.from_yjs_binary(captured_yjs_update)
|
|
1234
|
+
if (braid_text.verbose) console.log('DT→Yjs broadcast:', yjs_updates.length, 'updates to', resource.yjs.clients.size, 'yjs clients')
|
|
1235
|
+
for (var yjs_update of yjs_updates)
|
|
1236
|
+
for (var client of resource.yjs.clients)
|
|
1237
|
+
if (!peer || client.peer !== peer)
|
|
1238
|
+
await client.send_update(yjs_update)
|
|
1245
1239
|
}
|
|
1246
1240
|
|
|
1247
1241
|
// Persist Yjs delta
|
|
@@ -3072,48 +3066,60 @@ function create_braid_text() {
|
|
|
3072
3066
|
// Convert a Yjs binary update to yjs-text range patches.
|
|
3073
3067
|
// Decodes the binary without needing a Y.Doc.
|
|
3074
3068
|
// Returns array of {unit: 'yjs-text', range: '...', content: '...'}
|
|
3069
|
+
// Convert a Yjs binary update to an array of braid updates,
|
|
3070
|
+
// each with a version and patches in yjs-text format.
|
|
3075
3071
|
braid_text.from_yjs_binary = function(update) {
|
|
3076
3072
|
require_yjs()
|
|
3077
3073
|
var decoded = Y.decodeUpdate(
|
|
3078
3074
|
update instanceof Uint8Array ? update : new Uint8Array(update))
|
|
3079
|
-
var
|
|
3075
|
+
var updates = []
|
|
3080
3076
|
|
|
3081
|
-
//
|
|
3077
|
+
// Each inserted struct becomes one update with one insert patch.
|
|
3078
|
+
// GC'd structs (deleted items) have content.len but no content.str —
|
|
3079
|
+
// we emit placeholder text since the delete set will remove it anyway.
|
|
3082
3080
|
for (var struct of decoded.structs) {
|
|
3083
|
-
|
|
3081
|
+
var text = struct.content?.str
|
|
3082
|
+
if (!text && struct.content?.len) text = '_'.repeat(struct.content.len)
|
|
3083
|
+
if (!text) continue // skip non-text items (e.g. format, embed)
|
|
3084
3084
|
var id = struct.id
|
|
3085
3085
|
var origin = struct.origin
|
|
3086
3086
|
var rightOrigin = struct.rightOrigin
|
|
3087
3087
|
var left = origin ? `${origin.client}-${origin.clock}` : ''
|
|
3088
3088
|
var right = rightOrigin ? `${rightOrigin.client}-${rightOrigin.clock}` : ''
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3089
|
+
updates.push({
|
|
3090
|
+
version: [`${id.client}-${id.clock}`],
|
|
3091
|
+
patches: [{
|
|
3092
|
+
unit: 'yjs-text',
|
|
3093
|
+
range: `(${left}:${right})`,
|
|
3094
|
+
content: text,
|
|
3095
|
+
}]
|
|
3094
3096
|
})
|
|
3095
3097
|
}
|
|
3096
3098
|
|
|
3097
|
-
//
|
|
3099
|
+
// Each delete range becomes one update with one delete patch
|
|
3098
3100
|
for (var [clientID, deleteItems] of decoded.ds.clients) {
|
|
3099
3101
|
for (var item of deleteItems) {
|
|
3100
3102
|
var left = `${clientID}-${item.clock}`
|
|
3101
3103
|
var right = `${clientID}-${item.clock + item.len - 1}`
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3104
|
+
updates.push({
|
|
3105
|
+
version: [`${clientID}-${item.clock}`],
|
|
3106
|
+
patches: [{
|
|
3107
|
+
unit: 'yjs-text',
|
|
3108
|
+
range: `[${left}:${right}]`,
|
|
3109
|
+
content: ''
|
|
3110
|
+
}]
|
|
3106
3111
|
})
|
|
3107
3112
|
}
|
|
3108
3113
|
}
|
|
3109
3114
|
|
|
3110
|
-
return
|
|
3115
|
+
return updates
|
|
3111
3116
|
}
|
|
3112
3117
|
|
|
3113
3118
|
// Convert yjs-text range patches to a Yjs binary update.
|
|
3119
|
+
// Convert braid updates with yjs-text patches to a Yjs binary update.
|
|
3114
3120
|
// This is the inverse of from_yjs_binary.
|
|
3115
|
-
//
|
|
3116
|
-
braid_text.to_yjs_binary = function(
|
|
3121
|
+
// Accepts an array of updates, each with {version, patches}.
|
|
3122
|
+
braid_text.to_yjs_binary = function(updates) {
|
|
3117
3123
|
require_yjs()
|
|
3118
3124
|
var lib0_encoding = require('lib0/encoding')
|
|
3119
3125
|
var encoder = new Y.UpdateEncoderV1()
|
|
@@ -3122,26 +3128,30 @@ function create_braid_text() {
|
|
|
3122
3128
|
var inserts_by_client = new Map()
|
|
3123
3129
|
var deletes_by_client = new Map()
|
|
3124
3130
|
|
|
3125
|
-
for (var
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
if (
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3131
|
+
for (var update of updates) {
|
|
3132
|
+
if (!update.patches) continue
|
|
3133
|
+
for (var p of update.patches) {
|
|
3134
|
+
var parsed = parse_yjs_range(p.range)
|
|
3135
|
+
if (!parsed) throw new Error(`invalid yjs-text range: ${p.range}`)
|
|
3136
|
+
|
|
3137
|
+
if (p.content.length > 0) {
|
|
3138
|
+
// Insert — version on the update is the item ID as ["client-clock"]
|
|
3139
|
+
var v_str = Array.isArray(update.version) ? update.version[0] : update.version
|
|
3140
|
+
if (!v_str) throw new Error('insert update requires .version = ["client-clock"]')
|
|
3141
|
+
var v_parts = v_str.match(/^(\d+)-(\d+)$/)
|
|
3142
|
+
if (!v_parts) throw new Error('invalid update version: ' + v_str)
|
|
3143
|
+
var item_id = { client: parseInt(v_parts[1]), clock: parseInt(v_parts[2]) }
|
|
3144
|
+
var list = inserts_by_client.get(item_id.client) || []
|
|
3145
|
+
list.push({ id: item_id, origin: parsed.left, rightOrigin: parsed.right, content: p.content })
|
|
3146
|
+
inserts_by_client.set(item_id.client, list)
|
|
3147
|
+
} else {
|
|
3148
|
+
// Delete
|
|
3149
|
+
if (!parsed.left) throw new Error('delete patch requires left ID')
|
|
3150
|
+
var client = parsed.left.client
|
|
3151
|
+
var list = deletes_by_client.get(client) || []
|
|
3152
|
+
list.push({ clock: parsed.left.clock, len: parsed.right ? parsed.right.clock - parsed.left.clock + 1 : 1 })
|
|
3153
|
+
deletes_by_client.set(client, list)
|
|
3154
|
+
}
|
|
3145
3155
|
}
|
|
3146
3156
|
}
|
|
3147
3157
|
|