Fossil SCM

Get the /chat-send and /chat-poll interfaces working.

drh 2020-12-23 00:58 chatroom-dev
Commit 25828eb581d7599e608a38395d389f5952a49363504c3f6534b2c43598515bf4
+111 -15
--- src/chat.c
+++ src/chat.c
@@ -102,19 +102,27 @@
102102
@ <input type="file" name="file">
103103
@ </div>
104104
@ </div>
105105
@ </form>
106106
@ <hr>
107
+
107108
/* New chat messages get inserted immediately after this element */
108109
@ <span id='message-inject-point'></span>
110
+
111
+ /* Always in-line the javascript for the chat page */
112
+ @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
113
+ @ let _me = "%j(g.zLogin)";
114
+ cgi_append_content(builtin_text("chat.js"),-1);
115
+ @ </script>
116
+
109117
style_finish_page();
110118
}
111119
112120
/* Definition of repository tables used by chat
113121
*/
114122
static const char zChatSchema1[] =
115
-@ CREATE TABLE chat(
123
+@ CREATE TABLE repository.chat(
116124
@ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
117125
@ mtime JULIANDAY,
118126
@ xfrom TEXT,
119127
@ xmsg TEXT,
120128
@ file BLOB,
@@ -139,13 +147,39 @@
139147
**
140148
** This page receives (via XHR) a new chat-message and/or a new file
141149
** to be entered into the chat history.
142150
*/
143151
void chat_send_webpage(void){
152
+ int nByte;
153
+ const char *zMsg;
144154
login_check_credentials();
145155
if( !g.perm.Chat ) return;
146156
chat_create_tables();
157
+ nByte = atoi(PD("file:bytes",0));
158
+ zMsg = PD("msg","");
159
+ if( nByte==0 ){
160
+ if( zMsg[0] ){
161
+ db_multi_exec(
162
+ "INSERT INTO chat(mtime,xfrom,xmsg)"
163
+ "VALUES(julianday('now'),%Q,%Q)",
164
+ g.zLogin, zMsg
165
+ );
166
+ }
167
+ }else{
168
+ Stmt q;
169
+ Blob b;
170
+ db_prepare(&q,
171
+ "INSERT INTO chat(mtime, xfrom,xmsg,file,fname,fmime)"
172
+ "VALUES(julianday('now'),%Q,%Q,:file,%Q,%Q)",
173
+ g.zLogin, zMsg, PD("file:filename",""),
174
+ PD("file:mimetype","application/octet-stream"));
175
+ blob_init(&b, P("file"), nByte);
176
+ db_bind_blob(&q, ":file", &b);
177
+ db_step(&q);
178
+ db_finalize(&q);
179
+ blob_reset(&b);
180
+ }
147181
}
148182
149183
/*
150184
** WEBPAGE: chat-poll
151185
**
@@ -158,35 +192,97 @@
158192
** system implements "hanging-GET" or "long-poll" style event notification.
159193
**
160194
** The reply from this webpage is JSON that describes the new content.
161195
** Format of the json:
162196
**
163
-** {
164
-** "msg": [
165
-** {
166
-** "msgid": integer // message id
167
-** "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
168
-** "xfrom": text // Login name of sender
169
-** "uclr": text // Color string associated with the user
170
-** "xmsg": text // HTML text of the message
171
-** "fsize": integer // file attachment size in bytes
172
-** "fname": text // Name of file attachment
173
-** "fmime": text // MIME-type of file attachment
174
-** }
175
-** ]
176
-** }
197
+** | {
198
+** | "msg":[
199
+** | {
200
+** | "msgid": integer // message id
201
+** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
202
+** | "xfrom": text // Login name of sender
203
+** | "uclr": text // Color string associated with the user
204
+** | "xmsg": text // HTML text of the message
205
+** | "fsize": integer // file attachment size in bytes
206
+** | "fname": text // Name of file attachment
207
+** | "fmime": text // MIME-type of file attachment
208
+** | }
209
+** | ]
210
+** | }
177211
**
178212
** The "fname" and "fmime" fields are only present if "fsize" is greater
179213
** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
180214
**
181215
** The "msgid" values will be in increasing order.
182216
*/
183217
void chat_poll_webpage(void){
218
+ Blob json; /* The json to be constructed and returned */
219
+ sqlite3_int64 dataVersion; /* Data version. Used for polling. */
220
+ sqlite3_int64 newDataVers;
221
+ int iDelay = 1000; /* Delay until next poll (milliseconds) */
222
+ const char *zSep = "{\"msgs\":[\n"; /* List separator */
223
+ int msgid = atoi(PD("name","0"));
224
+ Stmt q1;
184225
login_check_credentials();
185226
if( !g.perm.Chat ) return;
186227
chat_create_tables();
187228
cgi_set_content_type("text/json");
229
+ dataVersion = db_int64(0, "PRAGMA data_version");
230
+ db_prepare(&q1,
231
+ "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file), fname, fmime"
232
+ " FROM chat"
233
+ " WHERE msgid>%d"
234
+ " ORDER BY msgid",
235
+ msgid
236
+ );
237
+ blob_init(&json, 0, 0);
238
+ while(1){
239
+ int cnt = 0;
240
+ while( db_step(&q1)==SQLITE_ROW ){
241
+ int id = db_column_int(&q1, 0);
242
+ const char *zDate = db_column_text(&q1, 1);
243
+ const char *zFrom = db_column_text(&q1, 2);
244
+ const char *zRawMsg = db_column_text(&q1, 3);
245
+ char *zMsg;
246
+ int nByte;
247
+ cnt++;
248
+ blob_append(&json, zSep, -1);
249
+ zSep = ",\n";
250
+ blob_appendf(&json, "{\"msgid\":%d,\"mtime\":\"%j\",", id, zDate);
251
+ blob_appendf(&json, "\"xfrom\":\"%j\",", zFrom);
252
+ blob_appendf(&json, "\"uclr\":\"%j\",", hash_color(zFrom));
253
+
254
+ /* TBD: Convert the raw message into HTML, perhaps by running it
255
+ ** through a text formatter, or putting markup on @name phrases,
256
+ ** etc. */
257
+ zMsg = mprintf("%h", zRawMsg);
258
+ blob_appendf(&json, "\"xmsg\":\"%j\",", zMsg);
259
+ fossil_free(zMsg);
260
+
261
+ nByte = db_column_bytes(&q1, 4);
262
+ if( nByte==0 ){
263
+ blob_appendf(&json, "\"fsize\":0}");
264
+ }else{
265
+ const char *zFName = db_column_text(&q1, 5);
266
+ const char *zFMime = db_column_text(&q1, 6);
267
+ blob_appendf(&json, "\"fsize\":%d,\"fname\":\"%j\",\"fmime\":\"%j\"}",
268
+ nByte, zFName, zFMime);
269
+ }
270
+ }
271
+ if( cnt ){
272
+ blob_append(&json, "\n]}", 3);
273
+ cgi_set_content(&json);
274
+ break;
275
+ }
276
+ sqlite3_sleep(iDelay);
277
+ while( (newDataVers = db_int64(0,"PRAGMA data_version"))==dataVersion ){
278
+ sqlite3_sleep(iDelay);
279
+ }
280
+ dataVersion = newDataVers;
281
+ } /* Exit by "break" */
282
+ db_finalize(&q1);
283
+ return;
188284
}
189285
190286
/*
191287
** WEBPAGE: chat-download
192288
**
193289
--- src/chat.c
+++ src/chat.c
@@ -102,19 +102,27 @@
102 @ <input type="file" name="file">
103 @ </div>
104 @ </div>
105 @ </form>
106 @ <hr>
 
107 /* New chat messages get inserted immediately after this element */
108 @ <span id='message-inject-point'></span>
 
 
 
 
 
 
 
109 style_finish_page();
110 }
111
112 /* Definition of repository tables used by chat
113 */
114 static const char zChatSchema1[] =
115 @ CREATE TABLE chat(
116 @ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
117 @ mtime JULIANDAY,
118 @ xfrom TEXT,
119 @ xmsg TEXT,
120 @ file BLOB,
@@ -139,13 +147,39 @@
139 **
140 ** This page receives (via XHR) a new chat-message and/or a new file
141 ** to be entered into the chat history.
142 */
143 void chat_send_webpage(void){
 
 
144 login_check_credentials();
145 if( !g.perm.Chat ) return;
146 chat_create_tables();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147 }
148
149 /*
150 ** WEBPAGE: chat-poll
151 **
@@ -158,35 +192,97 @@
158 ** system implements "hanging-GET" or "long-poll" style event notification.
159 **
160 ** The reply from this webpage is JSON that describes the new content.
161 ** Format of the json:
162 **
163 ** {
164 ** "msg": [
165 ** {
166 ** "msgid": integer // message id
167 ** "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
168 ** "xfrom": text // Login name of sender
169 ** "uclr": text // Color string associated with the user
170 ** "xmsg": text // HTML text of the message
171 ** "fsize": integer // file attachment size in bytes
172 ** "fname": text // Name of file attachment
173 ** "fmime": text // MIME-type of file attachment
174 ** }
175 ** ]
176 ** }
177 **
178 ** The "fname" and "fmime" fields are only present if "fsize" is greater
179 ** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
180 **
181 ** The "msgid" values will be in increasing order.
182 */
183 void chat_poll_webpage(void){
 
 
 
 
 
 
 
184 login_check_credentials();
185 if( !g.perm.Chat ) return;
186 chat_create_tables();
187 cgi_set_content_type("text/json");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188 }
189
190 /*
191 ** WEBPAGE: chat-download
192 **
193
--- src/chat.c
+++ src/chat.c
@@ -102,19 +102,27 @@
102 @ <input type="file" name="file">
103 @ </div>
104 @ </div>
105 @ </form>
106 @ <hr>
107
108 /* New chat messages get inserted immediately after this element */
109 @ <span id='message-inject-point'></span>
110
111 /* Always in-line the javascript for the chat page */
112 @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
113 @ let _me = "%j(g.zLogin)";
114 cgi_append_content(builtin_text("chat.js"),-1);
115 @ </script>
116
117 style_finish_page();
118 }
119
120 /* Definition of repository tables used by chat
121 */
122 static const char zChatSchema1[] =
123 @ CREATE TABLE repository.chat(
124 @ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
125 @ mtime JULIANDAY,
126 @ xfrom TEXT,
127 @ xmsg TEXT,
128 @ file BLOB,
@@ -139,13 +147,39 @@
147 **
148 ** This page receives (via XHR) a new chat-message and/or a new file
149 ** to be entered into the chat history.
150 */
151 void chat_send_webpage(void){
152 int nByte;
153 const char *zMsg;
154 login_check_credentials();
155 if( !g.perm.Chat ) return;
156 chat_create_tables();
157 nByte = atoi(PD("file:bytes",0));
158 zMsg = PD("msg","");
159 if( nByte==0 ){
160 if( zMsg[0] ){
161 db_multi_exec(
162 "INSERT INTO chat(mtime,xfrom,xmsg)"
163 "VALUES(julianday('now'),%Q,%Q)",
164 g.zLogin, zMsg
165 );
166 }
167 }else{
168 Stmt q;
169 Blob b;
170 db_prepare(&q,
171 "INSERT INTO chat(mtime, xfrom,xmsg,file,fname,fmime)"
172 "VALUES(julianday('now'),%Q,%Q,:file,%Q,%Q)",
173 g.zLogin, zMsg, PD("file:filename",""),
174 PD("file:mimetype","application/octet-stream"));
175 blob_init(&b, P("file"), nByte);
176 db_bind_blob(&q, ":file", &b);
177 db_step(&q);
178 db_finalize(&q);
179 blob_reset(&b);
180 }
181 }
182
183 /*
184 ** WEBPAGE: chat-poll
185 **
@@ -158,35 +192,97 @@
192 ** system implements "hanging-GET" or "long-poll" style event notification.
193 **
194 ** The reply from this webpage is JSON that describes the new content.
195 ** Format of the json:
196 **
197 ** | {
198 ** | "msg":[
199 ** | {
200 ** | "msgid": integer // message id
201 ** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
202 ** | "xfrom": text // Login name of sender
203 ** | "uclr": text // Color string associated with the user
204 ** | "xmsg": text // HTML text of the message
205 ** | "fsize": integer // file attachment size in bytes
206 ** | "fname": text // Name of file attachment
207 ** | "fmime": text // MIME-type of file attachment
208 ** | }
209 ** | ]
210 ** | }
211 **
212 ** The "fname" and "fmime" fields are only present if "fsize" is greater
213 ** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
214 **
215 ** The "msgid" values will be in increasing order.
216 */
217 void chat_poll_webpage(void){
218 Blob json; /* The json to be constructed and returned */
219 sqlite3_int64 dataVersion; /* Data version. Used for polling. */
220 sqlite3_int64 newDataVers;
221 int iDelay = 1000; /* Delay until next poll (milliseconds) */
222 const char *zSep = "{\"msgs\":[\n"; /* List separator */
223 int msgid = atoi(PD("name","0"));
224 Stmt q1;
225 login_check_credentials();
226 if( !g.perm.Chat ) return;
227 chat_create_tables();
228 cgi_set_content_type("text/json");
229 dataVersion = db_int64(0, "PRAGMA data_version");
230 db_prepare(&q1,
231 "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file), fname, fmime"
232 " FROM chat"
233 " WHERE msgid>%d"
234 " ORDER BY msgid",
235 msgid
236 );
237 blob_init(&json, 0, 0);
238 while(1){
239 int cnt = 0;
240 while( db_step(&q1)==SQLITE_ROW ){
241 int id = db_column_int(&q1, 0);
242 const char *zDate = db_column_text(&q1, 1);
243 const char *zFrom = db_column_text(&q1, 2);
244 const char *zRawMsg = db_column_text(&q1, 3);
245 char *zMsg;
246 int nByte;
247 cnt++;
248 blob_append(&json, zSep, -1);
249 zSep = ",\n";
250 blob_appendf(&json, "{\"msgid\":%d,\"mtime\":\"%j\",", id, zDate);
251 blob_appendf(&json, "\"xfrom\":\"%j\",", zFrom);
252 blob_appendf(&json, "\"uclr\":\"%j\",", hash_color(zFrom));
253
254 /* TBD: Convert the raw message into HTML, perhaps by running it
255 ** through a text formatter, or putting markup on @name phrases,
256 ** etc. */
257 zMsg = mprintf("%h", zRawMsg);
258 blob_appendf(&json, "\"xmsg\":\"%j\",", zMsg);
259 fossil_free(zMsg);
260
261 nByte = db_column_bytes(&q1, 4);
262 if( nByte==0 ){
263 blob_appendf(&json, "\"fsize\":0}");
264 }else{
265 const char *zFName = db_column_text(&q1, 5);
266 const char *zFMime = db_column_text(&q1, 6);
267 blob_appendf(&json, "\"fsize\":%d,\"fname\":\"%j\",\"fmime\":\"%j\"}",
268 nByte, zFName, zFMime);
269 }
270 }
271 if( cnt ){
272 blob_append(&json, "\n]}", 3);
273 cgi_set_content(&json);
274 break;
275 }
276 sqlite3_sleep(iDelay);
277 while( (newDataVers = db_int64(0,"PRAGMA data_version"))==dataVersion ){
278 sqlite3_sleep(iDelay);
279 }
280 dataVersion = newDataVers;
281 } /* Exit by "break" */
282 db_finalize(&q1);
283 return;
284 }
285
286 /*
287 ** WEBPAGE: chat-download
288 **
289
+6 -84
--- src/chat.js
+++ src/chat.js
@@ -1,100 +1,22 @@
11
(function(){
22
const form = document.querySelector('#chat-form');
33
let mxMsg = 0;
44
// let _me = "%string($me)";
5
+let me = "drh"; // FIX ME
56
form.addEventListener('submit',(e)=>{
67
e.preventDefault();
78
if( form.msg.value.length>0 || form.file.value.length>0 ){
8
- fetch("%string($submiturl)",{
9
+ fetch("chat-send",{
910
method: 'POST',
1011
body: new FormData(form)
1112
});
1213
}
1314
form.msg.value = "";
1415
form.file.value = "";
1516
form.msg.focus();
1617
});
17
- const rxUrl = /\b(?:https?|ftp):\/\/\[a-z0-9-+&@\#\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\/%=~_|]/gim;
18
- const rxAtName = /@\w+/gmi;
19
- // ^^^ achtung, extra backslashes needed for the outer TCL.
20
- const textNode = (T)=>document.createTextNode(T);
21
-
22
- // Converts a message string to a message-containing DOM element
23
- // and returns that element, which may contain child elements.
24
- // If 2nd arg is passed, it must be a DOM element to which all
25
- // child elements are appended.
26
- const messageToDOM = function f(str, tgtElem){
27
- "use strict";
28
- if(!f.rxUrl){
29
- f.rxUrl = rxUrl;
30
- f.rxAt = rxAtName;
31
- f.rxNS = /\S/;
32
- f.ce = (T)=>document.createElement(T);
33
- f.ct = (T)=>document.createTextNode(T);
34
- f.replaceUrls = function ff(sub, offset, whole){
35
- if(offset > ff.prevStart){
36
- f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
37
- }
38
- const a = f.ce('a');
39
- a.setAttribute('href',sub);
40
- a.setAttribute('target','_blank');
41
- a.appendChild(f.ct(sub));
42
- f.accum.push(a);
43
- ff.prevStart = offset + sub.length + 1;
44
- };
45
- f.replaceAtName = function ff(sub, offset,whole){
46
- if(offset > ff.prevStart){
47
- ff.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
48
- }else if(offset && f.rxNS.test(whole[offset-1])){
49
- // Sigh: https://stackoverflow.com/questions/52655367
50
- ff.accum.push(sub);
51
- return;
52
- }
53
- const e = f.ce('span');
54
- e.classList.add('at-name');
55
- e.appendChild(f.ct(sub));
56
- ff.accum.push(e);
57
- ff.prevStart = offset + sub.length + 1;
58
- };
59
- }
60
- f.accum = []; // accumulate strings and DOM elements here.
61
- f.rxUrl.lastIndex = f.replaceUrls.prevStart = 0; // reset regex cursor
62
- str.replace(f.rxUrl, f.replaceUrls);
63
- // Push remaining non-URL part of the string to the queue...
64
- if(f.replaceUrls.prevStart < str.length){
65
- f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
66
- }
67
- // Pass 2: process @NAME references...
68
- // TODO: only match NAME if it's the name of a currently participating
69
- // user. Add a second class if NAME == current user, and style that one
70
- // differently so that people can more easily see when they're spoken to.
71
- const accum2 = f.replaceAtName.accum = [];
72
- //console.debug("f.accum =",f.accum);
73
- f.accum.forEach(function(v){
74
- //console.debug("v =",v);
75
- if('string'===typeof v){
76
- f.rxAt.lastIndex = f.replaceAtName.prevStart = 0;
77
- v.replace(f.rxAt, f.replaceAtName);
78
- if(f.replaceAtName.prevStart < v.length){
79
- accum2.push((f.replaceAtName.prevStart?' ':'')+v.substring(f.replaceAtName.prevStart));
80
- }
81
- }else{
82
- accum2.push(v);
83
- }
84
- //console.debug("accum2 =",accum2);
85
- });
86
- delete f.accum;
87
- //console.debug("accum2 =",accum2);
88
- const span = tgtElem || f.ce('span');
89
- accum2.forEach(function(e){
90
- if('string'===typeof e) e = f.ct(e);
91
- span.appendChild(e);
92
- });
93
- //console.debug("span =",span.innerHTML);
94
- return span;
95
- }/*end messageToDOM()*/;
9618
/* Injects element e as a new row in the chat, at the top of the list */
9719
const injectMessage = function f(e){
9820
if(!f.injectPoint){
9921
f.injectPoint = document.querySelector('#message-inject-point');
10022
}
@@ -159,11 +81,11 @@
15981
let br = document.createElement("br");
16082
br.style.clear = "both";
16183
span.appendChild(br);
16284
}
16385
if(m.xmsg){
164
- messageToDOM(m.xmsg, span);
86
+ span.innerHTML += m.xmsg;
16587
}
16688
span.classList.add('chat-message');
16789
if( m.xfrom!=_me ){
16890
span.classList.add('chat-mx');
16991
}else{
@@ -172,12 +94,12 @@
17294
}
17395
}
17496
async function poll(){
17597
if(poll.running) return;
17698
poll.running = true;
177
- fetch("%string($pollurl)/" + mxMsg)
99
+ fetch("chat-poll/" + mxMsg)
178100
.then(x=>x.json())
179101
.then(y=>newcontent(y))
180102
.finally(()=>poll.running=false)
181103
}
182
- setInterval(poll, 1000);
183
-})();</script>
104
+ // setInterval(poll, 1000);
105
+})();
184106
--- src/chat.js
+++ src/chat.js
@@ -1,100 +1,22 @@
1 (function(){
2 const form = document.querySelector('#chat-form');
3 let mxMsg = 0;
4 // let _me = "%string($me)";
 
5 form.addEventListener('submit',(e)=>{
6 e.preventDefault();
7 if( form.msg.value.length>0 || form.file.value.length>0 ){
8 fetch("%string($submiturl)",{
9 method: 'POST',
10 body: new FormData(form)
11 });
12 }
13 form.msg.value = "";
14 form.file.value = "";
15 form.msg.focus();
16 });
17 const rxUrl = /\b(?:https?|ftp):\/\/\[a-z0-9-+&@\#\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\/%=~_|]/gim;
18 const rxAtName = /@\w+/gmi;
19 // ^^^ achtung, extra backslashes needed for the outer TCL.
20 const textNode = (T)=>document.createTextNode(T);
21
22 // Converts a message string to a message-containing DOM element
23 // and returns that element, which may contain child elements.
24 // If 2nd arg is passed, it must be a DOM element to which all
25 // child elements are appended.
26 const messageToDOM = function f(str, tgtElem){
27 "use strict";
28 if(!f.rxUrl){
29 f.rxUrl = rxUrl;
30 f.rxAt = rxAtName;
31 f.rxNS = /\S/;
32 f.ce = (T)=>document.createElement(T);
33 f.ct = (T)=>document.createTextNode(T);
34 f.replaceUrls = function ff(sub, offset, whole){
35 if(offset > ff.prevStart){
36 f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
37 }
38 const a = f.ce('a');
39 a.setAttribute('href',sub);
40 a.setAttribute('target','_blank');
41 a.appendChild(f.ct(sub));
42 f.accum.push(a);
43 ff.prevStart = offset + sub.length + 1;
44 };
45 f.replaceAtName = function ff(sub, offset,whole){
46 if(offset > ff.prevStart){
47 ff.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
48 }else if(offset && f.rxNS.test(whole[offset-1])){
49 // Sigh: https://stackoverflow.com/questions/52655367
50 ff.accum.push(sub);
51 return;
52 }
53 const e = f.ce('span');
54 e.classList.add('at-name');
55 e.appendChild(f.ct(sub));
56 ff.accum.push(e);
57 ff.prevStart = offset + sub.length + 1;
58 };
59 }
60 f.accum = []; // accumulate strings and DOM elements here.
61 f.rxUrl.lastIndex = f.replaceUrls.prevStart = 0; // reset regex cursor
62 str.replace(f.rxUrl, f.replaceUrls);
63 // Push remaining non-URL part of the string to the queue...
64 if(f.replaceUrls.prevStart < str.length){
65 f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
66 }
67 // Pass 2: process @NAME references...
68 // TODO: only match NAME if it's the name of a currently participating
69 // user. Add a second class if NAME == current user, and style that one
70 // differently so that people can more easily see when they're spoken to.
71 const accum2 = f.replaceAtName.accum = [];
72 //console.debug("f.accum =",f.accum);
73 f.accum.forEach(function(v){
74 //console.debug("v =",v);
75 if('string'===typeof v){
76 f.rxAt.lastIndex = f.replaceAtName.prevStart = 0;
77 v.replace(f.rxAt, f.replaceAtName);
78 if(f.replaceAtName.prevStart < v.length){
79 accum2.push((f.replaceAtName.prevStart?' ':'')+v.substring(f.replaceAtName.prevStart));
80 }
81 }else{
82 accum2.push(v);
83 }
84 //console.debug("accum2 =",accum2);
85 });
86 delete f.accum;
87 //console.debug("accum2 =",accum2);
88 const span = tgtElem || f.ce('span');
89 accum2.forEach(function(e){
90 if('string'===typeof e) e = f.ct(e);
91 span.appendChild(e);
92 });
93 //console.debug("span =",span.innerHTML);
94 return span;
95 }/*end messageToDOM()*/;
96 /* Injects element e as a new row in the chat, at the top of the list */
97 const injectMessage = function f(e){
98 if(!f.injectPoint){
99 f.injectPoint = document.querySelector('#message-inject-point');
100 }
@@ -159,11 +81,11 @@
159 let br = document.createElement("br");
160 br.style.clear = "both";
161 span.appendChild(br);
162 }
163 if(m.xmsg){
164 messageToDOM(m.xmsg, span);
165 }
166 span.classList.add('chat-message');
167 if( m.xfrom!=_me ){
168 span.classList.add('chat-mx');
169 }else{
@@ -172,12 +94,12 @@
172 }
173 }
174 async function poll(){
175 if(poll.running) return;
176 poll.running = true;
177 fetch("%string($pollurl)/" + mxMsg)
178 .then(x=>x.json())
179 .then(y=>newcontent(y))
180 .finally(()=>poll.running=false)
181 }
182 setInterval(poll, 1000);
183 })();</script>
184
--- src/chat.js
+++ src/chat.js
@@ -1,100 +1,22 @@
1 (function(){
2 const form = document.querySelector('#chat-form');
3 let mxMsg = 0;
4 // let _me = "%string($me)";
5 let me = "drh"; // FIX ME
6 form.addEventListener('submit',(e)=>{
7 e.preventDefault();
8 if( form.msg.value.length>0 || form.file.value.length>0 ){
9 fetch("chat-send",{
10 method: 'POST',
11 body: new FormData(form)
12 });
13 }
14 form.msg.value = "";
15 form.file.value = "";
16 form.msg.focus();
17 });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18 /* Injects element e as a new row in the chat, at the top of the list */
19 const injectMessage = function f(e){
20 if(!f.injectPoint){
21 f.injectPoint = document.querySelector('#message-inject-point');
22 }
@@ -159,11 +81,11 @@
81 let br = document.createElement("br");
82 br.style.clear = "both";
83 span.appendChild(br);
84 }
85 if(m.xmsg){
86 span.innerHTML += m.xmsg;
87 }
88 span.classList.add('chat-message');
89 if( m.xfrom!=_me ){
90 span.classList.add('chat-mx');
91 }else{
@@ -172,12 +94,12 @@
94 }
95 }
96 async function poll(){
97 if(poll.running) return;
98 poll.running = true;
99 fetch("chat-poll/" + mxMsg)
100 .then(x=>x.json())
101 .then(y=>newcontent(y))
102 .finally(()=>poll.running=false)
103 }
104 // setInterval(poll, 1000);
105 })();
106
+23 -4
--- src/dispatch.c
+++ src/dispatch.c
@@ -349,10 +349,12 @@
349349
** followed by two spaces and a non-space. <dd> elements can begin
350350
** on the same line as long as they are separated by at least
351351
** two spaces.
352352
**
353353
** * Indented text is show verbatim (<pre>...</pre>)
354
+**
355
+** * Lines that begin with "|" at the left margin are in <pre>...</pre>
354356
*/
355357
static void help_to_html(const char *zHelp, Blob *pHtml){
356358
int i;
357359
char c;
358360
int nIndent = 0;
@@ -361,10 +363,11 @@
361363
int aIndent[10];
362364
const char *azEnd[10];
363365
int iLevel = 0;
364366
int isLI = 0;
365367
int isDT = 0;
368
+ int inPRE = 0;
366369
static const char *zEndDL = "</dl></blockquote>";
367370
static const char *zEndPRE = "</pre></blockquote>";
368371
static const char *zEndUL = "</ul>";
369372
static const char *zEndDD = "</dd>";
370373
@@ -380,16 +383,32 @@
380383
wantBR = 1;
381384
continue;
382385
}
383386
i++;
384387
}
385
- if( i>2 && zHelp[0]=='>' && zHelp[1]==' ' ){
386
- isDT = 1;
387
- for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
388
+ if( i>2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){
389
+ if( zHelp[0]=='>' ){
390
+ isDT = 1;
391
+ for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
392
+ }else{
393
+ if( !inPRE ){
394
+ blob_append(pHtml, "<pre>\n", -1);
395
+ inPRE = 1;
396
+ }
397
+ }
388398
}else{
399
+ if( inPRE ){
400
+ blob_append(pHtml, "</pre>\n", -1);
401
+ inPRE = 0;
402
+ }
389403
isDT = 0;
390404
for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
405
+ }
406
+ if( inPRE ){
407
+ blob_append(pHtml, zHelp+1, i);
408
+ zHelp += i + 1;
409
+ continue;
391410
}
392411
if( nIndent==i ){
393412
if( c==0 ) break;
394413
if( iLevel && azEnd[iLevel]==zEndPRE ){
395414
/* Skip the newline at the end of a <pre> */
@@ -491,11 +510,11 @@
491510
blob_append(pText, "fossil", 6);
492511
zHelp += i+7;
493512
i = -1;
494513
continue;
495514
}
496
- if( c=='\n' && strncmp(zHelp+i+1,"> ",2)==0 ){
515
+ if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){
497516
blob_append(pText, zHelp, i+1);
498517
blob_append(pText, " ", 1);
499518
zHelp += i+2;
500519
i = -1;
501520
continue;
502521
--- src/dispatch.c
+++ src/dispatch.c
@@ -349,10 +349,12 @@
349 ** followed by two spaces and a non-space. <dd> elements can begin
350 ** on the same line as long as they are separated by at least
351 ** two spaces.
352 **
353 ** * Indented text is show verbatim (<pre>...</pre>)
 
 
354 */
355 static void help_to_html(const char *zHelp, Blob *pHtml){
356 int i;
357 char c;
358 int nIndent = 0;
@@ -361,10 +363,11 @@
361 int aIndent[10];
362 const char *azEnd[10];
363 int iLevel = 0;
364 int isLI = 0;
365 int isDT = 0;
 
366 static const char *zEndDL = "</dl></blockquote>";
367 static const char *zEndPRE = "</pre></blockquote>";
368 static const char *zEndUL = "</ul>";
369 static const char *zEndDD = "</dd>";
370
@@ -380,16 +383,32 @@
380 wantBR = 1;
381 continue;
382 }
383 i++;
384 }
385 if( i>2 && zHelp[0]=='>' && zHelp[1]==' ' ){
386 isDT = 1;
387 for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
 
 
 
 
 
 
 
388 }else{
 
 
 
 
389 isDT = 0;
390 for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
 
 
 
 
 
391 }
392 if( nIndent==i ){
393 if( c==0 ) break;
394 if( iLevel && azEnd[iLevel]==zEndPRE ){
395 /* Skip the newline at the end of a <pre> */
@@ -491,11 +510,11 @@
491 blob_append(pText, "fossil", 6);
492 zHelp += i+7;
493 i = -1;
494 continue;
495 }
496 if( c=='\n' && strncmp(zHelp+i+1,"> ",2)==0 ){
497 blob_append(pText, zHelp, i+1);
498 blob_append(pText, " ", 1);
499 zHelp += i+2;
500 i = -1;
501 continue;
502
--- src/dispatch.c
+++ src/dispatch.c
@@ -349,10 +349,12 @@
349 ** followed by two spaces and a non-space. <dd> elements can begin
350 ** on the same line as long as they are separated by at least
351 ** two spaces.
352 **
353 ** * Indented text is show verbatim (<pre>...</pre>)
354 **
355 ** * Lines that begin with "|" at the left margin are in <pre>...</pre>
356 */
357 static void help_to_html(const char *zHelp, Blob *pHtml){
358 int i;
359 char c;
360 int nIndent = 0;
@@ -361,10 +363,11 @@
363 int aIndent[10];
364 const char *azEnd[10];
365 int iLevel = 0;
366 int isLI = 0;
367 int isDT = 0;
368 int inPRE = 0;
369 static const char *zEndDL = "</dl></blockquote>";
370 static const char *zEndPRE = "</pre></blockquote>";
371 static const char *zEndUL = "</ul>";
372 static const char *zEndDD = "</dd>";
373
@@ -380,16 +383,32 @@
383 wantBR = 1;
384 continue;
385 }
386 i++;
387 }
388 if( i>2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){
389 if( zHelp[0]=='>' ){
390 isDT = 1;
391 for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
392 }else{
393 if( !inPRE ){
394 blob_append(pHtml, "<pre>\n", -1);
395 inPRE = 1;
396 }
397 }
398 }else{
399 if( inPRE ){
400 blob_append(pHtml, "</pre>\n", -1);
401 inPRE = 0;
402 }
403 isDT = 0;
404 for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
405 }
406 if( inPRE ){
407 blob_append(pHtml, zHelp+1, i);
408 zHelp += i + 1;
409 continue;
410 }
411 if( nIndent==i ){
412 if( c==0 ) break;
413 if( iLevel && azEnd[iLevel]==zEndPRE ){
414 /* Skip the newline at the end of a <pre> */
@@ -491,11 +510,11 @@
510 blob_append(pText, "fossil", 6);
511 zHelp += i+7;
512 i = -1;
513 continue;
514 }
515 if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){
516 blob_append(pText, zHelp, i+1);
517 blob_append(pText, " ", 1);
518 zHelp += i+2;
519 i = -1;
520 continue;
521
--- src/timeline.c
+++ src/timeline.c
@@ -122,10 +122,13 @@
122122
#define TIMELINE_DELTA 0x10000000 /* Background color shows delta manifests */
123123
#endif
124124
125125
/*
126126
** Hash a string and use the hash to determine a background color.
127
+**
128
+** This value returned is in static space and is overwritten with
129
+** each subsequent call.
127130
*/
128131
char *hash_color(const char *z){
129132
int i; /* Loop counter */
130133
unsigned int h = 0; /* Hash on the branch name */
131134
int r, g, b; /* Values for red, green, and blue */
132135
--- src/timeline.c
+++ src/timeline.c
@@ -122,10 +122,13 @@
122 #define TIMELINE_DELTA 0x10000000 /* Background color shows delta manifests */
123 #endif
124
125 /*
126 ** Hash a string and use the hash to determine a background color.
 
 
 
127 */
128 char *hash_color(const char *z){
129 int i; /* Loop counter */
130 unsigned int h = 0; /* Hash on the branch name */
131 int r, g, b; /* Values for red, green, and blue */
132
--- src/timeline.c
+++ src/timeline.c
@@ -122,10 +122,13 @@
122 #define TIMELINE_DELTA 0x10000000 /* Background color shows delta manifests */
123 #endif
124
125 /*
126 ** Hash a string and use the hash to determine a background color.
127 **
128 ** This value returned is in static space and is overwritten with
129 ** each subsequent call.
130 */
131 char *hash_color(const char *z){
132 int i; /* Loop counter */
133 unsigned int h = 0; /* Hash on the branch name */
134 int r, g, b; /* Values for red, green, and blue */
135

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button