Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | chat: added toggles for single/multi-line input (non-persistent) and monospace message font (persistent - affects message bodies and text input fields). |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
9d24a2849018c58e735d689121263587 |
| User & Date: | stephan 2020-12-25 17:52:51.123 |
Context
|
2020-12-25
| ||
| 19:12 | Improvements to chat documentation. ... (check-in: e525317e63 user: drh tags: trunk) | |
| 17:52 | chat: added toggles for single/multi-line input (non-persistent) and monospace message font (persistent - affects message bodies and text input fields). ... (check-in: 9d24a28490 user: stephan tags: trunk) | |
| 16:09 | First attempt at documentation for Fossil chat. ... (check-in: bcfdc1a106 user: drh tags: trunk) | |
Changes
Changes to src/chat.c.
| ︙ | ︙ | |||
100 101 102 103 104 105 106 |
if( iPingTcp ) style_disable_csp();
style_set_current_feature("chat");
style_header("Chat");
@ <div id='chat-input-area'>
@ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
@ <div id='chat-input-line'>
@ <input type="text" name="msg" id="chat-input-single" \
| | > > | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
if( iPingTcp ) style_disable_csp();
style_set_current_feature("chat");
style_header("Chat");
@ <div id='chat-input-area'>
@ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
@ <div id='chat-input-line'>
@ <input type="text" name="msg" id="chat-input-single" \
@ placeholder="Type message here." autocomplete="off">
@ <textarea rows="8" id="chat-input-multi" \
@ placeholder="Type message here" class="hidden"></textarea>
@ <input type="submit" value="Send" id="chat-message-submit">
@ <span id="chat-settings-button" class="settings-icon"></span>
@ </div>
@ <div id='chat-input-file-area'>
@ <div class='file-selection-wrapper'>
@ <div class='help-buttonlet'>
@ Select a file to upload, drag/drop a file into this spot,
@ or paste an image from the clipboard if supported by
|
| ︙ | ︙ |
Changes to src/chat.js.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
e:{/*map of certain DOM elements.*/
messageInjectPoint: E1('#message-inject-point'),
pageTitle: E1('head title'),
loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
inputWrapper: E1("#chat-input-area"),
messagesWrapper: E1('#chat-messages-wrapper'),
inputForm: E1('#chat-form'),
inputSingle: E1('#chat-input-single'),
inputFile: E1('#chat-input-file')
},
me: F.user.name,
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
pageIsActive: 'visible'===document.visibilityState,
changesSincePageHidden: 0,
| > > > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
e:{/*map of certain DOM elements.*/
messageInjectPoint: E1('#message-inject-point'),
pageTitle: E1('head title'),
loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
inputWrapper: E1("#chat-input-area"),
messagesWrapper: E1('#chat-messages-wrapper'),
inputForm: E1('#chat-form'),
btnSubmit: E1('#chat-message-submit'),
inputSingle: E1('#chat-input-single'),
inputMulti: E1('#chat-input-multi'),
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
inputFile: E1('#chat-input-file')
},
me: F.user.name,
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
pageIsActive: 'visible'===document.visibilityState,
changesSincePageHidden: 0,
|
| ︙ | ︙ | |||
36 37 38 39 40 41 42 |
mode). Can be toggled via settings popup. */
msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
ajaxInflight: 0,
/** Gets (no args) or sets (1 arg) the current input text field value,
taking into account single- vs multi-line input. The getter returns
a string and the setter returns this object. */
inputValue: function(){
| | < < > > > > > > | > > > > > > > > > > > > | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
mode). Can be toggled via settings popup. */
msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
ajaxInflight: 0,
/** Gets (no args) or sets (1 arg) the current input text field value,
taking into account single- vs multi-line input. The getter returns
a string and the setter returns this object. */
inputValue: function(){
const e = this.inputElement();
if(arguments.length){
e.value = arguments[0];
return this;
}
return e.value;
},
/** Asks the current user input field to take focus. Returns this. */
inputFocus: function(){
this.inputElement().focus();
return this;
},
/** Returns the current message input element. */
inputElement: function(){
return this.e.inputCurrent;
},
/** Toggles between single- and multi-line edit modes. Returns this. */
inputToggleSingleMulti: function(){
const old = this.e.inputCurrent;
if(this.e.inputCurrent === this.e.inputSingle){
this.e.inputCurrent = this.e.inputMulti;
}else{
this.e.inputCurrent = this.e.inputSingle;
}
D.addClass(old, 'hidden');
D.removeClass(this.e.inputCurrent, 'hidden');
this.e.inputCurrent.value = old.value;
return this;
},
/** Enables (if yes is truthy) or disables all elements in
* this.disableDuringAjax. */
enableAjaxComponents: function(yes){
D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
return this;
|
| ︙ | ︙ | |||
108 109 110 111 112 113 114 |
}
},
settings:{
get: (k,dflt)=>F.storage.get(k,dflt),
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
set: (k,v)=>F.storage.set(k,v),
defaults:{
| | > > > > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
}
},
settings:{
get: (k,dflt)=>F.storage.get(k,dflt),
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
set: (k,v)=>F.storage.set(k,v),
defaults:{
"images-inline": !!F.config.chat.imagesInline,
"monospace-messages": false
}
}
};
Object.keys(cs.settings.defaults).forEach(function f(k){
const v = cs.settings.get(k,f);
if(f===v) cs.settings.set(k,cs.settings.defaults[k]);
});
if(cs.settings.getBool('monospace-messages',false)){
document.body.classList.add('monospace-messages');
}
cs.e.inputCurrent = cs.e.inputSingle;
cs.pageTitleOrig = cs.e.pageTitle.innerText;
const qs = (e)=>document.querySelector(e);
const argsToArray = function(args){
return Array.prototype.slice.call(args,0);
};
cs.reportError = function(/*msg args*/){
const args = argsToArray(arguments);
|
| ︙ | ︙ | |||
278 279 280 281 282 283 284 |
};
Object.keys(dropEvents).forEach(
(k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
);
return bxs;
})()/*drag/drop*/;
| | < | > | | < | < > | > > > > > > > > > > > > > > > > > > > > > > > > | 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 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 |
};
Object.keys(dropEvents).forEach(
(k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
);
return bxs;
})()/*drag/drop*/;
Chat.submitMessage = function(){
const fd = new FormData(this.e.inputForm)
/* ^^^^ we don't really want/need the FORM element, but when
FormData() is default-constructed here then the server
segfaults, and i have no clue why! */;
const msg = this.inputValue();
if(msg) fd.set('msg',msg);
const file = BlobXferState.blob || this.e.inputFile.files[0];
if(file) fd.set("file", file);
if( msg || file ){
fetch("chat-send",{
method: 'POST',
body: fd
});
}
BlobXferState.clear();
Chat.inputValue("").inputFocus();
};
Chat.e.inputSingle.addEventListener('keydown',function(ev){
if(13===ev.keyCode/*ENTER*/){
ev.preventDefault();
ev.stopPropagation();
Chat.submitMessage();
return false;
}
}, false);
Chat.e.inputMulti.addEventListener('keydown',function(ev){
if(ev.ctrlKey && 13 === ev.keyCode){
ev.preventDefault();
ev.stopPropagation();
Chat.submitMessage();
return false;
}
}, false);
Chat.e.btnSubmit.addEventListener('click',(e)=>{
e.preventDefault();
Chat.submitMessage();
return false;
});
/* Returns a new TEXT node with the given text content. */
/** Returns the local time string of Date object d, defaulting
to the current time. */
const localTimeString = function ff(d){
if(!ff.pad){
|
| ︙ | ︙ | |||
389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
adjustY: function(y){
const rect = settingsButton.getBoundingClientRect();
return rect.top + rect.height + 2;
}
});
/* Settings menu entries... */
const settingsOps = [{
label: "Chat-only mode",
boolValue: ()=>!!document.body.classList.contains('chat-only-mode'),
callback: function f(){
if(undefined === f.isHidden){
f.isHidden = false;
f.elemsToToggle = [];
document.body.childNodes.forEach(function(e){
| > > > > > > > > > > > > > > | 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
adjustY: function(y){
const rect = settingsButton.getBoundingClientRect();
return rect.top + rect.height + 2;
}
});
/* Settings menu entries... */
const settingsOps = [{
label: "Multi-line input",
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
callback: function(){
Chat.inputToggleSingleMulti();
}
},{
label: "Monospace message font",
boolValue: ()=>document.body.classList.contains('monospace-messages'),
callback: function(){
document.body.classList.toggle('monospace-messages');
Chat.settings.set('monospace-messages',
document.body.classList.contains('monospace-messages'));
}
},{
label: "Chat-only mode",
boolValue: ()=>!!document.body.classList.contains('chat-only-mode'),
callback: function f(){
if(undefined === f.isHidden){
f.isHidden = false;
f.elemsToToggle = [];
document.body.childNodes.forEach(function(e){
|
| ︙ | ︙ |
Changes to src/default.css.
| ︙ | ︙ | |||
1488 1489 1490 1491 1492 1493 1494 |
}
/* The content area of a message (the body element of a FIELDSET) */
body.chat .message-content {
display: inline-block;
border-radius: 0.25em;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
| | | > > > > > > > | < | 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 |
}
/* The content area of a message (the body element of a FIELDSET) */
body.chat .message-content {
display: inline-block;
border-radius: 0.25em;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
padding: 0.25em 0.5em;
margin-top: -0.75em/*slide it up to the base of the LEGEND*/;
min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
white-space: pre-wrap/*needed for multi-line edits*/;
}
body.chat.monospace-messages .message-content,
body.chat.monospace-messages textarea,
body.chat.monospace-messages input[type=text]{
font-family: monospace;
}
/* User name for the post (a LEGEND element) */
body.chat .message-row .message-user {
border-radius: 0.25em 0.25em 0 0;
padding: 0 0.5em;
/*text-align: left; Firefox requires the 'align' attribute */
margin: 0 0.25em 0.4em 0.15em;
padding: 0 0.5em 0em 0.5em;
cursor: pointer;
}
body.chat .fossil-tooltip.help-buttonlet-content {
font-size: 80%;
}
|
| ︙ | ︙ | |||
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 |
body.chat.chat-only-mode #chat-input-area {
/* would like to pin this to the top so that it stays in place when
scrolling, but doing so causes #chat-messages-wrapper to scroll
behind it visibly, which is really ugly. Only current workaround is
to force an opaque background color on this element, but that's not
skin-friendly. */
position: sticky;
top: 0;
padding: 0.5em 1em;
z-index: 100
/* see notes in #chat-messages-wrapper. The various popups require a
z-index higher than this one. */
}
body.chat.chat-only-mode #chat-messages-wrapper {
| > | 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 |
body.chat.chat-only-mode #chat-input-area {
/* would like to pin this to the top so that it stays in place when
scrolling, but doing so causes #chat-messages-wrapper to scroll
behind it visibly, which is really ugly. Only current workaround is
to force an opaque background color on this element, but that's not
skin-friendly. */
position: sticky;
position: -webkit-sticky /* supposedly some versions of Safari */;
top: 0;
padding: 0.5em 1em;
z-index: 100
/* see notes in #chat-messages-wrapper. The various popups require a
z-index higher than this one. */
}
body.chat.chat-only-mode #chat-messages-wrapper {
|
| ︙ | ︙ | |||
1653 1654 1655 1656 1657 1658 1659 |
border-bottom: 1px solid black;
margin-bottom: 0.5em;
}
body.chat #chat-input-line {
display: flex;
flex-direction: row;
margin-bottom: 0.25em;
| | | > | 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 |
border-bottom: 1px solid black;
margin-bottom: 0.5em;
}
body.chat #chat-input-line {
display: flex;
flex-direction: row;
margin-bottom: 0.25em;
align-items: flex-start;
}
body.chat #chat-input-line > input[type=submit] {
flex: 1 5 auto;
max-width: 6em;
margin: 0 1em;
}
body.chat #chat-input-line > input[type=text],
body.chat #chat-input-line > textarea {
flex: 5 1 auto;
}
body.chat #chat-input-file-area {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
|
| ︙ | ︙ |