braid-text 0.2.47 → 0.2.48

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 CHANGED
@@ -148,38 +148,59 @@ simpleton = simpleton_client(url, options)
148
148
 
149
149
  - `url`: The URL of the resource to synchronize with.
150
150
  - `options`: An object containing the following properties:
151
- - `apply_remote_update`: A function that will be called whenever an update is received from the server. It should have the following signature:
151
+
152
+ ### Incoming Updates
153
+
154
+ - `on_patches`: <small style="color:lightgrey">[optional]</small> A function called when patches are received from the server:
152
155
 
153
156
  ```javascript
154
- ({state, patches}) => {...}
157
+ (patches) => {...}
155
158
  ```
156
159
 
157
- - `state`: If present, represents the new value of the text.
158
- - `patches`: If present, an array of patch objects, each representing a string-replace operation. Each patch object has the following properties:
160
+ - `patches`: An array of patch objects, each representing a string-replace operation. Each patch object has:
159
161
  - `range`: An array of two numbers, `[start, end]`, specifying the start and end positions of the characters to be deleted.
160
162
  - `content`: The text to be inserted in place of the deleted characters.
161
163
 
162
164
  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.
163
165
 
164
- The function should apply the `state` or `patches` to the local text and return the new state.
166
+ - `on_state`: <small style="color:lightgrey">[optional]</small> A function called when a complete state update is received from the server:
167
+
168
+ ```javascript
169
+ (state) => {...}
170
+ ```
171
+
172
+ - `state`: The new complete value of the text.
173
+
174
+ ### Local State Management
165
175
 
166
- - `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:
176
+ - `get_state`: **[required]** A function that returns the current state of the text:
167
177
 
168
178
  ```javascript
169
- (prev_state) => {...}
179
+ () => current_state
170
180
  ```
171
181
 
172
- 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`).
182
+ - `get_patches`: <small style="color:lightgrey">[optional]</small> A function that generates patches representing changes between a previous state and the current state:
173
183
 
174
- If a difference is detected, the function should return an object with the following properties:
175
- - `new_state`: The current state of the text.
176
- - `patches`: An array of patch objects representing the changes.
184
+ ```javascript
185
+ (prev_state) => patches
186
+ ```
177
187
 
178
- If no difference is detected, the function should return `undefined` or `null`.
188
+ Returns an array of patch objects representing the changes. The default implementation finds a common prefix and suffix for a simple diff, but you can provide a more sophisticated implementation or track patches directly from your editor.
189
+
190
+ ### Other Options
179
191
 
180
192
  - `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.
181
193
 
182
- - `simpleton.changed()`: Call this function to report local updates whenever they occur, e.g., in the `oninput` event handler of a textarea being synchronized.
194
+ ### Methods
195
+
196
+ - `simpleton.changed()`: Call this function to report local updates whenever they occur, e.g., in the `oninput` event handler of a textarea being synchronized. The system will call `get_patches` when it needs to send updates to the server.
197
+
198
+ ### Deprecated Options
199
+
200
+ The following options are deprecated and should be replaced with the new API:
201
+
202
+ - ~~`apply_remote_update`~~ → Use `on_patches` and `on_state` instead
203
+ - ~~`generate_local_diff_update`~~ → Use `get_patches` and `get_state` instead
183
204
 
184
205
  ## Testing
185
206
 
package/editor.html CHANGED
@@ -9,16 +9,9 @@
9
9
  <script src="/simpleton-client.js"></script>
10
10
  <script>
11
11
  let simpleton = simpleton_client(location.pathname, {
12
- apply_remote_update: ({ state, patches }) => {
13
- if (state !== undefined) texty.value = state;
14
- else apply_patches_and_update_selection(texty, patches);
15
- return texty.value;
16
- },
17
- generate_local_diff_update: (prev_state) => {
18
- var patches = diff(prev_state, texty.value);
19
- if (patches.length === 0) return null;
20
- return { patches, new_state: texty.value };
21
- },
12
+ on_patches: (patches) => apply_patches_and_update_selection(texty, patches),
13
+ get_patches: (prev_state) => diff(prev_state, texty.value),
14
+ get_state: () => texty.value,
22
15
  on_error: (e) => {
23
16
  texty.disabled = true
24
17
  texty.style.background = '#fee'
@@ -27,27 +27,14 @@ t = function() {
27
27
  };
28
28
 
29
29
  var simpleton = simpleton_client(location.pathname, {
30
- apply_remote_update: function(x) {
31
- if (x.state !== void 0) {
32
- t().value = x.state;
33
- } else {
34
- apply_patches_and_update_selection(t(), x.patches);
35
- }
30
+ on_patches: (patches) => {
31
+ apply_patches_and_update_selection(t(), patches);
36
32
  state.source = t().value;
37
33
  update_markdown_later();
38
- return t().value;
39
- },
40
- generate_local_diff_update: function(prev_state) {
41
- var patches;
42
- patches = diff(prev_state, t().value);
43
- if (patches.length === 0) {
44
- return null;
45
- }
46
- return {
47
- patches: patches,
48
- new_state: t().value
49
- };
50
34
  },
35
+ get_patches: (prev_state) => diff(prev_state, t().value),
36
+ get_state: () => t().value,
37
+
51
38
  on_error: (e) => {
52
39
  t().disabled = true
53
40
  t().style.background = '#fee'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
@@ -2,22 +2,51 @@
2
2
  //
3
3
  // url: resource endpoint
4
4
  //
5
- // apply_remote_update: ({patches, state}) => {...}
5
+ // on_patches?: (patches) => void
6
+ // processes incoming patches
7
+ //
8
+ // on_state?: (state) => void
9
+ // processes incoming state
10
+ //
11
+ // get_patches?: (prev_state) => patches
12
+ // returns patches representing diff
13
+ // between prev_state and current state,
14
+ // which are guaranteed to be different
15
+ // if this method is being called
16
+ // (the default does this in a fast/simple way,
17
+ // finding a common prefix and suffix,
18
+ // but you can supply something better,
19
+ // or possibly keep track of patches as they come from your editor)
20
+ //
21
+ // get_state: () => current_state
22
+ // returns the current state
23
+ //
24
+ // [DEPRECATED] apply_remote_update: ({patches, state}) => {...}
6
25
  // this is for incoming changes;
7
26
  // one of these will be non-null,
8
27
  // and can be applied to the current state.
9
28
  //
10
- // generate_local_diff_update: (prev_state) => {...}
29
+ // [DEPRECATED] generate_local_diff_update: (prev_state) => {...}
11
30
  // this is to generate outgoing changes,
12
31
  // and if there are changes, returns { patches, new_state }
13
32
  //
14
33
  // content_type: used for Accept and Content-Type headers
15
34
  //
16
- // returns { changed(): (diff_function) => {...} }
17
- // this is for outgoing changes;
18
- // diff_function = () => ({patches, new_version}).
35
+ // returns { changed }
36
+ // call changed whenever there is a local change,
37
+ // and the system will call get_patches when it needs to.
19
38
  //
20
- function simpleton_client(url, { apply_remote_update, generate_local_diff_update, content_type, on_error, on_res }) {
39
+ function simpleton_client(url, {
40
+ on_patches,
41
+ on_state,
42
+ get_patches,
43
+ get_state,
44
+ apply_remote_update, // DEPRECATED
45
+ generate_local_diff_update, // DEPRECATED
46
+ content_type,
47
+ on_error,
48
+ on_res
49
+ }) {
21
50
  var peer = Math.random().toString(36).substr(2)
22
51
  var current_version = []
23
52
  var prev_state = ""
@@ -69,7 +98,19 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
69
98
  }
70
99
  }
71
100
 
72
- prev_state = apply_remote_update(update)
101
+ if (apply_remote_update) {
102
+ // DEPRECATED
103
+ prev_state = apply_remote_update(update)
104
+ } else {
105
+ var patches = update.patches ||
106
+ [{range: [0, 0], content: update.state}]
107
+ if (on_patches) {
108
+ on_patches(patches)
109
+ prev_state = get_state()
110
+ } else prev_state = apply_patches(prev_state, patches)
111
+ }
112
+
113
+ if (on_state) on_state(prev_state)
73
114
  }
74
115
  }, on_error)
75
116
  }).catch(on_error)
@@ -81,9 +122,17 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
81
122
  changed: async () => {
82
123
  if (outstanding_changes >= max_outstanding_changes) return
83
124
  while (true) {
84
- var update = generate_local_diff_update(prev_state)
85
- if (!update) return // Stop if there wasn't a change!
86
- var {patches, new_state} = update
125
+ if (generate_local_diff_update) {
126
+ // DEPRECATED
127
+ var update = generate_local_diff_update(prev_state)
128
+ if (!update) return // Stop if there wasn't a change!
129
+ var {patches, new_state} = update
130
+ } else {
131
+ var new_state = get_state()
132
+ if (new_state === prev_state) return // Stop if there wasn't a change!
133
+ var patches = get_patches ? get_patches(prev_state) :
134
+ [simple_diff(prev_state, new_state)]
135
+ }
87
136
 
88
137
  // convert from js-indicies to code-points
89
138
  let c = 0
@@ -133,18 +182,42 @@ function simpleton_client(url, { apply_remote_update, generate_local_diff_update
133
182
  }
134
183
  }
135
184
  }
136
- }
137
185
 
138
- function get_char_size(s, i) {
139
- const charCode = s.charCodeAt(i)
140
- return (charCode >= 0xd800 && charCode <= 0xdbff) ? 2 : 1
141
- }
186
+ function get_char_size(s, i) {
187
+ const charCode = s.charCodeAt(i)
188
+ return (charCode >= 0xd800 && charCode <= 0xdbff) ? 2 : 1
189
+ }
190
+
191
+ function count_code_points(str) {
192
+ let code_points = 0
193
+ for (let i = 0; i < str.length; i++) {
194
+ if (str.charCodeAt(i) >= 0xd800 && str.charCodeAt(i) <= 0xdbff) i++
195
+ code_points++
196
+ }
197
+ return code_points
198
+ }
199
+
200
+ function simple_diff(a, b) {
201
+ // Find common prefix
202
+ var p = 0
203
+ var len = Math.min(a.length, b.length)
204
+ while (p < len && a[p] === b[p]) p++
142
205
 
143
- function count_code_points(str) {
144
- let code_points = 0
145
- for (let i = 0; i < str.length; i++) {
146
- if (str.charCodeAt(i) >= 0xd800 && str.charCodeAt(i) <= 0xdbff) i++
147
- code_points++
206
+ // Find common suffix (from what remains after prefix)
207
+ var s = 0
208
+ len -= p
209
+ while (s < len && a[a.length - s - 1] === b[b.length - s - 1]) s++
210
+
211
+ return {range: [p, a.length - s], content: b.slice(p, b.length - s)}
212
+ }
213
+
214
+ function apply_patches(state, patches) {
215
+ var offset = 0
216
+ for (var p of patches) {
217
+ state = state.substring(0, p.range[0] + offset) + p.content +
218
+ state.substring(p.range[1] + offset)
219
+ offset += p.content.length - (p.range[1] - p.range[0])
220
+ }
221
+ return state
148
222
  }
149
- return code_points
150
223
  }