15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
+
+
+
+
+
+
+
+
|
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
(function(){
let dbg = document.querySelector('#debugMsg');
if(dbg){
/* This can inadvertently influence our flexbox layouts, so move
it out of the way. */
D.append(document.body,dbg);
}
})();
const ForceResizeKludge = 0 ? function(){} : (function(){
/* Workaround for Safari mayhem regarding use of vh CSS units....
We tried to use vh units to set the content area size for the
chat layout, but Safari chokes on that, so we calculate that
height here: 85% when in "normal" mode and 95% in chat-only
mode. Larger than ~95% is too big for Firefox on Android,
causing the input area to move off-screen. */
|
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
|
},
disableDuringAjax: [
/* List of DOM elements disable while ajax traffic is in
transit. Must be populated before ajax starts. We do this
to avoid various race conditions in the UI and long-running
network requests. */
],
/** Either scrolls .message-widget element eMsg into view
immediately or, if it represents an inlined image, delays
the scroll until the image is loaded, at which point it will
scroll to either the newest message, if one is set or to
eMsg (the liklihood is good, at least on initial page load,
that the the image won't be loaded until other messages have
been injected). */
scheduleScrollOfMsg: function(eMsg){
if(1===+eMsg.dataset.hasImage){
console.debug("Delaying scroll for IMG.");
eMsg.querySelector('img').addEventListener(
'load', ()=>(this.e.newestMessage || eMsg).scrollIntoView()
);
}else{
eMsg.scrollIntoView();
}
return this;
},
/* Injects element e as a new row in the chat, at the top of the
list if atEnd is falsy, else at the end of the list, before
the load-history widget. */
injectMessageElem: function f(e, atEnd){
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
holder = this.e.messagesWrapper;
holder = this.e.messagesWrapper,
prevMessage = this.e.newestMessage;
if(atEnd){
const fe = mip.nextElementSibling;
if(fe) mip.parentNode.insertBefore(e, fe);
else D.append(mip.parentNode, e);
}else{
D.append(holder,e);
Chat.newestMessageElem = e;
this.e.newestMessage = e;
}
if(!atEnd && !this.isMassLoading
&& e.dataset.xfrom!==Chat.me && !isInViewport(e)){
&& e.dataset.xfrom!==this.me && !isInViewport(e)){
/* If a new non-history message arrives while the user is
scrolled elsewhere, do not scroll to the latest
message, but gently alert the user that a new message
has arrived. */
F.toast.message("New message has arrived.");
}else if(e.dataset.xfrom===Chat.me){
e.scrollIntoView();
}else if(!this.isMassLoading && e.dataset.xfrom===Chat.me){
this.scheduleScrollOfMsg(e);
}else if(!this.isMassLoading){
/* When a message from someone else arrives, we have to
figure out whether or not to scroll it into view. Ideally
we'd just stuff it in the UI and let the flexbox layout
DTRT, but Safari has expressed, in no uncertain terms,
some disappointment with that approach, so we'll
heuristicize it: if the previous last message is in view,
assume the user is at or near the input element and
scroll this one into view. If that message is NOT in
view, assume the user is up reading history somewhere and
do NOT scroll because doing so would interrupt
them. There are middle grounds here where the user will
experience a slight UI jolt, but this heuristic mostly
seems to work out okay. If there was no previous message,
assume we don't have any messages yet and go ahead and
scroll this message into view (noting that that scrolling
is hypothetically a no-op in such cases).
The wrench in these works are posts with IMG tags, as
those images are loaded async and the element does not
yet have enough information to know how far to scroll!
For such cases we have to delay the scroll until the
image loads (and we hope it does so before another
message arrives).
*/
if(1===+e.dataset.hasImage){
e.querySelector('img').addEventListener('load',()=>e.scrollIntoView());
}else if(!prevMessage || (prevMessage && isInViewport(prevMessage))){
e.scrollIntoView();
}
}
},
/** Returns true if chat-only mode is enabled. */
isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
/**
Enters (if passed a truthy value or no arguments) or leaves
"chat-only" mode. That mode hides the page's header and
footer, leaving only the chat application visible to the
user.
*/
chatOnlyMode: function f(yes){
if(undefined === f.elemsToToggle){
f.elemsToToggle = [];
document.querySelectorAll(
["body > div.header",
"body > div.header, body > div.mainmenu, body > div.footer"
"body > div.mainmenu",
"body > div.footer",
"#debugMsg"
].join(',')
).forEach((e)=>f.elemsToToggle.push(e));
}
if(!arguments.length) yes = true;
if(yes === this.isChatOnlyMode()) return this;
if(yes){
D.addClass(f.elemsToToggle, 'hidden');
D.addClass(document.body, 'chat-only-mode');
|
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
|
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
|
+
+
-
+
+
+
-
-
+
+
|
return qs('[data-msgid="'+id+'"]');
};
/** Finds the last .message-widget element and returns it or
the undefined value if none are found. */
cs.fetchLastMessageElem = function(){
const msgs = document.querySelectorAll('.message-widget');
var rc;
if(msgs.length){
return msgs.length ? msgs[msgs.length-1] : undefined;
rc = this.e.newestMessage = msgs[msgs.length-1];
}
return rc;
};
/**
LOCALLY deletes a message element by the message ID or passing
the .message-row element. Returns true if it removes an element,
else false.
*/
cs.deleteMessageElem = function(id){
var e;
if(id instanceof HTMLElement){
e = id;
id = e.dataset.msgid;
}else{
e = this.getMessageElemById(id);
}
if(e && id){
D.remove(e);
if(e===this.newestMessageElem){
Chat.newestMessageElem = Chat.fetchLastMessageElem();
if(e===this.e.newestMessage){
this.fetchLastMessageElem();
}
F.toast.message("Deleted message "+id+".");
}
return !!e;
};
/** Given a .message-row element, this function returns whethe the
|
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
|
+
|
var contentTarget = this.e.content;
if( m.fsize>0 ){
if( m.fmime
&& m.fmime.startsWith("image/")
&& Chat.settings.getBool('images-inline',true)
){
contentTarget.appendChild(D.img("chat-download/" + m.msgid));
ds.hasImage = 1;
}else{
const a = D.a(
window.fossil.rootPath+
'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname),
// ^^^ add m.fname to URL to cause downloaded file to have that name.
"(" + m.fname + " " + m.fsize + " bytes)"
)
|
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
|
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
|
-
-
-
+
+
-
-
-
-
+
-
|
/* ^^^ we don't use Chat.reportError(e) here b/c the polling
fails exepectedly when it times out, but is then immediately
resumed, and reportError() produces a loud error message. */
.finally(function(){
if(isFirstCall){
Chat.isMassLoading = false;
Chat.ajaxEnd();
setTimeout(function(){
const m = Chat.newestMessageElem;
if(m){
const m = Chat.e.newestMessage;
if(m) Chat.scheduleScrollOfMsg(m);
m.scrollIntoView();
//console.debug("Scrolling into view...",msgs[msgs.length-1]);
}
Chat.e.inputWrapper.scrollIntoView()
setTimeout(()=>Chat.e.inputWrapper.scrollIntoView(), 0);
}, 0);
}
poll.running=false;
});
}
poll.running = false;
poll(true);
setInterval(poll, 1000);
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();
|