diff options
Diffstat (limited to 'view')
-rw-r--r-- | view/css/conversation.css | 96 | ||||
-rw-r--r-- | view/js/main.js | 388 | ||||
-rw-r--r-- | view/tpl/conv_frame.tpl | 3 | ||||
-rw-r--r-- | view/tpl/conv_item.tpl | 19 | ||||
-rw-r--r-- | view/tpl/jot-header.tpl | 64 | ||||
-rw-r--r-- | view/tpl/js_strings.tpl | 1 | ||||
-rw-r--r-- | view/tpl/notifications_widget.tpl | 1 | ||||
-rw-r--r-- | view/tpl/pinned_item.tpl | 2 |
8 files changed, 499 insertions, 75 deletions
diff --git a/view/css/conversation.css b/view/css/conversation.css index d8a893b0e..38970bb45 100644 --- a/view/css/conversation.css +++ b/view/css/conversation.css @@ -193,18 +193,32 @@ a.wall-item-name-link { color: var(--bs-primary); } -.item-highlight { +.item-highlight, +.item-indent, +.wall-item-comment, +.thread-wrapper { position: relative; } -.item-highlight:after { +.item-fade-in { + opacity: 0; + transform: scale(.7) translateY(-20px); + transition: opacity 0.3s ease-out, transform 0.3s ease-out; +} +.item-fade-in.show { + opacity: 1; + transform: scale(1) translateY(0); +} + +.item-highlight::after { content: ''; position: absolute; pointer-events: none; box-shadow: inset .15rem 0 0 0 var(--hz-item-highlight); - height: 100%; width: 100%; top: 0; + bottom: 0; +/* margin: var(--bs-border-radius) 0 var(--bs-border-radius) 0; */ } .item-highlight-fade { @@ -217,6 +231,82 @@ a.wall-item-name-link { to { background-color: none; } } +.item-indent { + /* This must be equal to .item.indent:before width */ + padding-left: .75rem; +} + +.item-indent::before { + content: ''; + position: absolute; + height: 100%; + width: .75rem; + top: 0; + left: 0; + border-color: var(--hz-item-indent, var(--bs-border-color)); + border-radius: 0 1.5rem 0 0; + border-style: solid; + border-width: 1px 1px 0 0; +} + +.wall-item-comment.expanded { + opacity: 0.5; +} + +.wall-item-comment.collapsed::before { + content: '\2212'; + position: absolute; + left: .85rem; + top: .45rem; +} + +.wall-item-backdrop::before { + content: ''; + position: absolute; + inset: 0; + backdrop-filter: saturate(0%) brightness(90%); + z-index: 1; +} + +.wall-item-expanded { + position: relative; + border-radius: var(--bs-border-radius); + background-color: var(--bs-body-bg); + z-index: 2; +} + +.wall-item-expanded::after { + content: ''; + position: absolute; + pointer-events: none; + box-shadow: 0 0 0 1px var(--bs-border-color); + border-radius: var(--bs-border-radius); + width: 100%; + top: 0; + bottom: 0; +} + +.wall-item-expanded::before { + content: var(--hz-wall-item-expanded-before-content, "");; + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -50%); + background: var(--bs-info-bg-subtle); + color: var(--bs-info-text-emphasis); + border-radius: var(--bs-border-radius-pill); + padding: 0.35em 0.65em; + font-size: 0.6em; + font-weight: 700; + z-index: 3; + text-transform: uppercase; +} + +.load-more:hover .load-more-dots { + background: var(--bs-light-bg-subtle); + padding: 0 0.35em; +} + /* comment_item */ diff --git a/view/js/main.js b/view/js/main.js index 7043c8577..5bf7234aa 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -28,6 +28,8 @@ var expanded_items = []; var updateTimeout = []; const singlethread_modules = ['display', 'hq']; const redirect_modules = ['display', 'notify']; +let b64mids = []; + var page_cache = {}; @@ -87,6 +89,146 @@ $(document).ready(function() { } }); + document.addEventListener('click', function(event) { + // Only handle clicks on .wall-item-reaction or its children + const target = event.target.closest('.wall-item-reaction'); + if (!target) return; + + let doRequest = true; + const isUserClick = event.isTrusted; + + // Destructure relevant data attributes + const { itemId: id, itemMid: mid, itemParent: parentId, itemUuid: uuid, itemVerb: verb } = target.dataset; + const isCommentBtn = target.classList.contains('wall-item-comment'); + + if (isCommentBtn) { + if (id === parentId) { + // Handle blog mode + target.classList.add('disabled'); + document.getElementById(`load-more-progress-wrapper-${id}`).classList.remove('d-none'); + document.getElementById(`load-more-${id}`).classList.remove('d-none') + request(id, mid, 'load', parentId, uuid, isUserClick); + return; + } + + // Get relevant DOM elements + const threadWrapper = document.getElementById(`thread-wrapper-${id}`); + const parentWrapper = document.getElementById(`thread-wrapper-${parentId}`); + const subThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`); + const parentSubThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${parentId}`); + + // Query related sub-thread and highlight elements + const parentIndentedThreads = document.querySelectorAll(`#wall-item-sub-thread-wrapper-${parentId} .wall-item-sub-thread-wrapper.item-indent`); + + let ancestorIds = []; + + doRequest = !subThreadWrapper.children.length; + + // Set visual styles using UUID + subThreadWrapper.style.setProperty('--hz-item-indent', stringToHslColor(uuid)); + threadWrapper.style.setProperty('--hz-item-highlight', stringToHslColor(uuid)); + threadWrapper.style.setProperty('--hz-wall-item-expanded-before-content', '"' + aStr.dblclick_to_exit_zoom + '"'); + + // Clear previous highlights + parentSubThreadWrapper.querySelectorAll('.thread-wrapper.item-highlight').forEach(el => el.classList.remove('item-highlight')); + + if (isUserClick && parentIndentedThreads.length === 0 && !subThreadWrapper.children.length) { + // Handle first-time expansion and highlighting but not for toplevels (blog mode) + threadWrapper.classList.add('item-highlight'); + } else { + // Handle indentation and zooming + let ancestor = subThreadWrapper.parentElement; + ancestorIds.push(ancestor.id.slice(15)); // thread-wrapper-1234 + + while (ancestor) { + if (ancestor.classList.contains('item-indent') && ancestor.classList.contains('wall-item-sub-thread-wrapper')) { + ancestorIds.push(ancestor.parentElement.id.slice(15)); + } + ancestor = ancestor.parentElement; + } + + ancestorIds.reverse(); + + if (ancestorIds.length > 3) { + // Handle zooming in + let firstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[0]); + let firstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[0]); + + firstWrapper.querySelector('.wall-item-comment').classList.remove('indented'); + firstWrapper.classList.remove('wall-item-expanded', 'shadow'); + firstSubWrapper.classList.remove('item-indent'); + + let newFirstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[1]) + let newFirstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[1]) + + newFirstWrapper.classList.add('wall-item-expanded', 'shadow'); + parentWrapper.classList.add('wall-item-backdrop'); + + // Exit zoom on double-click + newFirstWrapper.addEventListener('dblclick', function() { + parentWrapper.querySelectorAll('.wall-item-comment.indented').forEach(el => el.classList.remove('indented')); + parentWrapper.querySelectorAll('.wall-item-comment.collapsed').forEach(el => el.classList.remove('collapsed')); + parentWrapper.classList.remove('wall-item-backdrop'); + parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-indent').forEach(el => el.classList.remove('item-indent')); + parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.d-none').forEach(el => el.classList.remove('d-none')); + parentWrapper.querySelectorAll('.thread-wrapper.wall-item-expanded').forEach(el => el.classList.remove('wall-item-expanded', 'shadow')); + }, { once: true }); + } + + // Toggle sub-thread visibility if indented + if (isUserClick && target.classList.contains('indented')) { + doRequest = false; + subThreadWrapper.classList.toggle('d-none'); + target.classList.toggle('collapsed'); + } + + // Indenting of already expanded but flattened items + if (isUserClick && subThreadWrapper.classList.contains('item-expanded') && !subThreadWrapper.classList.contains('item-indent')) { + doRequest = false; + + threadWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-expanded').forEach(function (el, i) { + el.classList.add('item-indent'); + + el.querySelectorAll('.wall-item-comment.expanded').forEach(function (el, i) { + el.classList.add('collapsed', 'indented'); + }); + + // Collapse everything below the first level + if (i > 0) { + el.classList.add('d-none'); + } + }); + } + + // Indent the subthread + subThreadWrapper.classList.add('item-indent', 'item-expanded'); + + // Mark as indented after visibility toggle + target.classList.add('indented'); + } + + // Mark as expanded + target.classList.add('expanded'); + } + + if (doRequest) { + request(id, mid, verb, parentId, uuid, isUserClick); + } + }); + + document.addEventListener('click', function(event) { + const targetElement = event.target.closest('.dropdown-item-expand'); + if (!targetElement) return; + + event.preventDefault(); + + const id = targetElement.dataset.itemId; + const subWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`); + + subWrapper.innerHTML = ''; + autoExpand(id); + }); + // @hilmar |-> if ( typeof(window.tao) == 'undefined' ) { window.tao = {}; @@ -752,12 +894,22 @@ function updateConvItems(mode, data) { let data_json = JSON.parse(elem.dataset.b64mids); - // Also highlight the thread parent - if (data_json.includes(bParam_mid) && elem.parentNode.classList.contains('wall-item-sub-thread-wrapper')) { - if (!elem.parentNode.parentNode.classList.contains('toplevel_item')) { + if (elem.parentNode.classList.contains('wall-item-sub-thread-wrapper') && elem.parentNode.children.length) { + // Set the highlight state + if (data_json.includes(bParam_mid) && !elem.parentNode.parentNode.classList.contains('toplevel_item')) { elem.parentNode.parentNode.classList.add('item-highlight'); - document.documentElement.style.setProperty('--hz-item-highlight', stringToHlsColor(JSON.parse(elem.parentNode.parentNode.dataset.b64mids)[0])); + document.documentElement.style.setProperty('--hz-item-highlight', stringToHslColor(JSON.parse(elem.parentNode.parentNode.dataset.b64mids)[0])); } + + let elemSubThreadWrapper = elem.querySelector('.wall-item-sub-thread-wrapper'); + let elemCommentButton = elem.querySelector('.wall-item-comment'); + + // Set the button and sub-thread-wrapper state + if (elemCommentButton && elemSubThreadWrapper.children.length) { + elemCommentButton.classList.add('expanded'); + } + + elem.parentNode.classList.add('item-expanded'); } b64mids.push(...data_json); @@ -1292,27 +1444,64 @@ function justifyPhotosAjax(id) { $('#' + id).justifiedGallery('norewind').on('jg.complete', function(e){ justifiedGalleryActive = false; }); } -function request(id, mid, verb, parent, uuid) { +function request(id, mid, verb, parent, uuid, userClick) { - const loading = document.getElementById('like-rotator-' + id); - loading.style.display = 'block'; + if (verb === 'load') { + const dots = document.getElementById('load-more-dots-' + parent); + dots.classList.add('jumping-dots'); - if (verb === 'comment') { + const parent_sub = document.getElementById('wall-item-sub-thread-wrapper-' + parent); + const offset = parent_sub.children.length; - if (singlethread_modules.indexOf(module) !== -1) { - let stateObj = { b64mid: uuid }; - history.pushState(stateObj, '', module + '/' + uuid); - } + fetch('/request?offset=' + offset + '&verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module) + .then(response => response.json()) + .then(obj => { + let parser = new DOMParser(); + let doc = parser.parseFromString(obj.html, 'text/html'); + let b64mids = []; + + doc.querySelectorAll('.thread-wrapper').forEach(function (e) { + let data = JSON.parse(e.dataset.b64mids); + b64mids.push(...data); + }); + + imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () { + injectWithAnimation('wall-item-sub-thread-wrapper-' + parent, doc); + dots.classList.remove('jumping-dots'); + + const loadmore_progress = document.getElementById('load-more-progress-' + parent); + loadmore_progress.style.width = Math.round(100 * parent_sub.children.length / loadmore_progress.dataset.commentsTotal) + '%'; + + if (Number(parent_sub.children.length) === Number(loadmore_progress.dataset.commentsTotal)) { + const loadmore = document.getElementById('load-more-' + parent); + loadmore.remove(); + } - document.querySelectorAll('.thread-wrapper.item-highlight').forEach(el => { - el.classList.remove('item-highlight'); - el.style.boxShadow = ''; + updateRelativeTime('.autotime'); + collapseHeight(); + + document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); + document.dispatchEvent(new Event('hz:sse_bs_counts')); + }); + + }) + .catch(error => { + console.error('Error fetching data:', error); }); - const wrapper = document.getElementById('thread-wrapper-' + id); - if (!wrapper.classList.contains('toplevel_item')) { - wrapper.classList.add('item-highlight'); - document.documentElement.style.setProperty('--hz-item-highlight', stringToHlsColor(uuid)); + return; + } + + const loading = document.getElementById('like-rotator-' + id); + + if (userClick) { + loading.style.display = 'block'; + } + + if (verb === 'comment') { + if (userClick && singlethread_modules.indexOf(module) !== -1) { + let stateObj = { b64mid: uuid }; + history.pushState(stateObj, '', module + '/' + uuid); } fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module) @@ -1320,7 +1509,6 @@ function request(id, mid, verb, parent, uuid) { .then(obj => { let parser = new DOMParser(); let doc = parser.parseFromString(obj.html, 'text/html'); - let b64mids = []; doc.querySelectorAll('.thread-wrapper').forEach(function (e) { let data = JSON.parse(e.dataset.b64mids); @@ -1328,13 +1516,15 @@ function request(id, mid, verb, parent, uuid) { }); imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () { - injectWithAnimation('wall-item-sub-thread-wrapper-' + id, obj.html); + injectWithAnimation('wall-item-sub-thread-wrapper-' + id, doc, true); updateRelativeTime('.autotime'); - loading.style.display = 'none'; collapseHeight(); - document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); - document.dispatchEvent(new Event('hz:sse_bs_counts')); + if (userClick) { + loading.style.display = 'none'; + document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); + document.dispatchEvent(new Event('hz:sse_bs_counts')); + } }); }) @@ -1377,32 +1567,134 @@ function request(id, mid, verb, parent, uuid) { } -function injectWithAnimation(container, html) { - const target = document.getElementById(container); - target.innerHTML = html; +function injectWithAnimation(containerId, parsedDoc, overwrite = false) { + const container = document.getElementById(containerId); + if (!container) return; + if (overwrite) container.innerHTML = ''; - target.animate([ - { opacity: 0, transform: 'translateY(-20px)' }, - { opacity: 1, transform: 'translateY(0)' } - ], { - duration: 300, - easing: 'ease-out' - }); + const newElements = Array.from(parsedDoc.body.children); - // For children animation - Array.from(target.children).forEach((el, i) => { - el.animate([ - { opacity: 0, transform: 'scale(.7)' }, - { opacity: 1, transform: 'scale(1)' } - ], { - duration: 300, - delay: i * 50, - fill: 'both', - easing: 'ease-out' - }); - }); + for (let i = newElements.length - 1; i >= 0; i--) { + const el = newElements[i].cloneNode(true); + el.classList.add('item-fade-in'); + container.insertBefore(el, container.firstChild); + + // Remove classes after transition ends + const onTransitionEnd = (event) => { + el.classList.remove('item-fade-in', 'show'); + el.removeEventListener('transitionend', onTransitionEnd); + }; + el.addEventListener('transitionend', onTransitionEnd); + + setTimeout(() => { + el.classList.add('show'); + }, (newElements.length - 1 - i) * 30); + } } +const autoExpand = (function () { + const clickedElements = new Set(); // Stores clicked button references + + // We wait 10 seconds for images. Set the timeout here slightly higher. + function waitForElement(selector, timeout = 11000) { + return new Promise((resolve, reject) => { + // Check if the element already exists + const element = document.querySelector(selector); + + if (element) { + resolve(element); + return; + } + + // Set up a timeout to reject the promise if the element doesn't appear in time + const timer = setTimeout(() => { + observer.disconnect(); + reject(new Error(`Element "${selector}" not found within ${timeout}ms`)); + }, timeout); + + // Create a MutationObserver to watch for DOM changes + const observer = new MutationObserver(() => { + const el = document.querySelector(selector); + if (el) { + clearTimeout(timer); + observer.disconnect(); + resolve(el); + } + }); + + // Start observing the document for changes + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + }); + } + + async function autoExpand(id) { + const loading = document.getElementById('like-rotator-' + id); + let iteration = 0; + const maxIterations = 3; + clickedElements.clear(); + + try { + // Step 1: Ensure initial button is clicked + const initBtnSelector = '#wall-item-comment-' + id; + const initBtn = await waitForElement(initBtnSelector); + + if (!clickedElements.has(initBtn)) { + initBtn.click(); + clickedElements.add(initBtn); + iteration++; + } + + // Step 2: Loop until no new buttons are found + let newButtonsFound; + + const commentSelector = `#wall-item-sub-thread-wrapper-${id} .thread-wrapper`; + const commentBtnSelector = `#wall-item-sub-thread-wrapper-${id} .wall-item-comment`; + + do { + newButtonsFound = false; + + // Wait for any comment to appear + await waitForElement(commentSelector); + + const expandButtons = document.querySelectorAll(commentBtnSelector); + + for (const btn of expandButtons) { + if (!clickedElements.has(btn)) { + btn.click(); + clickedElements.add(btn); + newButtonsFound = true; + // Optional: await waitForElement(...) to wait for new content + } + } + + // Wait between iterations to allow UI to update + if (newButtonsFound) { + iteration++; + await new Promise(res => setTimeout(res, 700)); + } + + } while (newButtonsFound && iteration < maxIterations); + + console.log('Replies loaded!'); + + loading.style.display = 'none'; + + document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); + document.dispatchEvent(new Event('hz:sse_bs_counts')); + + } catch (error) { + loading.style.display = 'none'; + console.error("autoExpand failed:", error.message); + } + } + + return autoExpand; +})(); + + function stringToHexColor(str) { let hash = 0; for (let i = 0; i < str.length; i++) { @@ -1416,11 +1708,11 @@ function stringToHexColor(str) { return color; } -function stringToHlsColor(str) { +function stringToHslColor(str) { let stringUniqueHash = [...str].reduce((acc, char) => { return char.charCodeAt(0) + ((acc << 5) - acc); }, 0); - return `hsl(${stringUniqueHash % 360}, 95%, 70%)`; + return `hsl(${stringUniqueHash % 360}, 65%, 65%)`; } function dolike(ident, verb) { diff --git a/view/tpl/conv_frame.tpl b/view/tpl/conv_frame.tpl index b40585a46..0acf7b24c 100644 --- a/view/tpl/conv_frame.tpl +++ b/view/tpl/conv_frame.tpl @@ -30,6 +30,9 @@ <div class="modal-body list-group" id="reactions_body"> {{$wait}} </div> + <div class="ps-3 pe-3" id="reactions_extra_top"></div> + <div class="ps-3 pe-3" id="reactions_extra_middle"></div> + <div class="ps-3 pe-3" id="reactions_extra_bottom"></div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> diff --git a/view/tpl/conv_item.tpl b/view/tpl/conv_item.tpl index 6d8aa6abc..c97c53c41 100644 --- a/view/tpl/conv_item.tpl +++ b/view/tpl/conv_item.tpl @@ -1,4 +1,4 @@ -{{if $item.comment_firstcollapsed}} +{{if !$item.threaded && $item.comment_firstcollapsed}} <div id="hide-comments-outer-{{$item.parent}}" class="hide-comments-outer fakelink small" onclick="showHideComments({{$item.id}});"> <i id="hide-comments-icon-{{$item.id}}" class="bi bi-chevron-down align-middle hide-comments-icon"></i> <span id="hide-comments-label-{{$item.id}}" class="hide-comments-label align-middle" data-expanded="{{$item.collapse_comments}}" data-collapsed="{{$item.expand_comments}}">{{$item.expand_comments}}</span>{{if !$item.threaded}} <span id="hide-comments-total-{{$item.id}}" class="hide-comments-label align-middle">{{$item.num_comments}}</span>{{/if}} </div> @@ -111,7 +111,7 @@ <div class="wall-item-tools-left hstack gap-1" id="wall-item-tools-left-{{$item.id}}"> {{foreach $item.responses as $verb=>$response}} {{if !($verb == 'comment' && (($item.toplevel && !$item.blog_mode) || $response.count == 0))}} - <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.observer_activity.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}" onclick="request({{$item.id}}, '{{$item.rawmid}}', '{{$verb}}', {{$item.parent}}, '{{$item.mid}}'); return false;" id="wall-item-{{$verb}}-{{$item.id}}"> + <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.observer_activity.$verb}} link-secondary{{/if}} wall-item-reaction wall-item-{{$response.button.class}}" id="wall-item-{{$verb}}-{{$item.id}}" data-item-id="{{$item.id}}" data-item-mid="{{$item.rawmid}}" data-item-verb="{{$verb}}" data-item-parent="{{$item.parent}}" data-item-uuid="{{$item.mid}}" data-item-reaction-count="{{$response.count}}"> <i class="bi bi-{{$response.button.icon}} generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}} </button> {{/if}} @@ -191,6 +191,9 @@ {{if $item.star}} <a class="dropdown-item" href="#" onclick="dostar({{$item.id}}); return false;"><i id="starred-{{$item.id}}" class="generic-icons-nav bi{{if $item.star.isstarred}} starred bi-star-fill{{else}} unstarred bi-star{{/if}}" title="{{$item.star.toggle}}"></i>{{$item.star.toggle}}</a> {{/if}} + {{if $item.expand}} + <a class="dropdown-item dropdown-item-expand" href="#" data-item-id="{{$item.id}}" data-item-uuid="{{$item.mid}}"><i id="expand-{{$item.id}}" class="generic-icons-nav bi bi-arrows-angle-expand" title="{{$item.expand}}"></i>{{$item.expand}}</a> + {{/if}} {{if $item.thread_action_menu}} {{foreach $item.thread_action_menu as $mitem}} <a class="dropdown-item" {{if $mitem.href}}href="{{$mitem.href}}"{{/if}} {{if $mitem.action}}onclick="{{$mitem.action}}"{{/if}} {{if $mitem.title}}title="{{$mitem.title}}"{{/if}} ><i class="generic-icons-nav bi bi-{{$mitem.icon}}"></i>{{$mitem.title}}</a> @@ -209,7 +212,7 @@ {{/if}} {{if $item.settings}} <div class="dropdown-divider"></div> - <a class="dropdown-item conversation-settings-link" href="" data-bs-toggle="modal" data-bs-target="#conversation_settings">{{$item.settings}}</a> + <a class="dropdown-item conversation-settings-link" href="#" data-bs-toggle="modal" data-bs-target="#conversation_settings">{{$item.settings}}</a> {{/if}} </div> </div> @@ -219,6 +222,14 @@ </div> </div> {{if $item.thread_level == 1}} + {{if $item.toplevel && $item.load_more && $item.threaded}} + <div id="load-more-progress-wrapper-{{$item.id}}" class="progress{{if $item.blog_mode}} d-none{{/if}}" role="progressbar" aria-valuenow="{{$item.comments_total_percent}}" aria-valuemin="0" aria-valuemax="100" style="height: 1px"> + <div id="load-more-progress-{{$item.id}}" class="progress-bar bg-info" style="width: {{$item.comments_total_percent}}%; margin-left: auto; margin-right: auto;" data-comments-total="{{$item.comments_total}}"></div> + </div> + <div id="load-more-{{$item.id}}" class="load-more text-center text-secondary cursor-pointer{{if $item.blog_mode}} d-none{{/if}}" title="{{$item.load_more_title}}" onclick="request(0, '{{$item.rawmid}}', 'load', {{$item.parent}}, ''); return false;"> + <span id="load-more-dots-{{$item.id}}" class="load-more-dots rounded"><span class="dot-1">-</span> <span class="dot-2">-</span> <span class="dot-3">-</span></span> + </div> + {{/if}} <div id="wall-item-sub-thread-wrapper-{{$item.id}}" class="wall-item-sub-thread-wrapper"> {{foreach $item.children as $child}} {{include file="{{$child.template}}" item=$child}} @@ -238,6 +249,6 @@ </div> {{/if}} </div> -{{if $item.comment_lastcollapsed}} +{{if !$item.threaded && $item.comment_lastcollapsed}} </div> {{/if}} diff --git a/view/tpl/jot-header.tpl b/view/tpl/jot-header.tpl index b01fa6609..b9b8b3012 100644 --- a/view/tpl/jot-header.tpl +++ b/view/tpl/jot-header.tpl @@ -11,6 +11,8 @@ var activeCommentID = 0; var activeCommentText = ''; + var isModalAction = false; + var postSaveTimer = null; function initEditor(cb){ @@ -414,19 +416,41 @@ // Fetch the photo album list getPhotoAlbumList(); - // Remove any existing click event listeners on the modal body - const modalBodyAlbumDialog = document.getElementById('embedPhotoModalBodyAlbumDialog'); - modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners + if (activeCommentID) { + const modalEl = document.getElementById('reactions'); + isModalAction = modalEl.classList.contains('show'); + } - // Show the modal - const modalEl = document.getElementById('embedPhotoModal'); - const modal = new bootstrap.Modal(modalEl); - modal.show(); + if (isModalAction) { + // Remove any existing click event listeners on the modal body + const modalBodyAlbumDialog = document.getElementById('reactions_extra_middle'); + modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners - // Reset activeCommentID when the modal is closed - modalEl.addEventListener('hide.bs.modal', event => { - activeCommentID = 0; - }); + const modalEl = document.getElementById('reactions'); + + // Reset activeCommentID when the modal is closed + modalEl.addEventListener('hide.bs.modal', event => { + activeCommentID = 0; + isModalAction = false; + document.getElementById('reactions_extra_middle').innerHTML = ''; + document.getElementById('reactions_extra_top').innerHTML = ''; + }); + } + else { + // Remove any existing click event listeners on the modal body + const modalBodyAlbumDialog = document.getElementById('embedPhotoModalBodyAlbumDialog'); + modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners + + // Show the modal + const modalEl = document.getElementById('embedPhotoModal'); + const modal = new bootstrap.Modal(modalEl); + modal.show(); + + // Reset activeCommentID when the modal is closed + modalEl.addEventListener('hide.bs.modal', event => { + activeCommentID = 0; + }); + } }; const choosePhotoFromAlbum = (album) => { @@ -442,15 +466,16 @@ .then(data => { if (data.status) { - const modalLabel = document.getElementById('embedPhotoModalLabel'); - const modalBody = document.getElementById('embedPhotoModalBodyAlbumDialog'); + const modalLabel = isModalAction ? document.getElementById('reactions_extra_top') : document.getElementById('embedPhotoModalLabel'); + const modalBody = isModalAction ? document.getElementById('reactions_extra_middle') : document.getElementById('embedPhotoModalBodyAlbumDialog'); - modalLabel.innerHTML = '{{$modalchooseimages}}'; + modalLabel.innerHTML = '<h3>{{$modalchooseimages}}</h3>'; modalBody.innerHTML = '<div><div class="nav nav-pills flex-column"><li class="nav-item"><a class="nav-link" href="#" onclick="initializeEmbedPhotoDialog(); return false;"><i class="bi bi-chevron-left"></i> {{$modaldiffalbum}}</a></li></div><br></div>'; modalBody.innerHTML += data.content; // Make sure the loaded script is executed const scripts = modalBody.querySelectorAll('script'); + scripts.forEach(script => { const scriptContent = script.textContent || script.innerText; eval(scriptContent); // Execute the script @@ -475,7 +500,6 @@ .then(ddata => { if (ddata.status) { addActiveEditorText(ddata.photolink); - preview_post(); } else { console.error("{{$modalerrorlink}}: " + ddata.errormsg); } @@ -502,10 +526,10 @@ .then(data => { if (data.status) { const albums = data.albumlist; - const modalLabel = document.getElementById('embedPhotoModalLabel'); - const modalBodyList = document.getElementById('embedPhotoModalBodyAlbumList'); + const modalLabel = isModalAction ? document.getElementById('reactions_extra_top') : document.getElementById('embedPhotoModalLabel'); + const modalBodyList = isModalAction ? document.getElementById('reactions_extra_middle') : document.getElementById('embedPhotoModalBodyAlbumList'); - modalLabel.innerHTML = '{{$modalchoosealbum}}'; + modalLabel.innerHTML = '<h3>{{$modalchoosealbum}}</h3>'; modalBodyList.innerHTML = '<ul class="nav nav-pills flex-column"></ul>'; albums.forEach(album => { @@ -551,7 +575,9 @@ textarea.value = currentText + data; textarea.focus(); textarea.click(); - preview_comment(activeCommentID); + if (!isModalAction) { + preview_comment(activeCommentID); + } } } else { addeditortext(data); diff --git a/view/tpl/js_strings.tpl b/view/tpl/js_strings.tpl index 2a374fdf8..0e438f450 100644 --- a/view/tpl/js_strings.tpl +++ b/view/tpl/js_strings.tpl @@ -36,6 +36,7 @@ 'pinned' : "{{$pinned}}", 'pin_item' : "{{$pin_item}}", 'unpin_item' : "{{$unpin_item}}", + 'dblclick_to_exit_zoom' : "{{$dblclick_to_exit_zoom}}", 'monthNames' : [ "{{$January}}","{{$February}}","{{$March}}","{{$April}}","{{$May}}","{{$June}}","{{$July}}","{{$August}}","{{$September}}","{{$October}}","{{$November}}","{{$December}}" ], 'monthNamesShort' : [ "{{$Jan}}","{{$Feb}}","{{$Mar}}","{{$Apr}}","{{$MayShort}}","{{$Jun}}","{{$Jul}}","{{$Aug}}","{{$Sep}}","{{$Oct}}","{{$Nov}}","{{$Dec}}" ], diff --git a/view/tpl/notifications_widget.tpl b/view/tpl/notifications_widget.tpl index 113660c7e..be66c00d4 100644 --- a/view/tpl/notifications_widget.tpl +++ b/view/tpl/notifications_widget.tpl @@ -88,6 +88,7 @@ } else { if (!document.hidden) { + sse_fallback(); sse_fallback_interval = setInterval(sse_fallback, updateInterval); } diff --git a/view/tpl/pinned_item.tpl b/view/tpl/pinned_item.tpl index b5b5c931e..db3a175da 100644 --- a/view/tpl/pinned_item.tpl +++ b/view/tpl/pinned_item.tpl @@ -102,7 +102,7 @@ <div class="p-2 wall-item-tools d-flex justify-content-between"> <div class="wall-item-tools-left hstack gap-1" id="pinned-item-tools-left-{{$id}}"> {{foreach $responses as $verb=>$response}} - <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$observer_activity.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}" onclick="request({{$id}}, '{{$rawmid}}', '{{$verb}}', {{$parent}}, '{{$mid}}'); return false;" id="pinned-item-{{$verb}}-{{$id}}"> + <button type="button" title="{{$response.count}} {{$response.button.label}}" class="disabled btn btn-sm btn-link{{if !$observer_activity.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}" id="pinned-item-{{$verb}}-{{$id}}"> <i class="bi bi-{{$response.button.icon}} generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}} </button> {{/foreach}} |