Fossil SCM

/chat: added option to toggle between text and contenteditable widget, defaulting to the former. Prettied up the config view a bit and made it more right-handed friendly.

stephan 2021-10-10 05:53 chat-input-revisited
Commit 5d7c98ef92046b068c663327b9cf12187654fdbf1e835c68860da3ea561ea9fc
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -420,11 +420,17 @@
420420
timestamp of each user's most recent message. */
421421
"active-user-list-timestamps": false,
422422
/* When on, the [audible-alert] is played for one's own
423423
messages, else it is only played for other users'
424424
messages. */
425
- "alert-own-messages": false
425
+ "alert-own-messages": false,
426
+ /* "Experimental mode" input: use a contenteditable field
427
+ for input. This is generally more comfortable to use,
428
+ and more modern, than plain text input fields, but
429
+ the list of browser-specific quirks and bugs is...
430
+ not short. */
431
+ "edit-widget-x": false
426432
}
427433
},
428434
/** Plays a new-message notification sound IF the audible-alert
429435
setting is true, else this is a no-op. Returns this.
430436
*/
@@ -1391,70 +1397,97 @@
13911397
/**
13921398
Settings ops structure:
13931399
13941400
label: string for the UI
13951401
1396
- boolValue: string (name of Chat.settings setting) or a
1397
- function which returns true or false.
1402
+ boolValue: string (name of Chat.settings setting) or a function
1403
+ which returns true or false. If it is a string, it gets
1404
+ replaced by a function which returns
1405
+ Chat.settings.getBool(thatString) and the string gets assigned
1406
+ to the persistentSetting property of this object.
13981407
13991408
select: SELECT element (instead of boolValue)
14001409
14011410
callback: optional handler to call after setting is modified.
1411
+ Its "this" is the options object. If this object has a
1412
+ boolValue string or a persistentSetting property, the argument
1413
+ passed to the callback is a settings object in the form {key:K,
1414
+ value:V}. If this object does not have boolValue string or
1415
+ persistentSetting then the callback is passed an event object
1416
+ in response to the config option's UI widget being activated,
1417
+ normally a 'change' event.
14021418
1403
- If a setting has a boolValue set, that gets transformed into a
1419
+ If a setting has a boolValue set, that gets rendered as a
14041420
checkbox which toggles the given persistent setting (if
14051421
boolValue is a string) AND listens for changes to that setting
14061422
fired via Chat.settings.set() so that the checkbox can stay in
14071423
sync with external changes to that setting. Various Chat UI
14081424
elements stay in sync with the config UI via those settings
1409
- events.
1410
- */
1425
+ events. The checkbox element gets added to the options object
1426
+ so that the callback() can reference it via this.checkbox.
1427
+ */
14111428
const settingsOps = [{
1429
+ label: "Chat Configuration Options",
1430
+ hint: "Most of these settings are persistent via window.localStorage."
1431
+ },{
1432
+ label: "Chat-only mode",
1433
+ hint: "Toggle the page between normal fossil view and chat-only view.",
1434
+ boolValue: 'chat-only-mode'
1435
+ },{
14121436
label: "Ctrl-enter to Send",
1413
- hint: "When on, only Ctrl-Enter will send messages and Enter adds "+
1414
- "blank lines. "+
1415
- "When off, both Enter and Ctrl-Enter send. "+
1416
- "When the input field has focus, is empty, and preview "+
1417
- "mode is NOT active then Ctrl-Enter toggles this setting.",
1437
+ hint: [
1438
+ "When on, only Ctrl-Enter will send messages and Enter adds ",
1439
+ "blank lines. When off, both Enter and Ctrl-Enter send. ",
1440
+ "When the input field has focus, is empty, and preview ",
1441
+ "mode is NOT active then Ctrl-Enter toggles this setting."
1442
+ ].join(''),
14181443
boolValue: 'edit-ctrl-send'
14191444
},{
14201445
label: "Compact mode",
1421
- hint: "Toggle between a space-saving or more spacious writing area. "+
1422
- "When the input field has focus, is empty, and preview mode "+
1423
- "is NOT active then Shift-Enter toggles this setting.",
1446
+ hint: [
1447
+ "Toggle between a space-saving or more spacious writing area. ",
1448
+ "When the input field has focus, is empty, and preview mode ",
1449
+ "is NOT active then Shift-Enter toggles this setting."].join(''),
14241450
boolValue: 'edit-compact-mode'
14251451
},{
14261452
label: "Left-align my posts",
14271453
hint: "Default alignment of your own messages is selected "
1428
- +"based window width/height relationship.",
1454
+ + "based window width/height relationship.",
14291455
boolValue: ()=>!document.body.classList.contains('my-messages-right'),
14301456
callback: function f(){
14311457
document.body.classList[
14321458
this.checkbox.checked ? 'remove' : 'add'
14331459
]('my-messages-right');
14341460
}
14351461
},{
14361462
label: "Monospace message font",
1437
- hint: "Use monospace font for message text?",
1463
+ hint: "Use monospace font for message and input text.",
14381464
boolValue: 'monospace-messages',
14391465
callback: function(setting){
14401466
document.body.classList[
14411467
setting.value ? 'add' : 'remove'
14421468
]('monospace-messages');
14431469
}
14441470
},{
1445
- label: "Chat-only mode",
1446
- hint: "Toggle the page between normal fossil view and chat-only view.",
1447
- boolValue: 'chat-only-mode'
1448
- },{
14491471
label: "Show images inline",
1450
- hint: "Whether to show images inline or as a hyperlink.",
1472
+ hint: "Show attached images inline or as a download link.",
14511473
boolValue: 'images-inline'
1452
- },namedOptions.activeUsers,{
1474
+ },
1475
+ namedOptions.activeUsers,
1476
+ {
14531477
label: "Timestamps in active users list",
1454
- hint: "Whether to show last-message timestamps.",
1478
+ hint: "Show most recent message timestamps in the active user list.",
14551479
boolValue: 'active-user-list-timestamps'
1480
+ },{
1481
+ label: "Use 'contenteditable' editing mode.",
1482
+ boolValue: 'edit-widget-x',
1483
+ hint: [
1484
+ "When enabled, chat input uses a so-called 'contenteditable' ",
1485
+ "field. Though generally more comfortable and modern than ",
1486
+ "plain-text input fields, browser-specific quirks and bugs ",
1487
+ "may lead to frustration."
1488
+ ].join('')
14561489
}];
14571490
14581491
/** Set up selection list of notification sounds. */
14591492
if(1){
14601493
const selectSound = D.select();
@@ -1473,11 +1506,14 @@
14731506
selectSound.selectedIndex = firstSoundIndex;
14741507
}
14751508
}
14761509
Chat.setNewMessageSound(selectSound.value);
14771510
settingsOps.push({
1478
- hint: "Audio alert. How to enable audio playback is browser-specific!",
1511
+ label: "Sound Options...",
1512
+ hint: "How to enable audio playback is browser-specific!"
1513
+ },{
1514
+ hint: "Audio alert",
14791515
select: selectSound,
14801516
callback: function(ev){
14811517
const v = ev.target.value;
14821518
Chat.setNewMessageSound(v);
14831519
F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
@@ -1499,19 +1535,20 @@
14991535
const line = D.addClass(D.div(), 'menu-entry');
15001536
const label = op.label
15011537
? D.append(D.label(),op.label) : undefined;
15021538
const labelWrapper = D.addClass(D.div(), 'label-wrapper');
15031539
var hint;
1504
- const col0 = D.span();
15051540
if(op.hint){
15061541
hint = D.append(D.addClass(D.span(),'hint'),op.hint);
15071542
}
15081543
if(op.hasOwnProperty('select')){
1509
- D.append(line, col0, labelWrapper);
1544
+ const col0 = D.addClass(D.span(/*empty, but for spacing*/),
1545
+ 'toggle-wrapper');
1546
+ D.append(line, labelWrapper, col0);
15101547
D.append(labelWrapper, op.select);
15111548
if(hint) D.append(labelWrapper, hint);
1512
- if(label) D.append(col0, label);
1549
+ if(label) D.append(label);
15131550
if(op.callback){
15141551
op.select.addEventListener('change', (ev)=>op.callback(ev), false);
15151552
}
15161553
}else if(op.hasOwnProperty('boolValue')){
15171554
if(undefined === f.$id) f.$id = 0;
@@ -1523,23 +1560,26 @@
15231560
}
15241561
const check = op.checkbox
15251562
= D.attr(D.checkbox(1, op.boolValue()),
15261563
'aria-label', op.label);
15271564
const id = 'cfgopt'+f.$id;
1565
+ const col0 = D.addClass(D.span(), 'toggle-wrapper');
15281566
check.checked = op.boolValue();
15291567
op.checkbox = check;
15301568
D.attr(check, 'id', id);
1531
- D.append(line, col0, labelWrapper);
1569
+ D.append(line, labelWrapper, col0);
15321570
D.append(col0, check);
15331571
if(label){
15341572
D.attr(label, 'for', id);
15351573
D.append(labelWrapper, label);
15361574
}
15371575
if(hint) D.append(labelWrapper, hint);
15381576
}else{
1539
- line.addEventListener('click', callback);
1540
- D.append(line, col0, labelWrapper);
1577
+ if(op.callback){
1578
+ line.addEventListener('click', (ev)=>op.callback(ev));
1579
+ }
1580
+ D.append(line, labelWrapper);
15411581
if(label) D.append(labelWrapper, label);
15421582
if(hint) D.append(labelWrapper, hint);
15431583
}
15441584
D.append(optionsMenu, line);
15451585
if(op.persistentSetting){
@@ -1577,28 +1617,50 @@
15771617
Chat.settings.addListener('active-user-list-timestamps',function(s){
15781618
Chat.showActiveUserTimestamps(s.value);
15791619
});
15801620
Chat.settings.addListener('chat-only-mode',function(s){
15811621
Chat.chatOnlyMode(s.value);
1622
+ });
1623
+ Chat.settings.addListener('edit-widget-x',function(s){
1624
+ let eSelected;
1625
+ if(s.value){
1626
+ if(Chat.e.inputX===Chat.inputElement()) return;
1627
+ eSelected = Chat.e.inputX;
1628
+ }else{
1629
+ eSelected = Chat.settings.getBool('edit-compact-mode')
1630
+ ? Chat.e.input1 : Chat.e.inputM;
1631
+ }
1632
+ const v = Chat.inputValue();
1633
+ Chat.inputValue('');
1634
+ Chat.e.inputFields.forEach(function(e,ndx){
1635
+ if(eSelected===e){
1636
+ Chat.e.inputFields.$currentIndex = ndx;
1637
+ D.removeClass(e, 'hidden');
1638
+ }
1639
+ else D.addClass(e,'hidden');
1640
+ });
1641
+ Chat.inputValue(v);
1642
+ eSelected.focus();
15821643
});
15831644
Chat.settings.addListener('edit-compact-mode',function(s){
15841645
if(Chat.e.inputX!==Chat.inputElement()){
1585
- /* text field/textarea mode: swap them if needed. */
1646
+ /* Text field/textarea mode: swap them if needed.
1647
+ Compact mode of inputX is toggled via CSS. */
15861648
const a = s.value
15871649
? [Chat.e.input1, Chat.e.inputM, 0]
15881650
: [Chat.e.inputM, Chat.e.input1, 1];
15891651
const v = Chat.inputValue();
15901652
Chat.inputValue('');
1653
+ Chat.e.inputFields.$currentIndex = a[2];
1654
+ Chat.inputValue(v);
15911655
D.removeClass(a[0], 'hidden');
15921656
D.addClass(a[1], 'hidden');
1593
- Chat.e.inputFields.$currentIndex = a[2];
1594
- Chat.inputValue(v);
1595
- a[0].focus();
15961657
}
15971658
Chat.e.inputElementWrapper.classList[
15981659
s.value ? 'add' : 'remove'
15991660
]('compact');
1661
+ Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
16001662
});
16011663
Chat.settings.addListener('edit-ctrl-send',function(s){
16021664
const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
16031665
Chat.e.inputFields.forEach((e)=>{
16041666
const v = e.dataset.placeholder0 + " " +label;
16051667
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -420,11 +420,17 @@
420 timestamp of each user's most recent message. */
421 "active-user-list-timestamps": false,
422 /* When on, the [audible-alert] is played for one's own
423 messages, else it is only played for other users'
424 messages. */
425 "alert-own-messages": false
 
 
 
 
 
 
426 }
427 },
428 /** Plays a new-message notification sound IF the audible-alert
429 setting is true, else this is a no-op. Returns this.
430 */
@@ -1391,70 +1397,97 @@
1391 /**
1392 Settings ops structure:
1393
1394 label: string for the UI
1395
1396 boolValue: string (name of Chat.settings setting) or a
1397 function which returns true or false.
 
 
 
1398
1399 select: SELECT element (instead of boolValue)
1400
1401 callback: optional handler to call after setting is modified.
 
 
 
 
 
 
 
1402
1403 If a setting has a boolValue set, that gets transformed into a
1404 checkbox which toggles the given persistent setting (if
1405 boolValue is a string) AND listens for changes to that setting
1406 fired via Chat.settings.set() so that the checkbox can stay in
1407 sync with external changes to that setting. Various Chat UI
1408 elements stay in sync with the config UI via those settings
1409 events.
1410 */
 
1411 const settingsOps = [{
 
 
 
 
 
 
 
1412 label: "Ctrl-enter to Send",
1413 hint: "When on, only Ctrl-Enter will send messages and Enter adds "+
1414 "blank lines. "+
1415 "When off, both Enter and Ctrl-Enter send. "+
1416 "When the input field has focus, is empty, and preview "+
1417 "mode is NOT active then Ctrl-Enter toggles this setting.",
 
1418 boolValue: 'edit-ctrl-send'
1419 },{
1420 label: "Compact mode",
1421 hint: "Toggle between a space-saving or more spacious writing area. "+
1422 "When the input field has focus, is empty, and preview mode "+
1423 "is NOT active then Shift-Enter toggles this setting.",
 
1424 boolValue: 'edit-compact-mode'
1425 },{
1426 label: "Left-align my posts",
1427 hint: "Default alignment of your own messages is selected "
1428 +"based window width/height relationship.",
1429 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1430 callback: function f(){
1431 document.body.classList[
1432 this.checkbox.checked ? 'remove' : 'add'
1433 ]('my-messages-right');
1434 }
1435 },{
1436 label: "Monospace message font",
1437 hint: "Use monospace font for message text?",
1438 boolValue: 'monospace-messages',
1439 callback: function(setting){
1440 document.body.classList[
1441 setting.value ? 'add' : 'remove'
1442 ]('monospace-messages');
1443 }
1444 },{
1445 label: "Chat-only mode",
1446 hint: "Toggle the page between normal fossil view and chat-only view.",
1447 boolValue: 'chat-only-mode'
1448 },{
1449 label: "Show images inline",
1450 hint: "Whether to show images inline or as a hyperlink.",
1451 boolValue: 'images-inline'
1452 },namedOptions.activeUsers,{
 
 
1453 label: "Timestamps in active users list",
1454 hint: "Whether to show last-message timestamps.",
1455 boolValue: 'active-user-list-timestamps'
 
 
 
 
 
 
 
 
 
1456 }];
1457
1458 /** Set up selection list of notification sounds. */
1459 if(1){
1460 const selectSound = D.select();
@@ -1473,11 +1506,14 @@
1473 selectSound.selectedIndex = firstSoundIndex;
1474 }
1475 }
1476 Chat.setNewMessageSound(selectSound.value);
1477 settingsOps.push({
1478 hint: "Audio alert. How to enable audio playback is browser-specific!",
 
 
 
1479 select: selectSound,
1480 callback: function(ev){
1481 const v = ev.target.value;
1482 Chat.setNewMessageSound(v);
1483 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
@@ -1499,19 +1535,20 @@
1499 const line = D.addClass(D.div(), 'menu-entry');
1500 const label = op.label
1501 ? D.append(D.label(),op.label) : undefined;
1502 const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1503 var hint;
1504 const col0 = D.span();
1505 if(op.hint){
1506 hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1507 }
1508 if(op.hasOwnProperty('select')){
1509 D.append(line, col0, labelWrapper);
 
 
1510 D.append(labelWrapper, op.select);
1511 if(hint) D.append(labelWrapper, hint);
1512 if(label) D.append(col0, label);
1513 if(op.callback){
1514 op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1515 }
1516 }else if(op.hasOwnProperty('boolValue')){
1517 if(undefined === f.$id) f.$id = 0;
@@ -1523,23 +1560,26 @@
1523 }
1524 const check = op.checkbox
1525 = D.attr(D.checkbox(1, op.boolValue()),
1526 'aria-label', op.label);
1527 const id = 'cfgopt'+f.$id;
 
1528 check.checked = op.boolValue();
1529 op.checkbox = check;
1530 D.attr(check, 'id', id);
1531 D.append(line, col0, labelWrapper);
1532 D.append(col0, check);
1533 if(label){
1534 D.attr(label, 'for', id);
1535 D.append(labelWrapper, label);
1536 }
1537 if(hint) D.append(labelWrapper, hint);
1538 }else{
1539 line.addEventListener('click', callback);
1540 D.append(line, col0, labelWrapper);
 
 
1541 if(label) D.append(labelWrapper, label);
1542 if(hint) D.append(labelWrapper, hint);
1543 }
1544 D.append(optionsMenu, line);
1545 if(op.persistentSetting){
@@ -1577,28 +1617,50 @@
1577 Chat.settings.addListener('active-user-list-timestamps',function(s){
1578 Chat.showActiveUserTimestamps(s.value);
1579 });
1580 Chat.settings.addListener('chat-only-mode',function(s){
1581 Chat.chatOnlyMode(s.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1582 });
1583 Chat.settings.addListener('edit-compact-mode',function(s){
1584 if(Chat.e.inputX!==Chat.inputElement()){
1585 /* text field/textarea mode: swap them if needed. */
 
1586 const a = s.value
1587 ? [Chat.e.input1, Chat.e.inputM, 0]
1588 : [Chat.e.inputM, Chat.e.input1, 1];
1589 const v = Chat.inputValue();
1590 Chat.inputValue('');
 
 
1591 D.removeClass(a[0], 'hidden');
1592 D.addClass(a[1], 'hidden');
1593 Chat.e.inputFields.$currentIndex = a[2];
1594 Chat.inputValue(v);
1595 a[0].focus();
1596 }
1597 Chat.e.inputElementWrapper.classList[
1598 s.value ? 'add' : 'remove'
1599 ]('compact');
 
1600 });
1601 Chat.settings.addListener('edit-ctrl-send',function(s){
1602 const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1603 Chat.e.inputFields.forEach((e)=>{
1604 const v = e.dataset.placeholder0 + " " +label;
1605
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -420,11 +420,17 @@
420 timestamp of each user's most recent message. */
421 "active-user-list-timestamps": false,
422 /* When on, the [audible-alert] is played for one's own
423 messages, else it is only played for other users'
424 messages. */
425 "alert-own-messages": false,
426 /* "Experimental mode" input: use a contenteditable field
427 for input. This is generally more comfortable to use,
428 and more modern, than plain text input fields, but
429 the list of browser-specific quirks and bugs is...
430 not short. */
431 "edit-widget-x": false
432 }
433 },
434 /** Plays a new-message notification sound IF the audible-alert
435 setting is true, else this is a no-op. Returns this.
436 */
@@ -1391,70 +1397,97 @@
1397 /**
1398 Settings ops structure:
1399
1400 label: string for the UI
1401
1402 boolValue: string (name of Chat.settings setting) or a function
1403 which returns true or false. If it is a string, it gets
1404 replaced by a function which returns
1405 Chat.settings.getBool(thatString) and the string gets assigned
1406 to the persistentSetting property of this object.
1407
1408 select: SELECT element (instead of boolValue)
1409
1410 callback: optional handler to call after setting is modified.
1411 Its "this" is the options object. If this object has a
1412 boolValue string or a persistentSetting property, the argument
1413 passed to the callback is a settings object in the form {key:K,
1414 value:V}. If this object does not have boolValue string or
1415 persistentSetting then the callback is passed an event object
1416 in response to the config option's UI widget being activated,
1417 normally a 'change' event.
1418
1419 If a setting has a boolValue set, that gets rendered as a
1420 checkbox which toggles the given persistent setting (if
1421 boolValue is a string) AND listens for changes to that setting
1422 fired via Chat.settings.set() so that the checkbox can stay in
1423 sync with external changes to that setting. Various Chat UI
1424 elements stay in sync with the config UI via those settings
1425 events. The checkbox element gets added to the options object
1426 so that the callback() can reference it via this.checkbox.
1427 */
1428 const settingsOps = [{
1429 label: "Chat Configuration Options",
1430 hint: "Most of these settings are persistent via window.localStorage."
1431 },{
1432 label: "Chat-only mode",
1433 hint: "Toggle the page between normal fossil view and chat-only view.",
1434 boolValue: 'chat-only-mode'
1435 },{
1436 label: "Ctrl-enter to Send",
1437 hint: [
1438 "When on, only Ctrl-Enter will send messages and Enter adds ",
1439 "blank lines. When off, both Enter and Ctrl-Enter send. ",
1440 "When the input field has focus, is empty, and preview ",
1441 "mode is NOT active then Ctrl-Enter toggles this setting."
1442 ].join(''),
1443 boolValue: 'edit-ctrl-send'
1444 },{
1445 label: "Compact mode",
1446 hint: [
1447 "Toggle between a space-saving or more spacious writing area. ",
1448 "When the input field has focus, is empty, and preview mode ",
1449 "is NOT active then Shift-Enter toggles this setting."].join(''),
1450 boolValue: 'edit-compact-mode'
1451 },{
1452 label: "Left-align my posts",
1453 hint: "Default alignment of your own messages is selected "
1454 + "based window width/height relationship.",
1455 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1456 callback: function f(){
1457 document.body.classList[
1458 this.checkbox.checked ? 'remove' : 'add'
1459 ]('my-messages-right');
1460 }
1461 },{
1462 label: "Monospace message font",
1463 hint: "Use monospace font for message and input text.",
1464 boolValue: 'monospace-messages',
1465 callback: function(setting){
1466 document.body.classList[
1467 setting.value ? 'add' : 'remove'
1468 ]('monospace-messages');
1469 }
1470 },{
 
 
 
 
1471 label: "Show images inline",
1472 hint: "Show attached images inline or as a download link.",
1473 boolValue: 'images-inline'
1474 },
1475 namedOptions.activeUsers,
1476 {
1477 label: "Timestamps in active users list",
1478 hint: "Show most recent message timestamps in the active user list.",
1479 boolValue: 'active-user-list-timestamps'
1480 },{
1481 label: "Use 'contenteditable' editing mode.",
1482 boolValue: 'edit-widget-x',
1483 hint: [
1484 "When enabled, chat input uses a so-called 'contenteditable' ",
1485 "field. Though generally more comfortable and modern than ",
1486 "plain-text input fields, browser-specific quirks and bugs ",
1487 "may lead to frustration."
1488 ].join('')
1489 }];
1490
1491 /** Set up selection list of notification sounds. */
1492 if(1){
1493 const selectSound = D.select();
@@ -1473,11 +1506,14 @@
1506 selectSound.selectedIndex = firstSoundIndex;
1507 }
1508 }
1509 Chat.setNewMessageSound(selectSound.value);
1510 settingsOps.push({
1511 label: "Sound Options...",
1512 hint: "How to enable audio playback is browser-specific!"
1513 },{
1514 hint: "Audio alert",
1515 select: selectSound,
1516 callback: function(ev){
1517 const v = ev.target.value;
1518 Chat.setNewMessageSound(v);
1519 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
@@ -1499,19 +1535,20 @@
1535 const line = D.addClass(D.div(), 'menu-entry');
1536 const label = op.label
1537 ? D.append(D.label(),op.label) : undefined;
1538 const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1539 var hint;
 
1540 if(op.hint){
1541 hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1542 }
1543 if(op.hasOwnProperty('select')){
1544 const col0 = D.addClass(D.span(/*empty, but for spacing*/),
1545 'toggle-wrapper');
1546 D.append(line, labelWrapper, col0);
1547 D.append(labelWrapper, op.select);
1548 if(hint) D.append(labelWrapper, hint);
1549 if(label) D.append(label);
1550 if(op.callback){
1551 op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1552 }
1553 }else if(op.hasOwnProperty('boolValue')){
1554 if(undefined === f.$id) f.$id = 0;
@@ -1523,23 +1560,26 @@
1560 }
1561 const check = op.checkbox
1562 = D.attr(D.checkbox(1, op.boolValue()),
1563 'aria-label', op.label);
1564 const id = 'cfgopt'+f.$id;
1565 const col0 = D.addClass(D.span(), 'toggle-wrapper');
1566 check.checked = op.boolValue();
1567 op.checkbox = check;
1568 D.attr(check, 'id', id);
1569 D.append(line, labelWrapper, col0);
1570 D.append(col0, check);
1571 if(label){
1572 D.attr(label, 'for', id);
1573 D.append(labelWrapper, label);
1574 }
1575 if(hint) D.append(labelWrapper, hint);
1576 }else{
1577 if(op.callback){
1578 line.addEventListener('click', (ev)=>op.callback(ev));
1579 }
1580 D.append(line, labelWrapper);
1581 if(label) D.append(labelWrapper, label);
1582 if(hint) D.append(labelWrapper, hint);
1583 }
1584 D.append(optionsMenu, line);
1585 if(op.persistentSetting){
@@ -1577,28 +1617,50 @@
1617 Chat.settings.addListener('active-user-list-timestamps',function(s){
1618 Chat.showActiveUserTimestamps(s.value);
1619 });
1620 Chat.settings.addListener('chat-only-mode',function(s){
1621 Chat.chatOnlyMode(s.value);
1622 });
1623 Chat.settings.addListener('edit-widget-x',function(s){
1624 let eSelected;
1625 if(s.value){
1626 if(Chat.e.inputX===Chat.inputElement()) return;
1627 eSelected = Chat.e.inputX;
1628 }else{
1629 eSelected = Chat.settings.getBool('edit-compact-mode')
1630 ? Chat.e.input1 : Chat.e.inputM;
1631 }
1632 const v = Chat.inputValue();
1633 Chat.inputValue('');
1634 Chat.e.inputFields.forEach(function(e,ndx){
1635 if(eSelected===e){
1636 Chat.e.inputFields.$currentIndex = ndx;
1637 D.removeClass(e, 'hidden');
1638 }
1639 else D.addClass(e,'hidden');
1640 });
1641 Chat.inputValue(v);
1642 eSelected.focus();
1643 });
1644 Chat.settings.addListener('edit-compact-mode',function(s){
1645 if(Chat.e.inputX!==Chat.inputElement()){
1646 /* Text field/textarea mode: swap them if needed.
1647 Compact mode of inputX is toggled via CSS. */
1648 const a = s.value
1649 ? [Chat.e.input1, Chat.e.inputM, 0]
1650 : [Chat.e.inputM, Chat.e.input1, 1];
1651 const v = Chat.inputValue();
1652 Chat.inputValue('');
1653 Chat.e.inputFields.$currentIndex = a[2];
1654 Chat.inputValue(v);
1655 D.removeClass(a[0], 'hidden');
1656 D.addClass(a[1], 'hidden');
 
 
 
1657 }
1658 Chat.e.inputElementWrapper.classList[
1659 s.value ? 'add' : 'remove'
1660 ]('compact');
1661 Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
1662 });
1663 Chat.settings.addListener('edit-ctrl-send',function(s){
1664 const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1665 Chat.e.inputFields.forEach((e)=>{
1666 const v = e.dataset.placeholder0 + " " +label;
1667
+45 -17
--- src/style.chat.css
+++ src/style.chat.css
@@ -178,18 +178,24 @@
178178
body.chat:not(.chat-only-mode) #chat-input-area{
179179
/* Safari user reports that 2em is necessary to keep the file selection
180180
widget from overlapping the page footer, whereas a margin of 0 is fine
181181
for FF/Chrome (and 2em is a *huge* waste of space for those). */
182182
margin-bottom: 0;
183
+}
184
+.chat-input-field {
185
+ flex: 10 1 auto;
186
+ margin: 0;
187
+}
188
+#chat-input-field-x,
189
+#chat-input-field-multi {
190
+ overflow: auto;
191
+ resize: vertical;
183192
}
184193
#chat-input-field-x {
185194
display: inline-block/*supposed workaround for Chrome weirdness*/;
186195
padding: 0.2em;
187
- flex: 10 1 auto;
188196
background-color: rgba(156,156,156,0.3);
189
- overflow: auto;
190
- resize: vertical;
191197
white-space: pre-wrap;
192198
/* ^^^ Firefox, when pasting plain text into a contenteditable field,
193199
loses all newlines unless we explicitly set this. Chrome does not. */
194200
cursor: text;
195201
/* ^^^ In some browsers the cursor may not change for a contenteditable
@@ -395,37 +401,59 @@
395401
/* /chat config options go here */
396402
flex: 1 1 auto;
397403
display: flex;
398404
flex-direction: column;
399405
overflow: auto;
406
+ align-items: stretch;
400407
}
401408
body.chat #chat-config #chat-config-options .menu-entry {
402409
display: flex;
403
- align-items: baseline;
410
+ align-items: center;
404411
flex-direction: row;
405412
flex-wrap: nowrap;
406413
padding: 1em;
414
+ flex: 1 1 auto;
415
+ align-self: stretch;
416
+}
417
+body.chat #chat-config #chat-config-options .menu-entry:nth-of-type(even){
418
+ background-color: rgba(175,175,175,0.1);
419
+}
420
+body.chat #chat-config #chat-config-options .menu-entry:nth-of-type(odd){
421
+ background-color: rgba(175,175,175,0.25);
422
+}
423
+body.chat #chat-config #chat-config-options .menu-entry:first-child {
424
+ /* Config list header */
425
+}
426
+body.chat #chat-config #chat-config-options .menu-entry:first-child .label-wrapper {
427
+ align-items: start;
428
+}
429
+body.chat #chat-config #chat-config-options .menu-entry > .toggle-wrapper {
430
+ /* Holder for a checkbox, if any */
431
+ min-width: 1.5rem;
432
+ margin-left: 1rem;
433
+}
434
+body.chat #chat-config #chat-config-options .menu-entry .label-wrapper {
435
+ /* Wrapper for a LABEL and a .hint element. */
436
+ display: flex;
437
+ flex-direction: column;
438
+ align-self: baseline;
439
+ flex: 1 1 auto;
440
+}
441
+body.chat #chat-config #chat-config-options .menu-entry label {
442
+ /* Config option label. */
443
+ font-weight: bold;
444
+ white-space: initial;
407445
}
408446
body.chat #chat-config #chat-config-options .menu-entry label[for] {
409447
cursor: pointer;
410448
}
411
-body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
412
- min-width: 1.5rem;
413
-}
414
-body.chat #chat-config #chat-config-options .menu-entry span.hint {
449
+body.chat #chat-config #chat-config-options .menu-entry .hint {
415450
/* Config menu hint text */
416
- font-size: 80%;
451
+ font-size: 85%;
417452
white-space: pre-wrap;
418453
display: inline-block;
419
-}
420
-body.chat #chat-config #chat-config-options .menu-entry:first-child {
421
-}
422
-body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
423
- display: flex;
424
- flex-direction: column;
425
- align-self: baseline;
426
- margin-left: 1em;
454
+ opacity: 0.85;
427455
}
428456
body.chat #chat-config #chat-config-options .menu-entry select {
429457
}
430458
body.chat #chat-preview #chat-preview-content {
431459
overflow: auto;
432460
--- src/style.chat.css
+++ src/style.chat.css
@@ -178,18 +178,24 @@
178 body.chat:not(.chat-only-mode) #chat-input-area{
179 /* Safari user reports that 2em is necessary to keep the file selection
180 widget from overlapping the page footer, whereas a margin of 0 is fine
181 for FF/Chrome (and 2em is a *huge* waste of space for those). */
182 margin-bottom: 0;
 
 
 
 
 
 
 
 
 
183 }
184 #chat-input-field-x {
185 display: inline-block/*supposed workaround for Chrome weirdness*/;
186 padding: 0.2em;
187 flex: 10 1 auto;
188 background-color: rgba(156,156,156,0.3);
189 overflow: auto;
190 resize: vertical;
191 white-space: pre-wrap;
192 /* ^^^ Firefox, when pasting plain text into a contenteditable field,
193 loses all newlines unless we explicitly set this. Chrome does not. */
194 cursor: text;
195 /* ^^^ In some browsers the cursor may not change for a contenteditable
@@ -395,37 +401,59 @@
395 /* /chat config options go here */
396 flex: 1 1 auto;
397 display: flex;
398 flex-direction: column;
399 overflow: auto;
 
400 }
401 body.chat #chat-config #chat-config-options .menu-entry {
402 display: flex;
403 align-items: baseline;
404 flex-direction: row;
405 flex-wrap: nowrap;
406 padding: 1em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407 }
408 body.chat #chat-config #chat-config-options .menu-entry label[for] {
409 cursor: pointer;
410 }
411 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
412 min-width: 1.5rem;
413 }
414 body.chat #chat-config #chat-config-options .menu-entry span.hint {
415 /* Config menu hint text */
416 font-size: 80%;
417 white-space: pre-wrap;
418 display: inline-block;
419 }
420 body.chat #chat-config #chat-config-options .menu-entry:first-child {
421 }
422 body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
423 display: flex;
424 flex-direction: column;
425 align-self: baseline;
426 margin-left: 1em;
427 }
428 body.chat #chat-config #chat-config-options .menu-entry select {
429 }
430 body.chat #chat-preview #chat-preview-content {
431 overflow: auto;
432
--- src/style.chat.css
+++ src/style.chat.css
@@ -178,18 +178,24 @@
178 body.chat:not(.chat-only-mode) #chat-input-area{
179 /* Safari user reports that 2em is necessary to keep the file selection
180 widget from overlapping the page footer, whereas a margin of 0 is fine
181 for FF/Chrome (and 2em is a *huge* waste of space for those). */
182 margin-bottom: 0;
183 }
184 .chat-input-field {
185 flex: 10 1 auto;
186 margin: 0;
187 }
188 #chat-input-field-x,
189 #chat-input-field-multi {
190 overflow: auto;
191 resize: vertical;
192 }
193 #chat-input-field-x {
194 display: inline-block/*supposed workaround for Chrome weirdness*/;
195 padding: 0.2em;
 
196 background-color: rgba(156,156,156,0.3);
 
 
197 white-space: pre-wrap;
198 /* ^^^ Firefox, when pasting plain text into a contenteditable field,
199 loses all newlines unless we explicitly set this. Chrome does not. */
200 cursor: text;
201 /* ^^^ In some browsers the cursor may not change for a contenteditable
@@ -395,37 +401,59 @@
401 /* /chat config options go here */
402 flex: 1 1 auto;
403 display: flex;
404 flex-direction: column;
405 overflow: auto;
406 align-items: stretch;
407 }
408 body.chat #chat-config #chat-config-options .menu-entry {
409 display: flex;
410 align-items: center;
411 flex-direction: row;
412 flex-wrap: nowrap;
413 padding: 1em;
414 flex: 1 1 auto;
415 align-self: stretch;
416 }
417 body.chat #chat-config #chat-config-options .menu-entry:nth-of-type(even){
418 background-color: rgba(175,175,175,0.1);
419 }
420 body.chat #chat-config #chat-config-options .menu-entry:nth-of-type(odd){
421 background-color: rgba(175,175,175,0.25);
422 }
423 body.chat #chat-config #chat-config-options .menu-entry:first-child {
424 /* Config list header */
425 }
426 body.chat #chat-config #chat-config-options .menu-entry:first-child .label-wrapper {
427 align-items: start;
428 }
429 body.chat #chat-config #chat-config-options .menu-entry > .toggle-wrapper {
430 /* Holder for a checkbox, if any */
431 min-width: 1.5rem;
432 margin-left: 1rem;
433 }
434 body.chat #chat-config #chat-config-options .menu-entry .label-wrapper {
435 /* Wrapper for a LABEL and a .hint element. */
436 display: flex;
437 flex-direction: column;
438 align-self: baseline;
439 flex: 1 1 auto;
440 }
441 body.chat #chat-config #chat-config-options .menu-entry label {
442 /* Config option label. */
443 font-weight: bold;
444 white-space: initial;
445 }
446 body.chat #chat-config #chat-config-options .menu-entry label[for] {
447 cursor: pointer;
448 }
449 body.chat #chat-config #chat-config-options .menu-entry .hint {
 
 
 
450 /* Config menu hint text */
451 font-size: 85%;
452 white-space: pre-wrap;
453 display: inline-block;
454 opacity: 0.85;
 
 
 
 
 
 
 
455 }
456 body.chat #chat-config #chat-config-options .menu-entry select {
457 }
458 body.chat #chat-preview #chat-preview-content {
459 overflow: auto;
460

Keyboard Shortcuts

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