@@ -1,11 +1,10 @@
1 1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/**
2 2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
This file contains the client-side implementation of fossil's /chat
3 3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
application.
4 4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
*/
5 5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
(function(){
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- const form = document.querySelector('#chat-form');
7 6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const F = window.fossil, D = F.dom;
8 7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const E1 = function(selector){
9 8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const e = document.querySelector(selector);
10 9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(!e) throw new Error("missing required DOM element: "+selector);
11 10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return e;
@@ -15,11 +14,14 @@
15 14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
e:{/*map of certain DOM elements.*/
16 15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
messageInjectPoint: E1('#message-inject-point'),
17 16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
pageTitle: E1('head title'),
18 17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
19 18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
inputWrapper: E1("#chat-input-area"),
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- messagesWrapper: E1('#chat-messages-wrapper')
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ messagesWrapper: E1('#chat-messages-wrapper'),
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ inputForm: E1('#chat-form'),
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ inputSingle: E1('#chat-input-single'),
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ inputFile: E1('#chat-input-file')
21 23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
},
22 24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
me: F.user.name,
23 25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
24 26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
25 27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
pageIsActive: 'visible'===document.visibilityState,
@@ -32,10 +34,27 @@
32 34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
that 'right' is conventional for mobile chat apps but can be
33 35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
difficult to read in wide windows (desktop/tablet landscape
34 36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
mode). Can be toggled via settings popup. */
35 37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
36 38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
ajaxInflight: 0,
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ /** Gets (no args) or sets (1 arg) the current input text field value,
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ taking into account single- vs multi-line input. The getter returns
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ a string and the setter returns this object. */
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ inputValue: function(){
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ const e = this.e.inputSingle;
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if(arguments.length){
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ e.value = arguments[0];
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return this;
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }else {
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return e.value;
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ /** Asks the current user input field to take focus. Returns this. */
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ inputFocus: function(){
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ this.e.inputSingle.focus();
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return this;
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
37 56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/** Enables (if yes is truthy) or disables all elements in
38 57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
* this.disableDuringAjax. */
39 58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
enableAjaxComponents: function(yes){
40 59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
41 60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return this;
@@ -188,11 +207,11 @@
188 207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
dropDetails: document.querySelector('#chat-drop-details'),
189 208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
blob: undefined,
190 209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
clear: function(){
191 210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
this.blob = undefined;
192 211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.clearElement(this.dropDetails);
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.file.value = "";
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.inputFile.value = "";
194 213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
195 214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
};
196 215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/** Updates the paste/drop zone with details of the pasted/dropped
197 216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
data. The argument must be a Blob or Blob-like object (File) or
198 217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
it can be falsy to reset/clear that state.*/
@@ -199,11 +218,11 @@
199 218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const updateDropZoneContent = function(blob){
200 219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const dd = bxs.dropDetails;
201 220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
bxs.blob = blob;
202 221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.clearElement(dd);
203 222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(!blob){
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.file.value = '';
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.inputFile.value = '';
205 224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return;
206 225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
207 226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.append(dd, "Name: ", blob.name,
208 227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.br(), "Size: ",blob.size);
209 228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(blob.type && blob.type.startsWith("image/")){
@@ -215,11 +234,11 @@
215 234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
216 235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const btn = D.button("Cancel");
217 236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.append(dd, D.br(), btn);
218 237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
btn.addEventListener('click', ()=>updateDropZoneContent(), false);
219 238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
};
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.file.addEventListener('change', function(ev){
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.inputFile.addEventListener('change', function(ev){
221 240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
222 241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
});
223 242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/* Handle image paste from clipboard. TODO: figure out how we can
224 243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
paste non-image binary data as if it had been selected via the
225 244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
file selection element. */
@@ -228,32 +247,21 @@
228 247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
item = items[0];
229 248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(!item || !item.type) return;
230 249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
else if('file'===item.kind){
231 250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
updateDropZoneContent(false/*clear prev state*/);
232 251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
updateDropZoneContent(items[0].getAsFile());
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }else if(false && 'string'===item.kind){
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- /* ----^^^^^ disabled for now: the intent here is that if
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.msg is not active, populate it with this text, but
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- whether populating it from ctrl-v when it does not have focus
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- is a feature or a bug is debatable. It seems useful but may
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- violate the Principle of Least Surprise. */
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if(document.activeElement !== form.msg){
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- /* Overwrite input field if it DOES NOT have focus,
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- otherwise let it do its own paste handling. */
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- item.getAsString((v)=>form.msg.value = v);
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
244 252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
245 253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}, false);
246 254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/* Add help button for drag/drop/paste zone */
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.file.parentNode.insertBefore(
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.inputFile.parentNode.insertBefore(
248 256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
F.helpButtonlets.create(
249 257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
document.querySelector('#chat-input-file-area .help-buttonlet')
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- ), form.file
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ), Chat.e.inputFile
251 259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
);
252 260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
////////////////////////////////////////////////////////////
253 261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// File drag/drop visual notification.
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- const dropHighlight = form.file /* target zone */;
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ const dropHighlight = Chat.e.inputFile /* target zone */;
255 263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
const dropEvents = {
256 264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
drop: function(ev){
257 265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.removeClass(dropHighlight, 'dragover');
258 266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
},
259 267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
dragenter: function(ev){
@@ -267,30 +275,31 @@
267 275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
dragend: function(ev){
268 276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
D.removeClass(dropHighlight, 'dragover');
269 277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
270 278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
};
271 279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Object.keys(dropEvents).forEach(
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- (k)=>form.file.addEventListener(k, dropEvents[k], true)
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
273 281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
);
274 282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return bxs;
275 283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})()/*drag/drop*/;
276 284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.addEventListener('submit',(e)=>{
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.inputForm.addEventListener('submit',(e)=>{
278 286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
e.preventDefault();
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- const fd = new FormData(form);
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ const fd = new FormData(Chat.e.inputForm);
280 288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(BlobXferState.blob/*replace file content with this*/){
281 289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
fd.set("file", BlobXferState.blob);
282 290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if( form.msg.value.length>0 || form.file.value.length>0 || BlobXferState.blob ){
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if( !!Chat.inputValue()
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ || Chat.e.inputFile.value.length>0
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ || BlobXferState.blob ){
284 294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
fetch("chat-send",{
285 295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
method: 'POST',
286 296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
body: fd
287 297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
});
288 298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
289 299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
BlobXferState.clear();
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.msg.value = "";
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- form.msg.focus();
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.inputValue("").inputFocus();
292 301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
});
293 302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
294 303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/* Returns a new TEXT node with the given text content. */
295 304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
/** Returns the local time string of Date object d, defaulting
296 305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
to the current time. */
@@ -589,12 +598,14 @@
589 598 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Chat.changesSincePageHidden = 0;
590 599 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
591 600 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
592 601 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}else{
593 602 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Chat.changesSincePageHidden += jx.msgs.length;
594 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
595 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- Chat.pageTitleOrig;
603 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if(jx.msgs.length){
604 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
605 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Chat.pageTitleOrig;
606 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
596 607 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
597 608 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if(jx.msgs.length && F.config.chat.pingTcp){
598 609 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping");
599 610 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
600 611 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}/*newcontent()*/;
601 612 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!