Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Found what seems to be a more or less viable solution for the chat layout in which the input area is effectively sticky while not actually being so. New messages do not scroll to the start of the list except for when a user locally posts a message, but instead, if a new message arrives and is scrolled out of view, a toast is shown to gently alert the user that a new message has arrived. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | chat-safari-breaks-here |
| Files: | files | file ages | folders |
| SHA3-256: |
0a00a103125e1cd9e9cb7a4219601c76 |
| User & Date: | stephan 2020-12-26 23:57:45.129 |
References
|
2020-12-27
| ||
| 03:39 | Eliminated top-down chat mode altogether in an attempt to eliminate some complexity and cruft. Re-added the toast-on-new-invisible-message from [0a00a103]. ... (check-in: 421d657078 user: stephan tags: trunk) | |
Context
|
2020-12-27
| ||
| 00:22 | Disabled the top-down/bottom-up chat toggle, per chat discussion. Removed explicit setting of div.content margins, in chat,except in chat-only mode, so that we honor skin-level margin settings (resolves layout breakage in Xekri skin). ... (check-in: 22b0faad3a user: stephan tags: chat-safari-breaks-here) | |
|
2020-12-26
| ||
| 23:57 | Found what seems to be a more or less viable solution for the chat layout in which the input area is effectively sticky while not actually being so. New messages do not scroll to the start of the list except for when a user locally posts a message, but instead, if a new message arrives and is scrolled out of view, a toast is shown to gently alert the user that a new message has arrived. ... (check-in: 0a00a10312 user: stephan tags: chat-safari-breaks-here) | |
| 22:09 | Disabled automatic scrolling when a new chat message arrives, as it is unnecessary when the user input fields are not sticky. To revisit later with sticky input fields. ... (check-in: b75ce86581 user: stephan tags: trunk) | |
Changes
Changes to src/chat.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/**
This file contains the client-side implementation of fossil's /chat
application.
*/
(function(){
const F = window.fossil, D = F.dom;
const E1 = function(selector){
const e = document.querySelector(selector);
if(!e) throw new Error("missing required DOM element: "+selector);
return e;
};
//document.body.classList.add('chat-only-mode');
const Chat = (function(){
const cs = {
e:{/*map of certain DOM elements.*/
messageInjectPoint: E1('#message-inject-point'),
pageTitle: E1('head title'),
| > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/**
This file contains the client-side implementation of fossil's /chat
application.
*/
(function(){
const F = window.fossil, D = F.dom;
const E1 = function(selector){
const e = document.querySelector(selector);
if(!e) throw new Error("missing required DOM element: "+selector);
return e;
};
const isInViewport = function(e) {
const rect = e.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
//document.body.classList.add('chat-only-mode');
const Chat = (function(){
const cs = {
e:{/*map of certain DOM elements.*/
messageInjectPoint: E1('#message-inject-point'),
pageTitle: E1('head title'),
|
| ︙ | ︙ | |||
115 116 117 118 119 120 121 |
the load-history widget. */
injectMessageElem: function f(e, atEnd){
const mip = atEnd ? this.e.loadToolbar : this.e.messageInjectPoint;
if(atEnd){
mip.parentNode.insertBefore(e, mip);
}else{
const self = this;
| < < < < < < < < < < < < < < < < < < < | | < < < > | < < < > > > | < < < < < < < | 124 125 126 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 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 |
the load-history widget. */
injectMessageElem: function f(e, atEnd){
const mip = atEnd ? this.e.loadToolbar : this.e.messageInjectPoint;
if(atEnd){
mip.parentNode.insertBefore(e, mip);
}else{
const self = this;
if(mip.nextSibling) mip.parentNode.insertBefore(e, mip.nextSibling);
else mip.parentNode.appendChild(e);
if(!atEnd && !this.isMassLoading
&& e.dataset.xfrom!==Chat.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.");
}
}
},
/** Returns true if chat-only mode is enabled. */
isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
/** Returns true if the UI seems to be in "bottom-up" mode. */
isUiFlipped: function(){
const style = window.getComputedStyle(this.e.contentDiv);
return style.flexDirection.indexOf("-reverse")>0;
},
/**
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.mainmenu, body > div.footer"
).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');
document.body.scroll(0,document.body.height);
}else{
D.removeClass(f.elemsToToggle, 'hidden');
D.removeClass(document.body, 'chat-only-mode');
}
const msg = document.querySelector('.message-widget');
if(msg) setTimeout(()=>msg.scrollIntoView(),0);
return this;
},
toggleChatOnlyMode: function(){
return this.chatOnlyMode(!this.isChatOnlyMode());
|
| ︙ | ︙ | |||
523 524 525 526 527 528 529 530 531 532 533 534 535 536 |
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();
| > > | 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
fetch("chat-send",{
method: 'POST',
body: fd
});
}
BlobXferState.clear();
Chat.inputValue("").inputFocus();
Chat.e.messagesWrapper.scrollTo(
0,0/*scrolls to top or bottom, depending on flex direction!*/);
};
Chat.e.inputSingle.addEventListener('keydown',function(ev){
if(13===ev.keyCode/*ENTER*/){
ev.preventDefault();
ev.stopPropagation();
Chat.submitMessage();
|
| ︙ | ︙ | |||
676 677 678 679 680 681 682 |
},{
label: "Bottom-up chat",
boolValue: ()=>document.body.classList.contains('chat-bottom-up'),
callback: function(){
document.body.classList.toggle('chat-bottom-up');
Chat.settings.set('bottom-up',
document.body.classList.contains('chat-bottom-up'));
| < < < < < < < < < < < | 659 660 661 662 663 664 665 666 667 668 669 670 671 672 |
},{
label: "Bottom-up chat",
boolValue: ()=>document.body.classList.contains('chat-bottom-up'),
callback: function(){
document.body.classList.toggle('chat-bottom-up');
Chat.settings.set('bottom-up',
document.body.classList.contains('chat-bottom-up'));
setTimeout(()=>Chat.e.inputWrapper.scrollIntoView(), 0);
}
},{
label: "Images inline",
boolValue: ()=>Chat.settings.getBool('images-inline'),
callback: function(){
const v = Chat.settings.getBool('images-inline',true);
|
| ︙ | ︙ | |||
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 |
D.append(Chat.e.messagesWrapper, toolbar);
toolbar.disabled = true /*will be enabled when msg load finishes */;
})()/*end history loading widget setup*/;
async function poll(isFirstCall){
if(poll.running) return;
poll.running = true;
if(isFirstCall) Chat.ajaxStart();
var p = fetch("chat-poll?name=" + Chat.mxMsg);
p.then(x=>x.json())
.then(y=>newcontent(y))
.catch(e=>console.error(e))
/* ^^^ 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(x){
if(isFirstCall){
Chat.ajaxEnd();
Chat.e.inputWrapper.scrollIntoView();
}
poll.running=false;
});
}
poll.running = false;
poll(true);
setInterval(poll, 1000);
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();
| > > | 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 |
D.append(Chat.e.messagesWrapper, toolbar);
toolbar.disabled = true /*will be enabled when msg load finishes */;
})()/*end history loading widget setup*/;
async function poll(isFirstCall){
if(poll.running) return;
poll.running = true;
Chat.isMassLoading = isFirstCall;
if(isFirstCall) Chat.ajaxStart();
var p = fetch("chat-poll?name=" + Chat.mxMsg);
p.then(x=>x.json())
.then(y=>newcontent(y))
.catch(e=>console.error(e))
/* ^^^ 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(x){
if(isFirstCall){
Chat.isMassLoading = false;
Chat.ajaxEnd();
Chat.e.inputWrapper.scrollIntoView();
}
poll.running=false;
});
}
poll.running = false;
poll(true);
setInterval(poll, 1000);
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();
|
Changes to src/default.css.
| ︙ | ︙ | |||
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 |
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
cursor: inherit;
}
/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {
display: flex;
flex-direction: column;
}
body.chat.chat-bottom-up #chat-messages-wrapper {
flex-direction: column-reverse;
z-index: 99 /* so that it scrolls under input area. If it's
lower than div.content then mouse events to it
are blocked!*/;
}
body.chat div.content {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: stretch;
}
body.chat.chat-bottom-up div.content {
flex-direction: column-reverse;
}
/* Wrapper for /chat user input controls */
body.chat #chat-input-area {
display: flex;
flex-direction: column;
border-bottom: 1px solid black;
| > > > > > > > > > > > | < < > < < < < < < < | 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 |
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
cursor: inherit;
}
/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {
display: flex;
flex-direction: column;
overflow: auto;
width: 100%;
flex: 1 1 auto;
}
body.chat.chat-bottom-up #chat-messages-wrapper {
flex-direction: column-reverse;
z-index: 99 /* so that it scrolls under input area. If it's
lower than div.content then mouse events to it
are blocked!*/;
}
body.chat.chat-only-mode #chat-message-wrapper {
}
body.chat div.content {
margin: 0;
padding: 0;
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
max-height: 85vh /* rough approximate */;
}
body.chat.chat-bottom-up div.content {
flex-direction: column-reverse;
}
body.chat.chat-only-mode div.content {
max-height: 95vh/*larger than approx. this is too big for Firefox on Android*/;
}
/* Wrapper for /chat user input controls */
body.chat #chat-input-area {
display: flex;
flex-direction: column;
border-bottom: 1px solid black;
padding: 0.5em 1em 0 0.5em;
margin-bottom: 0.5em;
z-index: 100
/* see notes in #chat-messages-wrapper. The various popups require a
z-index higher than this one. */;
flex: 0 0 auto;
}
body.chat.chat-bottom-up #chat-input-area {
border-bottom: none;
border-top: 1px solid black;
margin-bottom: 0;
margin-top: 0.5em;
}
/* Widget holding the chat message input field, send button, and
settings button. */
body.chat #chat-input-line {
display: flex;
flex-direction: row;
margin-bottom: 0.25em;
|
| ︙ | ︙ |