Fossil SCM

chat internal cleanups in prep for upcoming changes. Possibly fixed the cosmetic bug where the titlebar says '(0) ...' after receiving an empty list of messages in response to an auto-reconnect after a timeout.

stephan 2020-12-25 16:08 trunk
Commit e52d0fd5ce1cd608600fadecaaf5fa2bac02593b6c2f2dbec35b16c1c2d06ef8
3 files changed +1 -1 +38 -27 +10 -2
+1 -1
--- src/chat.c
+++ src/chat.c
@@ -101,11 +101,11 @@
101101
style_set_current_feature("chat");
102102
style_header("Chat");
103103
@ <div id='chat-input-area'>
104104
@ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
105105
@ <div id='chat-input-line'>
106
- @ <input type="text" name="msg" id="sbox" \
106
+ @ <input type="text" name="msg" id="chat-input-single" \
107107
@ placeholder="Type message here.">
108108
@ <input type="submit" value="Send">
109109
@ <span id="chat-settings-button" class="settings-icon"></span>
110110
@ </div>
111111
@ <div id='chat-input-file-area'>
112112
--- src/chat.c
+++ src/chat.c
@@ -101,11 +101,11 @@
101 style_set_current_feature("chat");
102 style_header("Chat");
103 @ <div id='chat-input-area'>
104 @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
105 @ <div id='chat-input-line'>
106 @ <input type="text" name="msg" id="sbox" \
107 @ placeholder="Type message here.">
108 @ <input type="submit" value="Send">
109 @ <span id="chat-settings-button" class="settings-icon"></span>
110 @ </div>
111 @ <div id='chat-input-file-area'>
112
--- src/chat.c
+++ src/chat.c
@@ -101,11 +101,11 @@
101 style_set_current_feature("chat");
102 style_header("Chat");
103 @ <div id='chat-input-area'>
104 @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
105 @ <div id='chat-input-line'>
106 @ <input type="text" name="msg" id="chat-input-single" \
107 @ placeholder="Type message here.">
108 @ <input type="submit" value="Send">
109 @ <span id="chat-settings-button" class="settings-icon"></span>
110 @ </div>
111 @ <div id='chat-input-file-area'>
112
+38 -27
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,10 @@
11
/**
22
This file contains the client-side implementation of fossil's /chat
33
application.
44
*/
55
(function(){
6
- const form = document.querySelector('#chat-form');
76
const F = window.fossil, D = F.dom;
87
const E1 = function(selector){
98
const e = document.querySelector(selector);
109
if(!e) throw new Error("missing required DOM element: "+selector);
1110
return e;
@@ -15,11 +14,14 @@
1514
e:{/*map of certain DOM elements.*/
1615
messageInjectPoint: E1('#message-inject-point'),
1716
pageTitle: E1('head title'),
1817
loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
1918
inputWrapper: E1("#chat-input-area"),
20
- messagesWrapper: E1('#chat-messages-wrapper')
19
+ messagesWrapper: E1('#chat-messages-wrapper'),
20
+ inputForm: E1('#chat-form'),
21
+ inputSingle: E1('#chat-input-single'),
22
+ inputFile: E1('#chat-input-file')
2123
},
2224
me: F.user.name,
2325
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
2426
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
2527
pageIsActive: 'visible'===document.visibilityState,
@@ -32,10 +34,27 @@
3234
that 'right' is conventional for mobile chat apps but can be
3335
difficult to read in wide windows (desktop/tablet landscape
3436
mode). Can be toggled via settings popup. */
3537
msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
3638
ajaxInflight: 0,
39
+ /** Gets (no args) or sets (1 arg) the current input text field value,
40
+ taking into account single- vs multi-line input. The getter returns
41
+ a string and the setter returns this object. */
42
+ inputValue: function(){
43
+ const e = this.e.inputSingle;
44
+ if(arguments.length){
45
+ e.value = arguments[0];
46
+ return this;
47
+ }else {
48
+ return e.value;
49
+ }
50
+ },
51
+ /** Asks the current user input field to take focus. Returns this. */
52
+ inputFocus: function(){
53
+ this.e.inputSingle.focus();
54
+ return this;
55
+ },
3756
/** Enables (if yes is truthy) or disables all elements in
3857
* this.disableDuringAjax. */
3958
enableAjaxComponents: function(yes){
4059
D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
4160
return this;
@@ -188,11 +207,11 @@
188207
dropDetails: document.querySelector('#chat-drop-details'),
189208
blob: undefined,
190209
clear: function(){
191210
this.blob = undefined;
192211
D.clearElement(this.dropDetails);
193
- form.file.value = "";
212
+ Chat.e.inputFile.value = "";
194213
}
195214
};
196215
/** Updates the paste/drop zone with details of the pasted/dropped
197216
data. The argument must be a Blob or Blob-like object (File) or
198217
it can be falsy to reset/clear that state.*/
@@ -199,11 +218,11 @@
199218
const updateDropZoneContent = function(blob){
200219
const dd = bxs.dropDetails;
201220
bxs.blob = blob;
202221
D.clearElement(dd);
203222
if(!blob){
204
- form.file.value = '';
223
+ Chat.e.inputFile.value = '';
205224
return;
206225
}
207226
D.append(dd, "Name: ", blob.name,
208227
D.br(), "Size: ",blob.size);
209228
if(blob.type && blob.type.startsWith("image/")){
@@ -215,11 +234,11 @@
215234
}
216235
const btn = D.button("Cancel");
217236
D.append(dd, D.br(), btn);
218237
btn.addEventListener('click', ()=>updateDropZoneContent(), false);
219238
};
220
- form.file.addEventListener('change', function(ev){
239
+ Chat.e.inputFile.addEventListener('change', function(ev){
221240
updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
222241
});
223242
/* Handle image paste from clipboard. TODO: figure out how we can
224243
paste non-image binary data as if it had been selected via the
225244
file selection element. */
@@ -228,32 +247,21 @@
228247
item = items[0];
229248
if(!item || !item.type) return;
230249
else if('file'===item.kind){
231250
updateDropZoneContent(false/*clear prev state*/);
232251
updateDropZoneContent(items[0].getAsFile());
233
- }else if(false && 'string'===item.kind){
234
- /* ----^^^^^ disabled for now: the intent here is that if
235
- form.msg is not active, populate it with this text, but
236
- whether populating it from ctrl-v when it does not have focus
237
- is a feature or a bug is debatable. It seems useful but may
238
- violate the Principle of Least Surprise. */
239
- if(document.activeElement !== form.msg){
240
- /* Overwrite input field if it DOES NOT have focus,
241
- otherwise let it do its own paste handling. */
242
- item.getAsString((v)=>form.msg.value = v);
243
- }
244252
}
245253
}, false);
246254
/* Add help button for drag/drop/paste zone */
247
- form.file.parentNode.insertBefore(
255
+ Chat.e.inputFile.parentNode.insertBefore(
248256
F.helpButtonlets.create(
249257
document.querySelector('#chat-input-file-area .help-buttonlet')
250
- ), form.file
258
+ ), Chat.e.inputFile
251259
);
252260
////////////////////////////////////////////////////////////
253261
// File drag/drop visual notification.
254
- const dropHighlight = form.file /* target zone */;
262
+ const dropHighlight = Chat.e.inputFile /* target zone */;
255263
const dropEvents = {
256264
drop: function(ev){
257265
D.removeClass(dropHighlight, 'dragover');
258266
},
259267
dragenter: function(ev){
@@ -267,30 +275,31 @@
267275
dragend: function(ev){
268276
D.removeClass(dropHighlight, 'dragover');
269277
}
270278
};
271279
Object.keys(dropEvents).forEach(
272
- (k)=>form.file.addEventListener(k, dropEvents[k], true)
280
+ (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
273281
);
274282
return bxs;
275283
})()/*drag/drop*/;
276284
277
- form.addEventListener('submit',(e)=>{
285
+ Chat.e.inputForm.addEventListener('submit',(e)=>{
278286
e.preventDefault();
279
- const fd = new FormData(form);
287
+ const fd = new FormData(Chat.e.inputForm);
280288
if(BlobXferState.blob/*replace file content with this*/){
281289
fd.set("file", BlobXferState.blob);
282290
}
283
- if( form.msg.value.length>0 || form.file.value.length>0 || BlobXferState.blob ){
291
+ if( !!Chat.inputValue()
292
+ || Chat.e.inputFile.value.length>0
293
+ || BlobXferState.blob ){
284294
fetch("chat-send",{
285295
method: 'POST',
286296
body: fd
287297
});
288298
}
289299
BlobXferState.clear();
290
- form.msg.value = "";
291
- form.msg.focus();
300
+ Chat.inputValue("").inputFocus();
292301
});
293302
294303
/* Returns a new TEXT node with the given text content. */
295304
/** Returns the local time string of Date object d, defaulting
296305
to the current time. */
@@ -589,12 +598,14 @@
589598
Chat.changesSincePageHidden = 0;
590599
Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
591600
}
592601
}else{
593602
Chat.changesSincePageHidden += jx.msgs.length;
594
- Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
595
- Chat.pageTitleOrig;
603
+ if(jx.msgs.length){
604
+ Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
605
+ Chat.pageTitleOrig;
606
+ }
596607
}
597608
if(jx.msgs.length && F.config.chat.pingTcp){
598609
fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping");
599610
}
600611
}/*newcontent()*/;
601612
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,10 @@
1 /**
2 This file contains the client-side implementation of fossil's /chat
3 application.
4 */
5 (function(){
6 const form = document.querySelector('#chat-form');
7 const F = window.fossil, D = F.dom;
8 const E1 = function(selector){
9 const e = document.querySelector(selector);
10 if(!e) throw new Error("missing required DOM element: "+selector);
11 return e;
@@ -15,11 +14,14 @@
15 e:{/*map of certain DOM elements.*/
16 messageInjectPoint: E1('#message-inject-point'),
17 pageTitle: E1('head title'),
18 loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
19 inputWrapper: E1("#chat-input-area"),
20 messagesWrapper: E1('#chat-messages-wrapper')
 
 
 
21 },
22 me: F.user.name,
23 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
24 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
25 pageIsActive: 'visible'===document.visibilityState,
@@ -32,10 +34,27 @@
32 that 'right' is conventional for mobile chat apps but can be
33 difficult to read in wide windows (desktop/tablet landscape
34 mode). Can be toggled via settings popup. */
35 msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
36 ajaxInflight: 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37 /** Enables (if yes is truthy) or disables all elements in
38 * this.disableDuringAjax. */
39 enableAjaxComponents: function(yes){
40 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
41 return this;
@@ -188,11 +207,11 @@
188 dropDetails: document.querySelector('#chat-drop-details'),
189 blob: undefined,
190 clear: function(){
191 this.blob = undefined;
192 D.clearElement(this.dropDetails);
193 form.file.value = "";
194 }
195 };
196 /** Updates the paste/drop zone with details of the pasted/dropped
197 data. The argument must be a Blob or Blob-like object (File) or
198 it can be falsy to reset/clear that state.*/
@@ -199,11 +218,11 @@
199 const updateDropZoneContent = function(blob){
200 const dd = bxs.dropDetails;
201 bxs.blob = blob;
202 D.clearElement(dd);
203 if(!blob){
204 form.file.value = '';
205 return;
206 }
207 D.append(dd, "Name: ", blob.name,
208 D.br(), "Size: ",blob.size);
209 if(blob.type && blob.type.startsWith("image/")){
@@ -215,11 +234,11 @@
215 }
216 const btn = D.button("Cancel");
217 D.append(dd, D.br(), btn);
218 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
219 };
220 form.file.addEventListener('change', function(ev){
221 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
222 });
223 /* Handle image paste from clipboard. TODO: figure out how we can
224 paste non-image binary data as if it had been selected via the
225 file selection element. */
@@ -228,32 +247,21 @@
228 item = items[0];
229 if(!item || !item.type) return;
230 else if('file'===item.kind){
231 updateDropZoneContent(false/*clear prev state*/);
232 updateDropZoneContent(items[0].getAsFile());
233 }else if(false && 'string'===item.kind){
234 /* ----^^^^^ disabled for now: the intent here is that if
235 form.msg is not active, populate it with this text, but
236 whether populating it from ctrl-v when it does not have focus
237 is a feature or a bug is debatable. It seems useful but may
238 violate the Principle of Least Surprise. */
239 if(document.activeElement !== form.msg){
240 /* Overwrite input field if it DOES NOT have focus,
241 otherwise let it do its own paste handling. */
242 item.getAsString((v)=>form.msg.value = v);
243 }
244 }
245 }, false);
246 /* Add help button for drag/drop/paste zone */
247 form.file.parentNode.insertBefore(
248 F.helpButtonlets.create(
249 document.querySelector('#chat-input-file-area .help-buttonlet')
250 ), form.file
251 );
252 ////////////////////////////////////////////////////////////
253 // File drag/drop visual notification.
254 const dropHighlight = form.file /* target zone */;
255 const dropEvents = {
256 drop: function(ev){
257 D.removeClass(dropHighlight, 'dragover');
258 },
259 dragenter: function(ev){
@@ -267,30 +275,31 @@
267 dragend: function(ev){
268 D.removeClass(dropHighlight, 'dragover');
269 }
270 };
271 Object.keys(dropEvents).forEach(
272 (k)=>form.file.addEventListener(k, dropEvents[k], true)
273 );
274 return bxs;
275 })()/*drag/drop*/;
276
277 form.addEventListener('submit',(e)=>{
278 e.preventDefault();
279 const fd = new FormData(form);
280 if(BlobXferState.blob/*replace file content with this*/){
281 fd.set("file", BlobXferState.blob);
282 }
283 if( form.msg.value.length>0 || form.file.value.length>0 || BlobXferState.blob ){
 
 
284 fetch("chat-send",{
285 method: 'POST',
286 body: fd
287 });
288 }
289 BlobXferState.clear();
290 form.msg.value = "";
291 form.msg.focus();
292 });
293
294 /* Returns a new TEXT node with the given text content. */
295 /** Returns the local time string of Date object d, defaulting
296 to the current time. */
@@ -589,12 +598,14 @@
589 Chat.changesSincePageHidden = 0;
590 Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
591 }
592 }else{
593 Chat.changesSincePageHidden += jx.msgs.length;
594 Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
595 Chat.pageTitleOrig;
 
 
596 }
597 if(jx.msgs.length && F.config.chat.pingTcp){
598 fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping");
599 }
600 }/*newcontent()*/;
601
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,10 @@
1 /**
2 This file contains the client-side implementation of fossil's /chat
3 application.
4 */
5 (function(){
 
6 const F = window.fossil, D = F.dom;
7 const E1 = function(selector){
8 const e = document.querySelector(selector);
9 if(!e) throw new Error("missing required DOM element: "+selector);
10 return e;
@@ -15,11 +14,14 @@
14 e:{/*map of certain DOM elements.*/
15 messageInjectPoint: E1('#message-inject-point'),
16 pageTitle: E1('head title'),
17 loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
18 inputWrapper: E1("#chat-input-area"),
19 messagesWrapper: E1('#chat-messages-wrapper'),
20 inputForm: E1('#chat-form'),
21 inputSingle: E1('#chat-input-single'),
22 inputFile: E1('#chat-input-file')
23 },
24 me: F.user.name,
25 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
26 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
27 pageIsActive: 'visible'===document.visibilityState,
@@ -32,10 +34,27 @@
34 that 'right' is conventional for mobile chat apps but can be
35 difficult to read in wide windows (desktop/tablet landscape
36 mode). Can be toggled via settings popup. */
37 msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
38 ajaxInflight: 0,
39 /** Gets (no args) or sets (1 arg) the current input text field value,
40 taking into account single- vs multi-line input. The getter returns
41 a string and the setter returns this object. */
42 inputValue: function(){
43 const e = this.e.inputSingle;
44 if(arguments.length){
45 e.value = arguments[0];
46 return this;
47 }else {
48 return e.value;
49 }
50 },
51 /** Asks the current user input field to take focus. Returns this. */
52 inputFocus: function(){
53 this.e.inputSingle.focus();
54 return this;
55 },
56 /** Enables (if yes is truthy) or disables all elements in
57 * this.disableDuringAjax. */
58 enableAjaxComponents: function(yes){
59 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
60 return this;
@@ -188,11 +207,11 @@
207 dropDetails: document.querySelector('#chat-drop-details'),
208 blob: undefined,
209 clear: function(){
210 this.blob = undefined;
211 D.clearElement(this.dropDetails);
212 Chat.e.inputFile.value = "";
213 }
214 };
215 /** Updates the paste/drop zone with details of the pasted/dropped
216 data. The argument must be a Blob or Blob-like object (File) or
217 it can be falsy to reset/clear that state.*/
@@ -199,11 +218,11 @@
218 const updateDropZoneContent = function(blob){
219 const dd = bxs.dropDetails;
220 bxs.blob = blob;
221 D.clearElement(dd);
222 if(!blob){
223 Chat.e.inputFile.value = '';
224 return;
225 }
226 D.append(dd, "Name: ", blob.name,
227 D.br(), "Size: ",blob.size);
228 if(blob.type && blob.type.startsWith("image/")){
@@ -215,11 +234,11 @@
234 }
235 const btn = D.button("Cancel");
236 D.append(dd, D.br(), btn);
237 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
238 };
239 Chat.e.inputFile.addEventListener('change', function(ev){
240 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
241 });
242 /* Handle image paste from clipboard. TODO: figure out how we can
243 paste non-image binary data as if it had been selected via the
244 file selection element. */
@@ -228,32 +247,21 @@
247 item = items[0];
248 if(!item || !item.type) return;
249 else if('file'===item.kind){
250 updateDropZoneContent(false/*clear prev state*/);
251 updateDropZoneContent(items[0].getAsFile());
 
 
 
 
 
 
 
 
 
 
 
252 }
253 }, false);
254 /* Add help button for drag/drop/paste zone */
255 Chat.e.inputFile.parentNode.insertBefore(
256 F.helpButtonlets.create(
257 document.querySelector('#chat-input-file-area .help-buttonlet')
258 ), Chat.e.inputFile
259 );
260 ////////////////////////////////////////////////////////////
261 // File drag/drop visual notification.
262 const dropHighlight = Chat.e.inputFile /* target zone */;
263 const dropEvents = {
264 drop: function(ev){
265 D.removeClass(dropHighlight, 'dragover');
266 },
267 dragenter: function(ev){
@@ -267,30 +275,31 @@
275 dragend: function(ev){
276 D.removeClass(dropHighlight, 'dragover');
277 }
278 };
279 Object.keys(dropEvents).forEach(
280 (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
281 );
282 return bxs;
283 })()/*drag/drop*/;
284
285 Chat.e.inputForm.addEventListener('submit',(e)=>{
286 e.preventDefault();
287 const fd = new FormData(Chat.e.inputForm);
288 if(BlobXferState.blob/*replace file content with this*/){
289 fd.set("file", BlobXferState.blob);
290 }
291 if( !!Chat.inputValue()
292 || Chat.e.inputFile.value.length>0
293 || BlobXferState.blob ){
294 fetch("chat-send",{
295 method: 'POST',
296 body: fd
297 });
298 }
299 BlobXferState.clear();
300 Chat.inputValue("").inputFocus();
 
301 });
302
303 /* Returns a new TEXT node with the given text content. */
304 /** Returns the local time string of Date object d, defaulting
305 to the current time. */
@@ -589,12 +598,14 @@
598 Chat.changesSincePageHidden = 0;
599 Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
600 }
601 }else{
602 Chat.changesSincePageHidden += jx.msgs.length;
603 if(jx.msgs.length){
604 Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+
605 Chat.pageTitleOrig;
606 }
607 }
608 if(jx.msgs.length && F.config.chat.pingTcp){
609 fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping");
610 }
611 }/*newcontent()*/;
612
+10 -2
--- src/default.css
+++ src/default.css
@@ -1513,11 +1513,10 @@
15131513
15141514
body.chat .chat-message-popup {
15151515
font-family: monospace;
15161516
font-size: 0.8em;
15171517
text-align: left;
1518
- opacity: 0.8;
15191518
display: flex;
15201519
flex-direction: column;
15211520
align-items: stretch;
15221521
padding: 0.25em;
15231522
z-index: 200;
@@ -1566,10 +1565,16 @@
15661565
max-height: 1em;
15671566
min-width: 1em;
15681567
max-width: 1em;
15691568
margin: 0;
15701569
padding: 0.2em/*needed to avoid image truncation*/;
1570
+ border: 1px solid rgba(0,0,0,0.0)/*avoid resize when hover style kicks in*/;
1571
+ cursor: pointer;
1572
+ border-radius: 0.25em;
1573
+}
1574
+.settings-icon:hover {
1575
+ border: 1px outset rgba(127,127,127,1);
15711576
}
15721577
body.fossil-dark-style .settings-icon {
15731578
filter: invert(100%);
15741579
}
15751580
body.chat #chat-settings-button {
@@ -1601,10 +1606,14 @@
16011606
vertical-align: middle;
16021607
}
16031608
body.chat .chat-settings-popup > span.menu-entry > span.button {
16041609
margin: 0.25em 0.2em;
16051610
padding: 0.5em;
1611
+ flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
1612
+}
1613
+body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1614
+ cursor: inherit;
16061615
}
16071616
body.chat #chat-messages-wrapper {
16081617
display: flex;
16091618
flex-direction: column;
16101619
}
@@ -1623,11 +1632,10 @@
16231632
behind it visibly, which is really ugly. Only current workaround is
16241633
to force an opaque background color on this element, but that's not
16251634
skin-friendly. */
16261635
position: sticky;
16271636
top: 0;
1628
- left: 0;
16291637
padding: 0.5em 1em;
16301638
z-index: 100
16311639
/* see notes in #chat-messages-wrapper. The various popups require a
16321640
z-index higher than this one. */
16331641
}
16341642
--- src/default.css
+++ src/default.css
@@ -1513,11 +1513,10 @@
1513
1514 body.chat .chat-message-popup {
1515 font-family: monospace;
1516 font-size: 0.8em;
1517 text-align: left;
1518 opacity: 0.8;
1519 display: flex;
1520 flex-direction: column;
1521 align-items: stretch;
1522 padding: 0.25em;
1523 z-index: 200;
@@ -1566,10 +1565,16 @@
1566 max-height: 1em;
1567 min-width: 1em;
1568 max-width: 1em;
1569 margin: 0;
1570 padding: 0.2em/*needed to avoid image truncation*/;
 
 
 
 
 
 
1571 }
1572 body.fossil-dark-style .settings-icon {
1573 filter: invert(100%);
1574 }
1575 body.chat #chat-settings-button {
@@ -1601,10 +1606,14 @@
1601 vertical-align: middle;
1602 }
1603 body.chat .chat-settings-popup > span.menu-entry > span.button {
1604 margin: 0.25em 0.2em;
1605 padding: 0.5em;
 
 
 
 
1606 }
1607 body.chat #chat-messages-wrapper {
1608 display: flex;
1609 flex-direction: column;
1610 }
@@ -1623,11 +1632,10 @@
1623 behind it visibly, which is really ugly. Only current workaround is
1624 to force an opaque background color on this element, but that's not
1625 skin-friendly. */
1626 position: sticky;
1627 top: 0;
1628 left: 0;
1629 padding: 0.5em 1em;
1630 z-index: 100
1631 /* see notes in #chat-messages-wrapper. The various popups require a
1632 z-index higher than this one. */
1633 }
1634
--- src/default.css
+++ src/default.css
@@ -1513,11 +1513,10 @@
1513
1514 body.chat .chat-message-popup {
1515 font-family: monospace;
1516 font-size: 0.8em;
1517 text-align: left;
 
1518 display: flex;
1519 flex-direction: column;
1520 align-items: stretch;
1521 padding: 0.25em;
1522 z-index: 200;
@@ -1566,10 +1565,16 @@
1565 max-height: 1em;
1566 min-width: 1em;
1567 max-width: 1em;
1568 margin: 0;
1569 padding: 0.2em/*needed to avoid image truncation*/;
1570 border: 1px solid rgba(0,0,0,0.0)/*avoid resize when hover style kicks in*/;
1571 cursor: pointer;
1572 border-radius: 0.25em;
1573 }
1574 .settings-icon:hover {
1575 border: 1px outset rgba(127,127,127,1);
1576 }
1577 body.fossil-dark-style .settings-icon {
1578 filter: invert(100%);
1579 }
1580 body.chat #chat-settings-button {
@@ -1601,10 +1606,14 @@
1606 vertical-align: middle;
1607 }
1608 body.chat .chat-settings-popup > span.menu-entry > span.button {
1609 margin: 0.25em 0.2em;
1610 padding: 0.5em;
1611 flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
1612 }
1613 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1614 cursor: inherit;
1615 }
1616 body.chat #chat-messages-wrapper {
1617 display: flex;
1618 flex-direction: column;
1619 }
@@ -1623,11 +1632,10 @@
1632 behind it visibly, which is really ugly. Only current workaround is
1633 to force an opaque background color on this element, but that's not
1634 skin-friendly. */
1635 position: sticky;
1636 top: 0;
 
1637 padding: 0.5em 1em;
1638 z-index: 100
1639 /* see notes in #chat-messages-wrapper. The various popups require a
1640 z-index higher than this one. */
1641 }
1642

Keyboard Shortcuts

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