braid-text 0.0.2 → 0.0.5
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/README.md +91 -44
- package/editor.html +1 -1
- package/index.js +36 -1
- package/markdown-editor.html +1 -1
- package/package.json +1 -1
- package/server-demo.js +2 -6
- package/simpleton-client.js +4 -4
- package/braid-text-db/%2F.1 +0 -0
- package/braid-text-db/%2Ffavicon.ico.1 +0 -0
package/README.md
CHANGED
|
@@ -12,7 +12,25 @@ This library provides a simple http route handler, along with client code, enabl
|
|
|
12
12
|
- Fast / Robust / Extensively fuzz-tested
|
|
13
13
|
- Developed in [braid.org](https://braid.org)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### Demo: a Wiki!
|
|
16
|
+
|
|
17
|
+
This will run a collaboratively-editable wiki:
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
npm install
|
|
21
|
+
node server-demo.js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Now open these URLs in your browser:
|
|
25
|
+
- http://localhost:8888/demo (to see the demo text)
|
|
26
|
+
- http://localhost:8888/demo?editor (to edit the text)
|
|
27
|
+
- http://localhost:8888/demo?markdown-editor (to edit it as markdown)
|
|
28
|
+
|
|
29
|
+
Or try opening the URL in [Braid-Chrome](https://github.com/braid-org/braid-chrome), or another Braid client, to edit it directly!
|
|
30
|
+
|
|
31
|
+
Check out the `server-demo.js` file to see examples for how to add access control, and a `/pages` endpoint to show all the edited pages.
|
|
32
|
+
|
|
33
|
+
## General Use on Server
|
|
16
34
|
|
|
17
35
|
Install it in your project:
|
|
18
36
|
```shell
|
|
@@ -32,25 +50,7 @@ server.on("request", (req, res) => {
|
|
|
32
50
|
})
|
|
33
51
|
```
|
|
34
52
|
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
This will run a collaboratively-editable wiki:
|
|
38
|
-
|
|
39
|
-
```shell
|
|
40
|
-
npm install
|
|
41
|
-
node server-demo.js
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Now open these URLs in your browser:
|
|
45
|
-
- http://localhost:8888/demo (to see the demo text)
|
|
46
|
-
- http://localhost:8888/demo?editor (to edit the text)
|
|
47
|
-
- http://localhost:8888/demo?markdown-editor (to edit it as markdown)
|
|
48
|
-
|
|
49
|
-
Or try opening the URL in [Braid-Chrome](https://github.com/braid-org/braid-chrome), or another Braid client, to edit it directly!
|
|
50
|
-
|
|
51
|
-
Check out the `server-demo.js` file to see examples for how to add access control, and a `/pages` endpoint to show all the edited pages.
|
|
52
|
-
|
|
53
|
-
## Full Server Library API
|
|
53
|
+
## Server API
|
|
54
54
|
|
|
55
55
|
`braid_text.db_folder = './braid-text-db' // <-- this is the default`
|
|
56
56
|
- This is where the Diamond-Types history files will be stored for each resource.
|
|
@@ -88,35 +88,82 @@ Check out the `server-demo.js` file to see examples for how to add access contro
|
|
|
88
88
|
- `patches`: <small style="color:lightgrey">[optional]</small> Array of patches, each of the form `{unit: 'text', range: '[1:3]', content: 'hi'}`, which would replace the second and third unicode code-points in the text with `hi`.
|
|
89
89
|
- `peer`: <small style="color:lightgrey">[optional]</small> Identifies this peer. This mutation will not be echoed back to `get` subscriptions that use this same `peer` header.
|
|
90
90
|
|
|
91
|
-
## Use
|
|
91
|
+
## General Use on Client
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
```html
|
|
94
|
+
<script src="https://unpkg.com/braid-text/simpleton-client.js"></script>
|
|
95
|
+
<script>
|
|
96
|
+
|
|
97
|
+
// connect to the server
|
|
98
|
+
let simpleton = simpleton_client('https://example.org/some-resource', {
|
|
99
|
+
apply_remote_update: ({ state, patches }) => {
|
|
100
|
+
|
|
101
|
+
// Apply the incoming state or patches to local text here.
|
|
102
|
+
|
|
103
|
+
// Then return the new state of textarea as a string:
|
|
104
|
+
return new_state
|
|
105
|
+
},
|
|
106
|
+
generate_local_diff_update: (prev_state) => {
|
|
107
|
+
|
|
108
|
+
// Compute diff between prev_state ^ and the current textarea string,
|
|
109
|
+
// which might look like [{
|
|
110
|
+
// range: [5:5],
|
|
111
|
+
// content: " World"
|
|
112
|
+
// }] to insert something after a prev_state of "Hello"
|
|
113
|
+
|
|
114
|
+
// Then return the new state (as a string) and the diff (as `patches`)
|
|
115
|
+
return {new_state, patches}
|
|
116
|
+
},
|
|
117
|
+
})
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
...
|
|
107
120
|
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
// When changes occur in client's textarea, let simpleton know,
|
|
122
|
+
// so that it can call generate_local_diff_update() to ask for them.
|
|
123
|
+
simpleton.changed()
|
|
124
|
+
|
|
125
|
+
</script>
|
|
126
|
+
```
|
|
110
127
|
|
|
111
128
|
See [editor.html](https://raw.githubusercontent.com/braid-org/braid-text/master/editor.html) for a simple working example.
|
|
112
129
|
|
|
113
|
-
##
|
|
130
|
+
## Client API
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
simpleton = simpleton_client(url, options)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `url`: The URL of the resource to synchronize with.
|
|
137
|
+
- `options`: An object containing the following properties:
|
|
138
|
+
- `apply_remote_update`: A function that will be called whenever an update is received from the server. It should have the following signature:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
({state, patches}) => {...}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `state`: If present, represents the new value of the text.
|
|
145
|
+
- `patches`: If present, an array of patch objects, each representing a string-replace operation. Each patch object has the following properties:
|
|
146
|
+
- `range`: An array of two numbers, `[start, end]`, specifying the start and end positions of the characters to be deleted.
|
|
147
|
+
- `content`: The text to be inserted in place of the deleted characters.
|
|
148
|
+
|
|
149
|
+
Note that patches will always be in order, but the range positions of each patch reference the original string, i.e., the second patch's range values do not take into account the application of the first patch.
|
|
150
|
+
|
|
151
|
+
The function should apply the `state` or `patches` to the local text and return the new state.
|
|
152
|
+
|
|
153
|
+
- `generate_local_diff_update`: A function that will be called whenever a local update occurs, but may be delayed if the network is congested. It should have the following signature:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
(prev_state) => {...}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The function should calculate the difference between `prev_state` and the current state, and express this difference as an array of patches (similar to the ones described in `apply_remote_update`).
|
|
160
|
+
|
|
161
|
+
If a difference is detected, the function should return an object with the following properties:
|
|
162
|
+
- `new_state`: The current state of the text.
|
|
163
|
+
- `patches`: An array of patch objects representing the changes.
|
|
164
|
+
|
|
165
|
+
If no difference is detected, the function should return `undefined` or `null`.
|
|
114
166
|
|
|
115
|
-
|
|
116
|
-
apply_remote_update,
|
|
117
|
-
generate_local_diff_update}
|
|
167
|
+
- `content_type`: <small style="color:lightgrey">[optional]</small> If set, this value will be sent in the `Accept` and `Content-Type` headers to the server.
|
|
118
168
|
|
|
119
|
-
|
|
120
|
-
- `apply_remote_update`: This function will be called whenever an update is received from the server. The function should look like `({state, patches}) => {...}`. Only one of `state` or `patches` will be set. If it is `state`, then this is the new value of the text. If it is `patches`, then patches is an array of values like `{range: [1, 3], content: "Hi"}`. Each such value represents a string-replace operation; the `range` specifies a start and end position — these characters will be deleted — and `content` says what text to put in its place. Note that these patches will always be in order, but that the range positions of each patch always reference the original string, e.g., the second patch's range values do not take into account applying the first patch. Finally, this function returns the new state, after the application of the `state` or `patches`.
|
|
121
|
-
- `generate_local_diff_update`: This function will often be called whenever an update happens locally, but the system may delay calling it if the network is congested. The function should look like `(prev_state) => {...}`. The function should basically do a diff between `prev_state` and the current state, and express this diff as an array of patches similar to the ones discussed above. Finally, if there is an actual difference detected, this function should return an object `{state, patches}`, otherwise it should return nothing.
|
|
122
|
-
- `simpleton.changed()`: Call this to report local updates whenever they occur, e.g., in the `oninput` handler of a textarea being synchronized.
|
|
169
|
+
- `simpleton.changed()`: Call this function to report local updates whenever they occur, e.g., in the `oninput` event handler of a textarea being synchronized.
|
package/editor.html
CHANGED
package/index.js
CHANGED
|
@@ -483,6 +483,15 @@ braid_text.put = async (key, options) => {
|
|
|
483
483
|
await resource.db_delta(resource.doc.getPatchSince(v_before))
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
braid_text.list = async () => {
|
|
487
|
+
var pages = new Set()
|
|
488
|
+
for (let x of await require('fs').promises.readdir(braid_text.db_folder)) {
|
|
489
|
+
let m = x.match(/^(.*)\.\d+$/)
|
|
490
|
+
if (m) pages.add(decode_filename(m[1]))
|
|
491
|
+
}
|
|
492
|
+
return [...pages.keys()]
|
|
493
|
+
}
|
|
494
|
+
|
|
486
495
|
async function get_resource(key) {
|
|
487
496
|
let cache = get_resource.cache || (get_resource.cache = {})
|
|
488
497
|
if (cache[key]) return cache[key]
|
|
@@ -496,7 +505,7 @@ async function get_resource(key) {
|
|
|
496
505
|
let { change, delete_me } = braid_text.db_folder
|
|
497
506
|
? await file_sync(
|
|
498
507
|
braid_text.db_folder,
|
|
499
|
-
|
|
508
|
+
encode_filename(key),
|
|
500
509
|
(bytes) => resource.doc.mergeBytes(bytes),
|
|
501
510
|
() => resource.doc.toBytes()
|
|
502
511
|
)
|
|
@@ -1260,4 +1269,30 @@ function codePoints_to_index(str, codePoints) {
|
|
|
1260
1269
|
return i
|
|
1261
1270
|
}
|
|
1262
1271
|
|
|
1272
|
+
function encode_filename(filename) {
|
|
1273
|
+
// Replace all '!' with '%21'
|
|
1274
|
+
let encoded = filename.replace(/!/g, '%21');
|
|
1275
|
+
|
|
1276
|
+
// Encode the filename using encodeURIComponent()
|
|
1277
|
+
encoded = encodeURIComponent(encoded);
|
|
1278
|
+
|
|
1279
|
+
// Replace all '%2F' (for '/') with '!'
|
|
1280
|
+
encoded = encoded.replace(/%2F/g, '!');
|
|
1281
|
+
|
|
1282
|
+
return encoded;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function decode_filename(encodedFilename) {
|
|
1286
|
+
// Replace all '!' with '%2F'
|
|
1287
|
+
let decoded = encodedFilename.replace(/!/g, '%2F');
|
|
1288
|
+
|
|
1289
|
+
// Decode the filename using decodeURIComponent()
|
|
1290
|
+
decoded = decodeURIComponent(decoded);
|
|
1291
|
+
|
|
1292
|
+
// Replace all '%21' with '!'
|
|
1293
|
+
decoded = decoded.replace(/%21/g, '!');
|
|
1294
|
+
|
|
1295
|
+
return decoded;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1263
1298
|
module.exports = braid_text
|
package/markdown-editor.html
CHANGED
package/package.json
CHANGED
package/server-demo.js
CHANGED
|
@@ -27,11 +27,7 @@ var server = require("http").createServer(async (req, res) => {
|
|
|
27
27
|
// which displays all the currently used keys
|
|
28
28
|
//
|
|
29
29
|
// if (req.url === '/pages') {
|
|
30
|
-
// var pages =
|
|
31
|
-
// for (let x of await require('fs').promises.readdir(db_folder)) {
|
|
32
|
-
// let m = x.match(/^(.*)\.\d+$/)
|
|
33
|
-
// if (m) pages.add(decodeURIComponent(m[1]))
|
|
34
|
-
// }
|
|
30
|
+
// var pages = await braid_text.list()
|
|
35
31
|
// res.writeHead(200, {
|
|
36
32
|
// "Content-Type": "application/json",
|
|
37
33
|
// "Access-Control-Allow-Origin": "*",
|
|
@@ -39,7 +35,7 @@ var server = require("http").createServer(async (req, res) => {
|
|
|
39
35
|
// "Access-Control-Allow-Headers": "*",
|
|
40
36
|
// "Access-Control-Expose-Headers": "*"
|
|
41
37
|
// })
|
|
42
|
-
// res.end(JSON.stringify(
|
|
38
|
+
// res.end(JSON.stringify(pages))
|
|
43
39
|
// return
|
|
44
40
|
// }
|
|
45
41
|
|
package/simpleton-client.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
//
|
|
10
10
|
// generate_local_diff_update: (prev_state) => {...}
|
|
11
11
|
// this is to generate outgoing changes,
|
|
12
|
-
// and if there are changes, returns { patches,
|
|
12
|
+
// and if there are changes, returns { patches, new_state }
|
|
13
13
|
//
|
|
14
|
-
// content_type:
|
|
14
|
+
// content_type: used for Accept and Content-Type headers
|
|
15
15
|
//
|
|
16
16
|
// returns { changed(): (diff_function) => {...} }
|
|
17
17
|
// this is for outgoing changes;
|
|
@@ -74,7 +74,7 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
|
|
|
74
74
|
while (true) {
|
|
75
75
|
var update = generate_local_diff_update(prev_state)
|
|
76
76
|
if (!update) return // Stop if there wasn't a change!
|
|
77
|
-
var {patches,
|
|
77
|
+
var {patches, new_state} = update
|
|
78
78
|
|
|
79
79
|
// convert from js-indicies to code-points
|
|
80
80
|
let c = 0
|
|
@@ -103,7 +103,7 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
|
|
|
103
103
|
|
|
104
104
|
var parents = current_version
|
|
105
105
|
current_version = version
|
|
106
|
-
prev_state =
|
|
106
|
+
prev_state = new_state
|
|
107
107
|
|
|
108
108
|
outstanding_changes++
|
|
109
109
|
await braid_fetch_wrapper(url, {
|
package/braid-text-db/%2F.1
DELETED
|
Binary file
|
|
Binary file
|