Fossil SCM

chat: initial implementation of marking @NAME references to make them more visible.

stephan 2020-12-22 09:05 chatroom-dev
Commit ee53e449a5086a6962a28be58dc67e5701b152851c9fc92daa77fa9dd4bcc428
1 file changed +55 -18
+55 -18
--- tools/chat.tcl
+++ tools/chat.tcl
@@ -102,10 +102,14 @@
102102
align-items: center;
103103
}
104104
\#chat-input-file > input {
105105
flex: 1 0 auto;
106106
}
107
+span.at-name { /* for @USERNAME references */
108
+ text-decoration: underline;
109
+ font-weight: bold;
110
+}
107111
</style>
108112
}
109113
set nonce [wapp-param FOSSIL_NONCE]
110114
set submiturl [wapp-param SCRIPT_NAME]/send
111115
set pollurl [wapp-param SCRIPT_NAME]/poll
@@ -127,52 +131,85 @@
127131
}
128132
form.msg.value = "";
129133
form.file.value = "";
130134
form.msg.focus();
131135
});
136
+ const rxUrl = /\\b(?:https?|ftp):\\/\\/\[a-z0-9-+&@\#\\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\\/%=~_|]/gim;
137
+ const rxAtName = /@\\w+/gmi;
138
+ // ^^^ achtung, extra backslashes needed for the outer TCL.
132139
// Converts a message string to a message-containing DOM element
133140
// and returns that element, which may contain child elements.
134141
const messageToDOM = function f(str){
135142
"use strict";
136143
if(!f.rxUrl){
137
- f.rxUrl = /\\b(?:https?|ftp):\\/\\/\[a-z0-9-+&@\#\\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\\/%=~_|]/gim;
138
- // ^^^ achtung, extra backslashes needed for the outer TCL.
144
+ f.rxUrl = rxUrl;
145
+ f.rxAt = rxAtName;
146
+ f.rxNS = /\\S/;
139147
f.ce = (T)=>document.createElement(T);
140148
f.ct = (T)=>document.createTextNode(T);
141
- f.replaceUrls = function ff(sub, off, whole){
142
- if(off > ff.prevStart){
143
- f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, off-1)+' ');
149
+ f.replaceUrls = function ff(sub, offset, whole){
150
+ if(offset > ff.prevStart){
151
+ f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
144152
}
145153
const a = f.ce('a');
146154
a.setAttribute('href',sub);
147155
a.setAttribute('target','_blank');
148156
a.appendChild(f.ct(sub));
149157
f.accum.push(a);
150
- ff.prevStart = off + sub.length + 1;
151
- return sub;
158
+ ff.prevStart = offset + sub.length + 1;
159
+ };
160
+ f.replaceAtName = function ff(sub, offset,whole){
161
+ if(offset > ff.prevStart){
162
+ ff.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
163
+ }else if(offset && f.rxNS.test(whole[offset-1])){
164
+ // Sigh: https://stackoverflow.com/questions/52655367
165
+ ff.accum.push(sub);
166
+ return;
167
+ }
168
+ const e = f.ce('span');
169
+ e.classList.add('at-name');
170
+ e.appendChild(f.ct(sub));
171
+ ff.accum.push(e);
172
+ ff.prevStart = offset + sub.length + 1;
152173
};
153174
}
154175
f.accum = []; // accumulate strings and DOM elements here.
155
- f.rxUrl.lastIndex = 0; // reset regex cursor
156
- f.replaceUrls.prevStart = 0;
176
+ f.rxUrl.lastIndex = f.replaceUrls.prevStart = 0; // reset regex cursor
157177
str.replace(f.rxUrl, f.replaceUrls);
178
+ // Push remaining non-URL part of the string to the queue...
158179
if(f.replaceUrls.prevStart < str.length){
159180
f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
160181
}
182
+ // Pass 2: process @NAME references...
183
+ // TODO: only match NAME if it's the name of a currently participating
184
+ // user. Add a second class if NAME == current user, and style that one
185
+ // differently so that people can more easily see when they're spoken to.
186
+ const accum2 = f.replaceAtName.accum = [];
187
+ //console.debug("f.accum =",f.accum);
188
+ f.accum.forEach(function(v){
189
+ //console.debug("v =",v);
190
+ if('string'===typeof v){
191
+ f.rxAt.lastIndex = f.replaceAtName.prevStart = 0;
192
+ v.replace(f.rxAt, f.replaceAtName);
193
+ if(f.replaceAtName.prevStart < v.length){
194
+ accum2.push((f.replaceAtName.prevStart?' ':'')+v.substring(f.replaceAtName.prevStart));
195
+ }
196
+ }else{
197
+ accum2.push(v);
198
+ }
199
+ //console.debug("accum2 =",accum2);
200
+ });
201
+ delete f.accum;
202
+ //console.debug("accum2 =",accum2);
161203
const span = f.ce('span');
162
- f.accum.forEach(function(e){
163
- // append accumulated strings/DOM elements to target element
204
+ accum2.forEach(function(e){
164205
if('string'===typeof e) e = f.ct(e);
165206
span.appendChild(e);
166207
});
167
- delete f.accum;
168
- // TODO: replace @WORD refs with <span class='at-me'>@WORD</span>, but
169
- // only when WORD==current user name. That requires a separate pass
170
- // over the remaining STRING entries and a separate array to accumulate
171
- // the results to.
172
- return span;
173
- };
208
+ //console.debug("span =",span.innerHTML);
209
+ return span;
210
+ }/*end messageToDOM()*/;
174211
function newcontent(jx){
175212
var tab = document.getElementById("dialog");
176213
var i;
177214
for(i=0; i<jx.msgs.length; ++i){
178215
let m = jx.msgs[i];
179216
--- tools/chat.tcl
+++ tools/chat.tcl
@@ -102,10 +102,14 @@
102 align-items: center;
103 }
104 \#chat-input-file > input {
105 flex: 1 0 auto;
106 }
 
 
 
 
107 </style>
108 }
109 set nonce [wapp-param FOSSIL_NONCE]
110 set submiturl [wapp-param SCRIPT_NAME]/send
111 set pollurl [wapp-param SCRIPT_NAME]/poll
@@ -127,52 +131,85 @@
127 }
128 form.msg.value = "";
129 form.file.value = "";
130 form.msg.focus();
131 });
 
 
 
132 // Converts a message string to a message-containing DOM element
133 // and returns that element, which may contain child elements.
134 const messageToDOM = function f(str){
135 "use strict";
136 if(!f.rxUrl){
137 f.rxUrl = /\\b(?:https?|ftp):\\/\\/\[a-z0-9-+&@\#\\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\\/%=~_|]/gim;
138 // ^^^ achtung, extra backslashes needed for the outer TCL.
 
139 f.ce = (T)=>document.createElement(T);
140 f.ct = (T)=>document.createTextNode(T);
141 f.replaceUrls = function ff(sub, off, whole){
142 if(off > ff.prevStart){
143 f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, off-1)+' ');
144 }
145 const a = f.ce('a');
146 a.setAttribute('href',sub);
147 a.setAttribute('target','_blank');
148 a.appendChild(f.ct(sub));
149 f.accum.push(a);
150 ff.prevStart = off + sub.length + 1;
151 return sub;
 
 
 
 
 
 
 
 
 
 
 
 
 
152 };
153 }
154 f.accum = []; // accumulate strings and DOM elements here.
155 f.rxUrl.lastIndex = 0; // reset regex cursor
156 f.replaceUrls.prevStart = 0;
157 str.replace(f.rxUrl, f.replaceUrls);
 
158 if(f.replaceUrls.prevStart < str.length){
159 f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
160 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161 const span = f.ce('span');
162 f.accum.forEach(function(e){
163 // append accumulated strings/DOM elements to target element
164 if('string'===typeof e) e = f.ct(e);
165 span.appendChild(e);
166 });
167 delete f.accum;
168 // TODO: replace @WORD refs with <span class='at-me'>@WORD</span>, but
169 // only when WORD==current user name. That requires a separate pass
170 // over the remaining STRING entries and a separate array to accumulate
171 // the results to.
172 return span;
173 };
174 function newcontent(jx){
175 var tab = document.getElementById("dialog");
176 var i;
177 for(i=0; i<jx.msgs.length; ++i){
178 let m = jx.msgs[i];
179
--- tools/chat.tcl
+++ tools/chat.tcl
@@ -102,10 +102,14 @@
102 align-items: center;
103 }
104 \#chat-input-file > input {
105 flex: 1 0 auto;
106 }
107 span.at-name { /* for @USERNAME references */
108 text-decoration: underline;
109 font-weight: bold;
110 }
111 </style>
112 }
113 set nonce [wapp-param FOSSIL_NONCE]
114 set submiturl [wapp-param SCRIPT_NAME]/send
115 set pollurl [wapp-param SCRIPT_NAME]/poll
@@ -127,52 +131,85 @@
131 }
132 form.msg.value = "";
133 form.file.value = "";
134 form.msg.focus();
135 });
136 const rxUrl = /\\b(?:https?|ftp):\\/\\/\[a-z0-9-+&@\#\\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\\/%=~_|]/gim;
137 const rxAtName = /@\\w+/gmi;
138 // ^^^ achtung, extra backslashes needed for the outer TCL.
139 // Converts a message string to a message-containing DOM element
140 // and returns that element, which may contain child elements.
141 const messageToDOM = function f(str){
142 "use strict";
143 if(!f.rxUrl){
144 f.rxUrl = rxUrl;
145 f.rxAt = rxAtName;
146 f.rxNS = /\\S/;
147 f.ce = (T)=>document.createElement(T);
148 f.ct = (T)=>document.createTextNode(T);
149 f.replaceUrls = function ff(sub, offset, whole){
150 if(offset > ff.prevStart){
151 f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
152 }
153 const a = f.ce('a');
154 a.setAttribute('href',sub);
155 a.setAttribute('target','_blank');
156 a.appendChild(f.ct(sub));
157 f.accum.push(a);
158 ff.prevStart = offset + sub.length + 1;
159 };
160 f.replaceAtName = function ff(sub, offset,whole){
161 if(offset > ff.prevStart){
162 ff.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
163 }else if(offset && f.rxNS.test(whole[offset-1])){
164 // Sigh: https://stackoverflow.com/questions/52655367
165 ff.accum.push(sub);
166 return;
167 }
168 const e = f.ce('span');
169 e.classList.add('at-name');
170 e.appendChild(f.ct(sub));
171 ff.accum.push(e);
172 ff.prevStart = offset + sub.length + 1;
173 };
174 }
175 f.accum = []; // accumulate strings and DOM elements here.
176 f.rxUrl.lastIndex = f.replaceUrls.prevStart = 0; // reset regex cursor
 
177 str.replace(f.rxUrl, f.replaceUrls);
178 // Push remaining non-URL part of the string to the queue...
179 if(f.replaceUrls.prevStart < str.length){
180 f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
181 }
182 // Pass 2: process @NAME references...
183 // TODO: only match NAME if it's the name of a currently participating
184 // user. Add a second class if NAME == current user, and style that one
185 // differently so that people can more easily see when they're spoken to.
186 const accum2 = f.replaceAtName.accum = [];
187 //console.debug("f.accum =",f.accum);
188 f.accum.forEach(function(v){
189 //console.debug("v =",v);
190 if('string'===typeof v){
191 f.rxAt.lastIndex = f.replaceAtName.prevStart = 0;
192 v.replace(f.rxAt, f.replaceAtName);
193 if(f.replaceAtName.prevStart < v.length){
194 accum2.push((f.replaceAtName.prevStart?' ':'')+v.substring(f.replaceAtName.prevStart));
195 }
196 }else{
197 accum2.push(v);
198 }
199 //console.debug("accum2 =",accum2);
200 });
201 delete f.accum;
202 //console.debug("accum2 =",accum2);
203 const span = f.ce('span');
204 accum2.forEach(function(e){
 
205 if('string'===typeof e) e = f.ct(e);
206 span.appendChild(e);
207 });
208 //console.debug("span =",span.innerHTML);
209 return span;
210 }/*end messageToDOM()*/;
 
 
 
 
211 function newcontent(jx){
212 var tab = document.getElementById("dialog");
213 var i;
214 for(i=0; i<jx.msgs.length; ++i){
215 let m = jx.msgs[i];
216

Keyboard Shortcuts

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