braid-text 0.0.1

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.
@@ -0,0 +1,316 @@
1
+ <html lang="en">
2
+ <script type="statebus">
3
+ dom.BODY = -> DIV(WIKI())
4
+ </script>
5
+ <meta name="viewport" content="width=device-width,initial-scale=.62"/>
6
+
7
+ <script src="https://stateb.us/client6.js" server="none"></script>
8
+ <script src="https://invisible.college/js/marked.min.js"></script>
9
+
10
+ <script src="https://braid.org/code/myers-diff1.js"></script>
11
+ <script>
12
+ window.statebus_fetch = window.fetch
13
+ window.fetch = window.og_fetch
14
+ </script>
15
+ <script src="https://unpkg.com/braid-http@~0.3/braid-http-client.js"></script>
16
+ <script>
17
+ window.fetch = window.statebus_fetch
18
+ </script>
19
+ <script src="https://unpkg.com/braid-text/client.js"></script>
20
+
21
+ <script>
22
+
23
+ var apply_patches_and_update_selection, diff, first_time, i, j, render_delay, scroll, braid_text_key, t, timer, ting, toggle_editor, update_markdown, update_markdown_later;
24
+
25
+ braid_text_key = location.pathname;
26
+
27
+ t = function() {
28
+ return document.getElementById('the editor');
29
+ };
30
+
31
+ var braid_text = braid_text_client(braid_text_key, {
32
+ apply_remote_update: function(x) {
33
+ if (x.state !== void 0) {
34
+ t().value = x.state;
35
+ } else {
36
+ apply_patches_and_update_selection(t(), x.patches);
37
+ }
38
+ state.source = t().value;
39
+ update_markdown_later();
40
+ return t().value;
41
+ },
42
+ generate_local_diff_update: function(prev_state) {
43
+ var patches;
44
+ patches = diff(prev_state, t().value);
45
+ if (patches.length === 0) {
46
+ return null;
47
+ }
48
+ return {
49
+ patches: patches,
50
+ state: t().value
51
+ };
52
+ }
53
+ });
54
+
55
+ window.statebus_ready || (window.statebus_ready = []);
56
+
57
+ window.statebus_ready.push(function() {
58
+ state.vert = true;
59
+ state.editing = false;
60
+ state.source = '';
61
+ // Toggle the editor with keyboard or edit button
62
+ document.body.onkeydown = function(e) {
63
+ if (e.keyCode === 27) { // Escape key
64
+ e.stopPropagation();
65
+ toggle_editor();
66
+ }
67
+ };
68
+ // Switch to vertical layout when you resize
69
+ window.onresize = function() {
70
+ return state.vert = window.innerWidth < 1200;
71
+ };
72
+ return onresize();
73
+ });
74
+
75
+ // Diffing and Patching Utilities
76
+ diff = function(before, after) {
77
+ var d, diff2, j, len, offset, p, patches;
78
+ diff2 = diff_main(before, after);
79
+ // Now we just need to reformat the output from diff_main into some
80
+ // nice json objects
81
+ patches = [];
82
+ offset = 0;
83
+ for (j = 0, len = diff2.length; j < len; j++) {
84
+ d = diff2[j];
85
+ p = null;
86
+ if (d[0] === 1) {
87
+ p = {
88
+ range: [offset, offset],
89
+ content: d[1]
90
+ };
91
+ } else if (d[0] === -1) {
92
+ p = {
93
+ range: [offset, offset + d[1].length],
94
+ content: ''
95
+ };
96
+ offset += d[1].length;
97
+ } else {
98
+ offset += d[1].length;
99
+ }
100
+ if (p) {
101
+ patches.push(p);
102
+ }
103
+ }
104
+ return patches;
105
+ };
106
+
107
+ apply_patches_and_update_selection = function(textarea, patches) {
108
+ var i, j, k, l, len, len1, m, offset, original, p, range, ref, ref1, sel;
109
+ // convert from absolute to relative coordinates
110
+ offset = 0;
111
+ for (j = 0, len = patches.length; j < len; j++) {
112
+ p = patches[j];
113
+ p.range[0] += offset;
114
+ p.range[1] += offset;
115
+ offset -= p.range[1] - p.range[0];
116
+ offset += p.content.length;
117
+ }
118
+ original = textarea.value;
119
+ sel = [
120
+ textarea.selectionStart,
121
+ textarea.selectionEnd // Current cursor & selection
122
+ ];
123
+ for (k = 0, len1 = patches.length; k < len1; k++) {
124
+ p = patches[k];
125
+ range = p.range;
126
+ // Update the cursor locations
127
+ for (i = l = 0, ref = sel.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) {
128
+ if (sel[i] > range[0]) {
129
+ if (sel[i] > range[1]) {
130
+ sel[i] -= range[1] - range[0];
131
+ } else {
132
+ sel[i] = range[0];
133
+ }
134
+ }
135
+ }
136
+ for (i = m = 0, ref1 = sel.length; (0 <= ref1 ? m < ref1 : m > ref1); i = 0 <= ref1 ? ++m : --m) {
137
+ if (sel[i] > range[0]) {
138
+ sel[i] += p.content.length;
139
+ }
140
+ }
141
+ // Update the text with the new value
142
+ original = original.substring(0, range[0]) + p.content + original.substring(range[1]);
143
+ }
144
+ textarea.value = original;
145
+ textarea.selectionStart = sel[0];
146
+ return textarea.selectionEnd = sel[1];
147
+ };
148
+
149
+ // Render everything
150
+ dom.WIKI = function() {
151
+ var o;
152
+ // output
153
+ return DIV({}, DIV({
154
+ className: 'pad',
155
+ maxWidth: 750,
156
+ width: state.editing && !state.vert ? '55vw' : void 0
157
+ }, (function() {
158
+ var j, len, ref, results;
159
+ ref = state.outputs || [];
160
+ results = [];
161
+ for (j = 0, len = ref.length; j < len; j++) {
162
+ o = ref[j];
163
+ results.push(DIV({
164
+ dangerouslySetInnerHTML: {
165
+ __html: o
166
+ }
167
+ }));
168
+ }
169
+ return results;
170
+ // bottom pad
171
+ })()), DIV({
172
+ height: '50vh',
173
+ display: !state.editing || !state.vert ? 'none' : void 0
174
+ }), TEXTAREA({
175
+ position: 'fixed',
176
+ hyphens: 'none',
177
+ bottom: 0,
178
+ right: 0,
179
+ width: state.vert ? '100%' : '45vw',
180
+ height: state.vert ? '50vh' : '100%',
181
+ display: !state.editing ? 'none' : void 0,
182
+ fontSize: 15,
183
+ fontFamily: 'helvetica, arial, avenir, lucida grande',
184
+ id: 'the editor',
185
+ onChange: function(e) {
186
+ if (!e.target.value && e.target.value !== '') {
187
+ return;
188
+ }
189
+ // Bail on edits that try to wipe us out
190
+ state.source = e.target.value;
191
+ braid_text.changed();
192
+ return update_markdown_later();
193
+ },
194
+ defaultValue: state.source
195
+ }), DIV({
196
+ position: 'fixed',
197
+ bottom: 0,
198
+ right: 0,
199
+ padding: 30,
200
+ cursor: 'pointer',
201
+ textDecoration: 'none',
202
+ backgroundColor: 'rgba(250, 250, 250, .5)',
203
+ onClick: toggle_editor
204
+ }, 'edit'));
205
+ };
206
+
207
+ // Render markdown after a delay
208
+ timer = null;
209
+
210
+ render_delay = 100;
211
+
212
+ update_markdown_later = function() {
213
+ if (timer) {
214
+ clearTimeout(timer);
215
+ }
216
+ return timer = setTimeout(update_markdown, render_delay);
217
+ };
218
+
219
+ update_markdown = function() {
220
+ var e, i, j, len, parse_markdown, ref, results, s, sources;
221
+ parse_markdown = function() {
222
+ var match, matches;
223
+ matches = (function() {
224
+ var results;
225
+ results = [];
226
+ while (match = /\n\|{3,}([^\n]*)\n/g.exec(state.source)) {
227
+ results.push(match[1]);
228
+ }
229
+ return results;
230
+ })();
231
+ return matches;
232
+ };
233
+ try {
234
+ if (!state.source) {
235
+ return;
236
+ }
237
+ sources = state.source.split(/\n\|{3,}[^\n]*\n/g);
238
+ timer = null;
239
+ if (!state.sources || sources.length !== state.sources.length) {
240
+ state.sources = sources.splice();
241
+ state.outputs = (function() {
242
+ var j, len, results;
243
+ results = [];
244
+ for (j = 0, len = sources.length; j < len; j++) {
245
+ s = sources[j];
246
+ results.push(marked(s, {
247
+ sanitize: false
248
+ }));
249
+ }
250
+ return results;
251
+ })();
252
+ return document.body.className = 'nopad';
253
+ } else {
254
+ ref = state.sources;
255
+ // But most of the time we just redo one section
256
+ results = [];
257
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
258
+ s = ref[i];
259
+ if (s !== sources[i]) {
260
+ state.sources[i] = sources[i];
261
+ results.push(state.outputs[i] = marked(s, {
262
+ sanitize: false
263
+ }));
264
+ } else {
265
+ results.push(void 0);
266
+ }
267
+ }
268
+ return results;
269
+ }
270
+ } catch (error) {
271
+ e = error;
272
+ return console.error('parse failure with', e);
273
+ }
274
+ };
275
+
276
+ update_markdown();
277
+
278
+ first_time = true;
279
+
280
+ toggle_editor = function() {
281
+ state.editing = !state.editing;
282
+ if (state.editing) {
283
+ t().focus();
284
+ }
285
+ if (state.editing && first_time) {
286
+ first_time = false;
287
+ t().setSelectionRange(0, 0);
288
+ t().scrollTop = 0;
289
+ }
290
+ return update_markdown();
291
+ };
292
+
293
+ // Support #hashtag scrolling into view
294
+ ting = null;
295
+
296
+ scroll = function() {
297
+ // We only scroll to the ting once -- if it's fresh
298
+ if (ting || location.hash.length === 0) {
299
+ return;
300
+ }
301
+ ting = document.getElementById(location.hash.substr(1));
302
+ return ting && ting.scrollIntoView();
303
+ };
304
+
305
+ for (i = j = 0; j <= 50; i = ++j) {
306
+ setTimeout(scroll, i / 5.0 * 1000);
307
+ }
308
+
309
+ </script>
310
+
311
+ <link rel="stylesheet" href="https://invisible.college/css/github-markdown.css">
312
+ <style>
313
+ body{-ms-hyphens: auto;-webkit-hyphens: auto;hyphens: auto;}
314
+ h1,h2,h3,h4 {text-align: left; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none;}
315
+ note {position: absolute; left: 720px; width: 270px; background-color: #F8F3B7; padding: 10px; box-shadow: -2px 2px 2px #ccc; border-radius: 2px; text-align: left;}
316
+ </style>
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "braid-text",
3
+ "version": "0.0.1",
4
+ "description": "Library for collaborative text over http using braid.",
5
+ "author": "Braid Working Group",
6
+ "repository": "braid-org/braidjs",
7
+ "homepage": "https://braid.org",
8
+ "dependencies": {
9
+ "diamond-types-node": "^1.0.2",
10
+ "braid-http": "^0.3.18"
11
+ }
12
+ }
package/server-demo.js ADDED
@@ -0,0 +1,79 @@
1
+
2
+ var port = 8888
3
+
4
+ var braid_text = require("./index.js")
5
+
6
+ // TODO: set a custom database folder
7
+ // (the default is ./braid-text-db)
8
+ //
9
+ // braid_text.db_folder = './custom_db_folder'
10
+
11
+ var server = require("http").createServer(async (req, res) => {
12
+ console.log(`${req.method} ${req.url}`)
13
+
14
+ if (req.url.endsWith("?editor")) {
15
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" })
16
+ require("fs").createReadStream("./editor.html").pipe(res)
17
+ return
18
+ }
19
+
20
+ if (req.url.endsWith("?markdown-editor")) {
21
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" })
22
+ require("fs").createReadStream("./markdown-editor.html").pipe(res)
23
+ return
24
+ }
25
+
26
+ // TODO: uncomment out the code below to add /pages endpoint,
27
+ // which displays all the currently used keys
28
+ //
29
+ // if (req.url === '/pages') {
30
+ // var pages = new Set()
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
+ // }
35
+ // res.writeHead(200, {
36
+ // "Content-Type": "application/json",
37
+ // "Access-Control-Allow-Origin": "*",
38
+ // "Access-Control-Allow-Methods": "*",
39
+ // "Access-Control-Allow-Headers": "*",
40
+ // "Access-Control-Expose-Headers": "*"
41
+ // })
42
+ // res.end(JSON.stringify([...pages.keys()]))
43
+ // return
44
+ // }
45
+
46
+ // TODO: uncomment and change admin_pass above,
47
+ // and uncomment out the code below to add basic access control
48
+ //
49
+ // var admin_pass = "fake_password"
50
+ //
51
+ // if (req.url === '/login_' + admin_pass) {
52
+ // res.writeHead(200, {
53
+ // "Content-Type": "text/plain",
54
+ // "Set-Cookie": `admin_pass=${admin_pass}; Path=/`,
55
+ // });
56
+ // res.end("Logged in successfully");
57
+ // return;
58
+ // }
59
+ //
60
+ // if (req.method == "PUT" || req.method == "POST" || req.method == "PATCH") {
61
+ // if (!req.headers.cookie?.includes(`admin_pass=${admin_pass}`)) {
62
+ // console.log("Blocked PUT:", { cookie: req.headers.cookie })
63
+ // res.statusCode = 401
64
+ // return res.end()
65
+ // }
66
+ // }
67
+
68
+ // Create some initial text for new documents
69
+ if (await braid_text.get(req.url) === undefined) {
70
+ await braid_text.put(req.url, {body: 'This is a fresh blank document, ready for you to edit.' })
71
+ }
72
+
73
+ // Now serve the collaborative text!
74
+ braid_text.serve(req, res)
75
+ })
76
+
77
+ server.listen(port, () => {
78
+ console.log(`server started on port ${port}`)
79
+ })