braid-text 0.3.9 → 0.3.10
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.
|
@@ -62,8 +62,18 @@ function textarea_highlights(textarea) {
|
|
|
62
62
|
|
|
63
63
|
// Move textarea's background to the wrapper so backdrops show through
|
|
64
64
|
var bg = getComputedStyle(textarea).backgroundColor
|
|
65
|
-
if (!wrap.style.backgroundColor)
|
|
66
|
-
|
|
65
|
+
if (!wrap.style.backgroundColor) {
|
|
66
|
+
if (!bg || bg === 'rgba(0, 0, 0, 0)') {
|
|
67
|
+
// Walk up the DOM to find the effective background
|
|
68
|
+
var el = wrap
|
|
69
|
+
while (el) {
|
|
70
|
+
var elBg = getComputedStyle(el).backgroundColor
|
|
71
|
+
if (elBg && elBg !== 'rgba(0, 0, 0, 0)') { bg = elBg; break }
|
|
72
|
+
el = el.parentElement
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
wrap.style.backgroundColor = bg || 'white'
|
|
76
|
+
}
|
|
67
77
|
textarea.style.backgroundColor = 'transparent'
|
|
68
78
|
textarea.style.position = 'relative'
|
|
69
79
|
textarea.style.zIndex = '2'
|
|
@@ -158,6 +168,77 @@ function textarea_highlights(textarea) {
|
|
|
158
168
|
return result
|
|
159
169
|
}
|
|
160
170
|
|
|
171
|
+
// --- render implementation ---
|
|
172
|
+
|
|
173
|
+
function do_render() {
|
|
174
|
+
var text = textarea.value
|
|
175
|
+
var len = text.length
|
|
176
|
+
var style_str = backdrop_style()
|
|
177
|
+
|
|
178
|
+
// Remove divs for layers that no longer exist
|
|
179
|
+
for (var id of Object.keys(layer_divs)) {
|
|
180
|
+
if (!layer_data[id]) {
|
|
181
|
+
layer_divs[id].remove()
|
|
182
|
+
delete layer_divs[id]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Render each layer
|
|
187
|
+
for (var id of Object.keys(layer_data)) {
|
|
188
|
+
var highlights = layer_data[id].map(h => ({
|
|
189
|
+
from: Math.min(h.from, len),
|
|
190
|
+
to: Math.min(h.to, len),
|
|
191
|
+
color: h.color
|
|
192
|
+
}))
|
|
193
|
+
|
|
194
|
+
if (!layer_divs[id]) {
|
|
195
|
+
var div = document.createElement('div')
|
|
196
|
+
div.className = 'textarea-hl-backdrop'
|
|
197
|
+
wrap.insertBefore(div, textarea)
|
|
198
|
+
layer_divs[id] = div
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Font/padding/border are set inline to match textarea;
|
|
202
|
+
// positioning/pointer-events/etc come from the CSS class.
|
|
203
|
+
layer_divs[id].style.cssText = style_str
|
|
204
|
+
|
|
205
|
+
layer_divs[id].innerHTML = build_html(text, highlights)
|
|
206
|
+
layer_divs[id].scrollTop = textarea.scrollTop
|
|
207
|
+
layer_divs[id].scrollLeft = textarea.scrollLeft
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- show local cursor/selection when textarea is not focused ---
|
|
212
|
+
|
|
213
|
+
var local_id = '__local__'
|
|
214
|
+
|
|
215
|
+
function on_blur() {
|
|
216
|
+
var from = textarea.selectionStart
|
|
217
|
+
var to = textarea.selectionEnd
|
|
218
|
+
var color = getComputedStyle(textarea).caretColor
|
|
219
|
+
if (!color || color === 'auto') color = getComputedStyle(textarea).color
|
|
220
|
+
if (from === to) {
|
|
221
|
+
layer_data[local_id] = [{ from, to, color: color }]
|
|
222
|
+
} else {
|
|
223
|
+
var match = color.match(/(\d+),\s*(\d+),\s*(\d+)/)
|
|
224
|
+
var sel_color = match ? 'rgba(' + match[1] + ', ' + match[2] + ', ' + match[3] + ', 0.3)' : color
|
|
225
|
+
layer_data[local_id] = [{ from, to, color: sel_color }]
|
|
226
|
+
}
|
|
227
|
+
do_render()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function on_focus() {
|
|
231
|
+
delete layer_data[local_id]
|
|
232
|
+
if (layer_divs[local_id]) {
|
|
233
|
+
layer_divs[local_id].remove()
|
|
234
|
+
delete layer_divs[local_id]
|
|
235
|
+
}
|
|
236
|
+
do_render()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
textarea.addEventListener('blur', on_blur)
|
|
240
|
+
textarea.addEventListener('focus', on_focus)
|
|
241
|
+
|
|
161
242
|
return {
|
|
162
243
|
set: function(layer_id, highlights) {
|
|
163
244
|
layer_data[layer_id] = highlights
|
|
@@ -171,43 +252,7 @@ function textarea_highlights(textarea) {
|
|
|
171
252
|
}
|
|
172
253
|
},
|
|
173
254
|
|
|
174
|
-
render:
|
|
175
|
-
var text = textarea.value
|
|
176
|
-
var len = text.length
|
|
177
|
-
var style_str = backdrop_style()
|
|
178
|
-
|
|
179
|
-
// Remove divs for layers that no longer exist
|
|
180
|
-
for (var id of Object.keys(layer_divs)) {
|
|
181
|
-
if (!layer_data[id]) {
|
|
182
|
-
layer_divs[id].remove()
|
|
183
|
-
delete layer_divs[id]
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Render each layer
|
|
188
|
-
for (var id of Object.keys(layer_data)) {
|
|
189
|
-
var highlights = layer_data[id].map(h => ({
|
|
190
|
-
from: Math.min(h.from, len),
|
|
191
|
-
to: Math.min(h.to, len),
|
|
192
|
-
color: h.color
|
|
193
|
-
}))
|
|
194
|
-
|
|
195
|
-
if (!layer_divs[id]) {
|
|
196
|
-
var div = document.createElement('div')
|
|
197
|
-
div.className = 'textarea-hl-backdrop'
|
|
198
|
-
wrap.insertBefore(div, textarea)
|
|
199
|
-
layer_divs[id] = div
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Font/padding/border are set inline to match textarea;
|
|
203
|
-
// positioning/pointer-events/etc come from the CSS class.
|
|
204
|
-
layer_divs[id].style.cssText = style_str
|
|
205
|
-
|
|
206
|
-
layer_divs[id].innerHTML = build_html(text, highlights)
|
|
207
|
-
layer_divs[id].scrollTop = textarea.scrollTop
|
|
208
|
-
layer_divs[id].scrollLeft = textarea.scrollLeft
|
|
209
|
-
}
|
|
210
|
-
},
|
|
255
|
+
render: do_render,
|
|
211
256
|
|
|
212
257
|
layers: function() {
|
|
213
258
|
return Object.keys(layer_data)
|
|
@@ -215,6 +260,8 @@ function textarea_highlights(textarea) {
|
|
|
215
260
|
|
|
216
261
|
destroy: function() {
|
|
217
262
|
textarea.removeEventListener('scroll', sync_scroll)
|
|
263
|
+
textarea.removeEventListener('blur', on_blur)
|
|
264
|
+
textarea.removeEventListener('focus', on_focus)
|
|
218
265
|
for (var div of Object.values(layer_divs)) div.remove()
|
|
219
266
|
layer_data = {}
|
|
220
267
|
layer_divs = {}
|
|
@@ -238,7 +285,8 @@ function peer_bg_color(peer_id) {
|
|
|
238
285
|
var r = parseInt(c.slice(1, 3), 16)
|
|
239
286
|
var g = parseInt(c.slice(3, 5), 16)
|
|
240
287
|
var b = parseInt(c.slice(5, 7), 16)
|
|
241
|
-
|
|
288
|
+
var dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
289
|
+
return `rgba(${r}, ${g}, ${b}, ${dark ? 0.4 : 0.25})`
|
|
242
290
|
}
|
|
243
291
|
|
|
244
292
|
// --- High-level wrapper ---
|
package/client/cursor-sync.js
CHANGED
|
@@ -141,13 +141,13 @@ async function cursor_client(url, { peer, get_text, on_change }) {
|
|
|
141
141
|
braid_fetch(url, {
|
|
142
142
|
subscribe: true,
|
|
143
143
|
retry: { onRes: function() {
|
|
144
|
-
if (connected_before
|
|
145
|
-
// Reconnecting —
|
|
146
|
-
//
|
|
144
|
+
if (connected_before) {
|
|
145
|
+
// Reconnecting — clear stale cursors; fresh snapshot incoming.
|
|
146
|
+
// Stay in current online state so the snapshot is processed
|
|
147
|
+
// immediately (the text subscription manages online/offline).
|
|
147
148
|
var changed = {}
|
|
148
149
|
for (var id of Object.keys(selections)) changed[id] = []
|
|
149
150
|
selections = {}
|
|
150
|
-
online = false
|
|
151
151
|
pending = null
|
|
152
152
|
if (on_change && Object.keys(changed).length) on_change(changed)
|
|
153
153
|
}
|
package/client/editor.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<body style="margin: 0px; padding: 0px; box-sizing: border-box">
|
|
2
|
-
<textarea id="the_editor" style="width: 100%; height: 100%;"></textarea>
|
|
2
|
+
<textarea id="the_editor" style="width: 100%; height: 100%;" disabled></textarea>
|
|
3
3
|
</body>
|
|
4
4
|
<script src="https://braid.org/code/myers-diff1.js"></script>
|
|
5
5
|
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
var cursors = cursor_highlights(the_editor, location.pathname)
|
|
13
13
|
|
|
14
14
|
var simpleton = simpleton_client(location.pathname, {
|
|
15
|
-
on_online: (
|
|
15
|
+
on_online: (online) => { online ? cursors.online() : cursors.offline() },
|
|
16
16
|
on_patches: (patches) => {
|
|
17
|
+
the_editor.disabled = false
|
|
17
18
|
apply_patches_and_update_selection(the_editor, patches)
|
|
18
19
|
cursors.on_patches(patches)
|
|
19
20
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
">
|
|
19
19
|
<textarea
|
|
20
20
|
id="the_editor"
|
|
21
|
+
disabled
|
|
21
22
|
style="
|
|
22
23
|
width: 100%;
|
|
23
24
|
height: 100%;
|
|
@@ -48,8 +49,9 @@ var render_delay = 100
|
|
|
48
49
|
var cursors = cursor_highlights(the_editor, location.pathname)
|
|
49
50
|
|
|
50
51
|
var simpleton = simpleton_client(location.pathname, {
|
|
51
|
-
on_online: (
|
|
52
|
+
on_online: (online) => { online ? cursors.online() : cursors.offline() },
|
|
52
53
|
on_patches: (patches) => {
|
|
54
|
+
the_editor.disabled = false
|
|
53
55
|
apply_patches_and_update_selection(the_editor, patches)
|
|
54
56
|
cursors.on_patches(patches)
|
|
55
57
|
update_markdown_later()
|
package/client/simpleton-sync.js
CHANGED
|
@@ -127,7 +127,7 @@ function simpleton_client(url, {
|
|
|
127
127
|
...(content_type ? {Accept: content_type} : {}) },
|
|
128
128
|
subscribe: true,
|
|
129
129
|
retry: () => true,
|
|
130
|
-
onSubscriptionStatus: (status) => { if (on_online) on_online(status) },
|
|
130
|
+
onSubscriptionStatus: (status) => { if (on_online) on_online(status.online) },
|
|
131
131
|
parents: () => client_version.length ? client_version : null,
|
|
132
132
|
peer,
|
|
133
133
|
signal: ac.signal
|