Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch chat-safari-breaks-here Excluding Merge-Ins
This is equivalent to a diff from b75ce865 to 8747d85e
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: 421d6570 user: stephan tags: trunk) | |
01:58 | chat: Safari has a severe allergic reaction to CSS vh units, so calculate the size of the affected DOM element in JS code at app startup and when the window resizes. ... (Closed-Leaf check-in: 8747d85e user: stephan tags: chat-safari-breaks-here, chat-safari-experiments) | |
01:07 | CSS tweaks to attempt to counter some Safari message spacing problems - cannot test locally. Also limit the chat image preview to 40% of the viewport width/height, to avoid it taking up the whole screen for a large image. ... (check-in: 670732a6 user: stephan tags: chat-safari-breaks-here, chat-safari-experiments) | |
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: 0a00a103 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: b75ce865 user: stephan tags: trunk) | |
22:01 | Added a CSS class to the xekri skin to keep the chat settings menu from having a transparent background. ... (check-in: 12b53919 user: stephan tags: trunk) | |
Changes to src/chat.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** 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'), loadToolbar: undefined /* the load-posts toolbar (dynamically created) */, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /** 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) ); }; const ForceResizeKludge = (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. */ const contentArea = E1('div.content'); const resized = function(){ const wh = window.innerHeight, mult = document.body.classList.contains('chat-only-mode') ? 0.95 : 0.85; contentArea.style.maxHeight = (wh * mult)+"px"; //console.debug("resized.",wh, window.getComputedStyle(contentArea).maxHeight); }; var doit; window.addEventListener('resize',function(ev){ clearTimeout(doit); doit = setTimeout(resized, 100); }, false); resized(); return resized; })(); //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'), loadToolbar: undefined /* the load-posts toolbar (dynamically created) */, |
︙ | ︙ | |||
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; | < < < < < < < < < < < < < < < < < < < | | < < < > | < < < > > > | < < < < < < < > | 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 | 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); ForceResizeKludge(); return this; }, toggleChatOnlyMode: function(){ return this.chatOnlyMode(!this.isChatOnlyMode()); }, settings:{ get: (k,dflt)=>F.storage.get(k,dflt), |
︙ | ︙ | |||
216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /* Alignment of 'my' messages: right alignment is conventional for mobile chat apps but can be difficult to read in wide windows (desktop/tablet landscape mode), so we default to a layout based on the apparently "orientation" of the window: tall vs wide. Can be toggled via settings popup. */ document.body.classList.add('my-messages-right'); } if(cs.settings.getBool("bottom-up")){ document.body.classList.add('chat-bottom-up'); } if(cs.settings.getBool('monospace-messages',false)){ document.body.classList.add('monospace-messages'); } cs.e.inputCurrent = cs.e.inputSingle; | > > | 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | /* Alignment of 'my' messages: right alignment is conventional for mobile chat apps but can be difficult to read in wide windows (desktop/tablet landscape mode), so we default to a layout based on the apparently "orientation" of the window: tall vs wide. Can be toggled via settings popup. */ document.body.classList.add('my-messages-right'); } cs.settings.set("bottom-up",true) /*force-set this for the time being: we will likely remove the other option*/; if(cs.settings.getBool("bottom-up")){ document.body.classList.add('chat-bottom-up'); } if(cs.settings.getBool('monospace-messages',false)){ document.body.classList.add('monospace-messages'); } cs.e.inputCurrent = cs.e.inputSingle; |
︙ | ︙ | |||
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(); | > > | 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 | 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(); |
︙ | ︙ | |||
671 672 673 674 675 676 677 678 679 680 681 682 | label: "Left-align my posts", boolValue: ()=>!document.body.classList.contains('my-messages-right'), callback: function f(){ document.body.classList.toggle('my-messages-right'); } },{ 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')); | > < < < < < < < < < < < > | 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 | label: "Left-align my posts", boolValue: ()=>!document.body.classList.contains('my-messages-right'), callback: function f(){ document.body.classList.toggle('my-messages-right'); } },{ label: "Bottom-up chat", disable: true, 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); Chat.settings.set('images-inline', !v); F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+"."); } }]; /** Rebuild the menu each time it's shown so that the toggles can show their current values. */ settingsPopup.options.refresh = function(){ D.clearElement(this.e); settingsOps.forEach(function(op){ if(op.disable) return; const line = D.addClass(D.span(), 'menu-entry'); const btn = D.append(D.addClass(D.span(), 'button'), op.label); D.attr(btn, 'role', 'button'); const callback = function(ev){ settingsPopup.hide(); op.callback.call(this,ev); }; |
︙ | ︙ | |||
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 */; })(); | > > | 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 888 889 890 | 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.
︙ | ︙ | |||
1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 | 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; min-width: 9em /*avoid unsightly "underlap" with the neighboring .message-widget-tab element*/; white-space: pre-wrap/*needed for multi-line edits*/; } body.chat.monospace-messages .message-widget-content, body.chat.monospace-messages textarea, body.chat.monospace-messages input[type=text]{ font-family: monospace; } /* User name and timestamp (a LEGEND-like element) */ body.chat .message-widget .message-widget-tab { border-radius: 0.25em 0.25em 0 0; | > | | 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 | 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; min-width: 9em /*avoid unsightly "underlap" with the neighboring .message-widget-tab element*/; white-space: pre-wrap/*needed for multi-line edits*/; flex: 1 1 auto; } body.chat.monospace-messages .message-widget-content, body.chat.monospace-messages textarea, body.chat.monospace-messages input[type=text]{ font-family: monospace; } /* User name and timestamp (a LEGEND-like element) */ body.chat .message-widget .message-widget-tab { border-radius: 0.25em 0.25em 0 0; margin: 0.25em 0.25em 0em 0.15em; padding: 0 0.5em 0.15em 0.5em; cursor: pointer; white-space: nowrap; } body.chat .fossil-tooltip.help-buttonlet-content { font-size: 80%; } |
︙ | ︙ | |||
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 | 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 { | > > > > > > > < > > > > > > > > > > > | < < > < < < < < < < | 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 1695 1696 1697 1698 1699 1700 1701 | 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; justify-content: flex-start; } 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 { padding: 0; display: flex; flex-direction: column; align-items: stretch; /*max-height: 85vh*/ /* rough approximate */ /* ^^^^ This breaks Safari badly - we'll have to calc this from JS */; } 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*/; /* ^^^^ Safari hates this: we re-calc it in JS */ /* Some skins set margins and a max-width on div.content, but we needn't(?) honor those in chat-only mode. */ margin: 0; width: 100%; max-width: 100%; } /* 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: 1 1 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; |
︙ | ︙ | |||
1732 1733 1734 1735 1736 1737 1738 1739 | /* Widget holding the details of a selected/dropped file/image. */ body.chat #chat-drop-details { flex: 0 1 auto; padding: 0.5em 1em; margin-left: 0.5em; white-space: pre; font-family: monospace; } | > > > > > > | 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 | /* Widget holding the details of a selected/dropped file/image. */ body.chat #chat-drop-details { flex: 0 1 auto; padding: 0.5em 1em; margin-left: 0.5em; white-space: pre; font-family: monospace; } body.chat #chat-drop-details img { /* If we don't artificially limit the img preview size, they can be so big that the user cannot navigate to the buttons. */ max-height: 40vh; max-width: 40vw; } |