diff options
Diffstat (limited to 'view')
-rw-r--r-- | view/css/conversation.css | 114 | ||||
-rw-r--r-- | view/css/mod_help.css | 2 | ||||
-rw-r--r-- | view/js/autocomplete.js | 2 | ||||
-rw-r--r-- | view/js/main.js | 644 | ||||
-rw-r--r-- | view/pdl/mod_dreport.pdl | 7 | ||||
-rw-r--r-- | view/php/default.php | 20 | ||||
-rw-r--r-- | view/theme/redbasic/css/style.css | 5 | ||||
-rw-r--r-- | view/theme/redbasic/js/redbasic.js | 13 | ||||
-rw-r--r-- | view/theme/redbasic/schema/Focus-Boxy.css | 8 | ||||
-rw-r--r-- | view/tpl/app.tpl | 12 | ||||
-rw-r--r-- | view/tpl/breadcrumb.tpl | 2 | ||||
-rw-r--r-- | view/tpl/comment_item.tpl | 12 | ||||
-rw-r--r-- | view/tpl/connection_template.tpl | 3 | ||||
-rw-r--r-- | view/tpl/conv_frame.tpl | 18 | ||||
-rw-r--r-- | view/tpl/conv_item.tpl | 91 | ||||
-rw-r--r-- | view/tpl/direntry.tpl | 8 | ||||
-rw-r--r-- | view/tpl/dreport.tpl | 2 | ||||
-rw-r--r-- | view/tpl/head.tpl | 1 | ||||
-rw-r--r-- | view/tpl/jot-header.tpl | 65 | ||||
-rw-r--r-- | view/tpl/js_strings.tpl | 3 | ||||
-rw-r--r-- | view/tpl/notifications_widget.tpl | 52 | ||||
-rw-r--r-- | view/tpl/pinned_item.tpl | 226 | ||||
-rw-r--r-- | view/tpl/search_item.tpl | 4 | ||||
-rw-r--r-- | view/tpl/settings_display.tpl | 2 |
24 files changed, 951 insertions, 365 deletions
diff --git a/view/css/conversation.css b/view/css/conversation.css index fb26c7e3f..38970bb45 100644 --- a/view/css/conversation.css +++ b/view/css/conversation.css @@ -193,8 +193,118 @@ a.wall-item-name-link { color: var(--bs-primary); } -.item-highlight { - border-left: 0.2rem solid var(--bs-primary); +.item-highlight, +.item-indent, +.wall-item-comment, +.thread-wrapper { + position: relative; +} + +.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); + width: 100%; + top: 0; + bottom: 0; +/* margin: var(--bs-border-radius) 0 var(--bs-border-radius) 0; */ +} + +.item-highlight-fade { + background-color: var(--bs-primary-bg-subtle); + animation: fadeBgOut .35s .7s backwards; +} + +@keyframes fadeBgOut { + from { background-color: var(--bs-primary-bg-subtle); } + 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/css/mod_help.css b/view/css/mod_help.css index d596048c9..b77b65e0e 100644 --- a/view/css/mod_help.css +++ b/view/css/mod_help.css @@ -12,7 +12,7 @@ } #doco-content img { - width: 100%; + max-width: 100%; } #region_1 .widget ul ul { diff --git a/view/js/autocomplete.js b/view/js/autocomplete.js index 7d6ddb1c4..ad8e04050 100644 --- a/view/js/autocomplete.js +++ b/view/js/autocomplete.js @@ -38,7 +38,7 @@ function contact_format(item) { var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick); if(typeof desc === 'undefined') desc = ''; if(desc) desc = ' ('+desc+')'; - return "<div class='dropdown-item dropdown-notification lh-sm text-truncate' title='{4}'><img class='menu-img-2' src='{1}' loading='lazy'><strong>{2}</strong><br><small class='opacity-75'>{4}</small></div>".format(item.taggable, item.photo, item.name, desc, typeof(item.link) !== 'undefined' ? item.link : desc.replace('(','').replace(')','')); + return "<div class='dropdown-item dropdown-notification lh-sm text-truncate' title='{4}'><img class='menu-img-2' src='{1}' loading='lazy'><strong>{2}</strong><br><small class='text-body-secondary'>{4}</small></div>".format(item.taggable, item.photo, item.name, desc, typeof(item.link) !== 'undefined' ? item.link : desc.replace('(','').replace(')','')); } else return ""; diff --git a/view/js/main.js b/view/js/main.js index 43f0333ed..5bf7234aa 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -26,6 +26,10 @@ var followUpPageLoad = false; var window_needs_alert = true; var expanded_items = []; var updateTimeout = []; +const singlethread_modules = ['display', 'hq']; +const redirect_modules = ['display', 'notify']; +let b64mids = []; + var page_cache = {}; @@ -85,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 = {}; @@ -149,8 +293,6 @@ $(document).ready(function() { let notify_id = this.dataset.notify_id; let path = $(this)[0].pathname.split('/')[1]; let stateObj = { b64mid: b64mid }; - let singlethread_modules = ['display', 'hq']; - let redirect_modules = ['display', 'notify']; if (!b64mid && !notify_id) { return; @@ -329,6 +471,13 @@ function handle_comment_form(e) { var commentSaveTimer = null; var emptyCommentElm = form.find('.comment-edit-text').attr('id'); var convId = emptyCommentElm.replace('comment-edit-text-',''); + + // in case parent input is set use it as convId + const parentInputVal = form.find(':input[name=parent]').val(); + if (parentInputVal) { + convId = parentInputVal; + } + $('#' + emptyCommentElm).on('focusout',function(e){ if(commentSaveTimer) clearTimeout(commentSaveTimer); @@ -502,8 +651,11 @@ function showHideComments(id) { let isCollapsed = collapsedComments.style.display === 'none'; collapsedComments.style.display = isCollapsed ? '' : 'none'; - hideCommentsLabel.textContent = isCollapsed ? aStr.showfewer : aStr.showmore; - hideCommentsTotal.style.display = isCollapsed ? 'none' : ''; + hideCommentsLabel.textContent = isCollapsed ? hideCommentsLabel.dataset.expanded : hideCommentsLabel.dataset.collapsed; + + if (hideCommentsTotal) { + hideCommentsTotal.style.display = isCollapsed ? 'none' : ''; + } let oldClass = isCollapsed ? 'bi-chevron-down' : 'bi-chevron-up'; let newClass = isCollapsed ? 'bi-chevron-up' : 'bi-chevron-down'; @@ -740,7 +892,27 @@ function updateConvItems(mode, data) { } } - b64mids.push(...JSON.parse(elem.dataset.b64mids)); + let data_json = JSON.parse(elem.dataset.b64mids); + + 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', 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); }); document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); @@ -771,10 +943,12 @@ function updateConvItems(mode, data) { } imagesLoaded(document.querySelectorAll('.wall-item-body img, .wall-photo-item img'), function () { - collapseHeight(); if (bParam_mid && mode === 'replace') { scrollToItem(); } + else { + collapseHeight(); + } }); // reset rotators and cursors we may have set before reaching this place @@ -789,6 +963,7 @@ function updateConvItems(mode, data) { followUpPageLoad = true; + updateRelativeTime('.autotime'); } @@ -859,19 +1034,25 @@ function imagesLoaded(elements, callback) { // Iterate through images to add load and error event listeners images.forEach((img) => { - img.loading = 'eager'; // Preload the image + let new_img = new Image(); + new_img.src = img.src; - if (img.complete && img.naturalHeight > 0) { + if (new_img.complete && new_img.naturalHeight > 0) { // Image is already loaded, handle immediately - checkComplete(img.src); + // console.log(`Image cached: ${new_img.src}`); + checkComplete(new_img.src); } else { // Add event listeners for load and error events - img.addEventListener('load', () => checkComplete(img.src)); - img.addEventListener('error', () => { - console.log(`Image failed to load: ${img.src}`); - checkComplete(img.src); + new_img.addEventListener('load', () => { + // console.log(`Image loaded: ${new_img.src}`); + checkComplete(new_img.src) + }); + new_img.addEventListener('error', () => { + console.log(`Image failed to load: ${new_img.src}`); + checkComplete(new_img.src); }); } + }); } @@ -927,45 +1108,52 @@ function updateRelativeTime(selector) { } function scrollToItem() { - // auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode + // auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode - if (justifiedGalleryActive) return; + if (justifiedGalleryActive) return; - let submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg'); + let submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg'); - // Select all thread wrappers - let threadWrappers = document.querySelectorAll('.thread-wrapper'); + // Select all thread wrappers + let threadWrappers = document.querySelectorAll('.thread-wrapper'); - threadWrappers.forEach(thread => { - // Get the 'data-b64mids' attribute and check if it contains submid - let b64mids = thread.dataset.b64mids; + threadWrappers.forEach(thread => { + // Get the 'data-b64mids' attribute and check if it contains submid + let b64mids = thread.dataset.b64mids; - if (b64mids && b64mids.includes(submid) && !thread.classList.contains('toplevel_item')) { + if (b64mids && b64mids.includes(submid)) { + // Handle collapsed comments if any + let collapsedComments = document.querySelectorAll('.collapsed-comments'); + if (collapsedComments.length) { + let scrollToId = collapsedComments[0].id.substring(19); + showHideComments(scrollToId); + } - // Handle collapsed comments if any - let collapsedComments = document.querySelectorAll('.collapsed-comments'); - if (collapsedComments.length) { - let scrolltoid = collapsedComments[0].id.substring(19); - let collapsedComment = document.getElementById('collapsed-comments-' + scrolltoid); - let hideCommentsLabel = document.getElementById('hide-comments-label-' + scrolltoid); - let hideCommentsTotal = document.getElementById('hide-comments-total-' + scrolltoid); + collapseHeight(); - if (collapsedComment) collapsedComment.style.display = 'block'; - if (hideCommentsLabel) hideCommentsLabel.innerHTML = aStr.showfewer; - if (hideCommentsTotal) hideCommentsTotal.style.display = 'none'; - } + if (!thread.classList.contains('toplevel_item')) { + // Scroll to the target element + let navHeight = document.getElementById('navbar-top') ? document.getElementById('navbar-top').offsetHeight : 0; + window.scrollTo({ + top: getOffsetTopRelativeToBody(thread) - navHeight, + behavior: 'smooth' + }); + } - // Scroll to the target element - let navHeight = document.querySelector('nav') ? document.querySelector('nav').offsetHeight : 0; - window.scrollTo({ - top: thread.offsetTop - navHeight, - behavior: 'smooth' - }); + let id = thread.id.replace('thread-wrapper-', ''); + let content = document.getElementById('wall-item-content-wrapper-' + id); + content.classList.add('item-highlight-fade'); + } + }); +} - // Add highlight class - thread.classList.add('item-highlight'); - } - }); +function getOffsetTopRelativeToBody(element) { + let offsetTop = 0; + while (element) { + offsetTop += element.offsetTop; + element = element.offsetParent; + } + return offsetTop; } function collapseHeight() { @@ -1153,26 +1341,16 @@ function liveUpdate(notify_id) { var dready = new Date(); console.log('DATA ready in: ' + (dready - dstart)/1000 + ' seconds.'); - if(update_mode === 'update' || preloadImages) { - console.log('LOADING images...'); - imagesLoaded(data, function () { - var iready = new Date(); - console.log('IMAGES ready in: ' + (iready - dready)/1000 + ' seconds.'); - - page_load = false; - scroll_next = false; - updateConvItems(update_mode,data); + console.log('LOADING images...'); + imagesLoaded(data, function () { + var iready = new Date(); + console.log('IMAGES ready in: ' + (iready - dready)/1000 + ' seconds.'); - in_progress = false; - }); - } - else { page_load = false; scroll_next = false; updateConvItems(update_mode,data); in_progress = false; - } - + }); }); } @@ -1266,6 +1444,277 @@ function justifyPhotosAjax(id) { $('#' + id).justifiedGallery('norewind').on('jg.complete', function(e){ justifiedGalleryActive = false; }); } +function request(id, mid, verb, parent, uuid, userClick) { + + if (verb === 'load') { + const dots = document.getElementById('load-more-dots-' + parent); + dots.classList.add('jumping-dots'); + + const parent_sub = document.getElementById('wall-item-sub-thread-wrapper-' + parent); + const offset = parent_sub.children.length; + + 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(); + } + + 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); + }); + + 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) + .then(response => response.json()) + .then(obj => { + let parser = new DOMParser(); + let doc = parser.parseFromString(obj.html, 'text/html'); + + 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-' + id, doc, true); + updateRelativeTime('.autotime'); + collapseHeight(); + + if (userClick) { + loading.style.display = 'none'; + 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); + }); + } + else { + fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent) + .then(response => response.json()) + .then(obj => { + const modal = new bootstrap.Modal('#reactions'); + const modal_content = document.getElementById('reactions_body'); + const modal_title = document.getElementById('reactions_title'); + const modal_action = document.getElementById('reactions_action'); + modal_action.style.display = 'none'; + modal_title.innerHTML = obj.title; + modal_content.innerHTML = ''; + if (obj.action) { + modal_action.innerHTML = '<a href="#" onclick="' + obj.action + '(' + id + ',\'' + verb + '\'); return false;">' + obj.action_label + '</a>'; + modal_action.style.display = 'block'; + } + console.log(obj) + obj.result.forEach(e => { + let mod = ''; + if (e.item_blocked === 4) { + mod = '<span onclick="moderate_approve(' + e.id + '); return false;" class="text-success pe-4 d-inline-block"><i class="bi bi-check-lg" ></i></span><span onclick="moderate_drop(' + e.id + '); return false;" class="text-danger pe-4 d-inline-block"><i class="bi bi-trash" ></i></span>'; + } + modal_content.innerHTML += '<a href="' + e.url + '" class="list-group-item list-group-item-action border-0">' + mod + '<img src="' + e.photo + '" class="menu-img-1" loading="lazy"> ' + e.name + '</a>'; + + }); + + modal.show(); + loading.style.display = 'none'; + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + +} + +function injectWithAnimation(containerId, parsedDoc, overwrite = false) { + const container = document.getElementById(containerId); + if (!container) return; + if (overwrite) container.innerHTML = ''; + + const newElements = Array.from(parsedDoc.body.children); + + 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++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = "#"; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xFF; + color += value.toString(16).padStart(2, '0'); + } + return color; +} + +function stringToHslColor(str) { + let stringUniqueHash = [...str].reduce((acc, char) => { + return char.charCodeAt(0) + ((acc << 5) - acc); + }, 0); + return `hsl(${stringUniqueHash % 360}, 65%, 65%)`; +} + function dolike(ident, verb) { $('#like-rotator-' + ident).show(); @@ -1321,17 +1770,73 @@ function doprofilelike(ident, verb) { function doreply(parent, ident, owner, hint) { - var form = $('#comment-edit-form-' + parent.toString()); - form.find('input[name=parent]').val(ident); - var i = form.find('button[type=submit]'); - var btn = i.html().replace(/<[^>]*>/g, '').trim(); - i.html('<i class="bi bi-arrow-90deg-left"></i> ' + btn); - var sel = 'wall-item-body-' + ident.toString(); - var quote = window.getSelection().toString().trim(); - form.find('textarea').val("@{" + owner + "}" + ((($(window.getSelection().anchorNode).closest("#" + sel).attr("id") != sel) || (quote.length === 0))? " " : "\n[quote]" + quote + "[/quote]\n")); - $('#comment-edit-text-' + parent.toString()).focus(); + const modal = new bootstrap.Modal('#reactions'); + const modal_content = document.getElementById('reactions_body'); + const modal_title = document.getElementById('reactions_title'); + const modal_action = document.getElementById('reactions_action'); + + modal_action.style.display = 'none'; + modal_title.innerHTML = hint; + + const preview = document.getElementById('comment-edit-preview-' + parent.toString()); + preview.innerHTML = ''; + + // Get the form element by ID + const form = document.getElementById('comment-edit-wrapper-' + parent.toString()); + if (!form) return; + + modal_content.innerHTML = ''; + modal_content.append(form); + + // Set the value of the input named 'parent' + const parentInput = form.querySelector('input[name=parent]'); + if (parentInput) { + parentInput.value = ident; + } + + // Find the submit button and update its HTML + const submitBtn = form.querySelector('button[type=submit]'); + if (submitBtn) { + const btnText = submitBtn.innerHTML.replace(/<[^>]*>/g, '').trim(); + submitBtn.innerHTML = '<i class="bi bi-arrow-90deg-left"></i> ' + btnText; + } + + // Prepare the quote logic + const sel = 'wall-item-body-' + ident.toString(); + const quote = window.getSelection().toString().trim(); + + // Check if the selection is inside the correct element + let isInSel = false; + const anchorNode = window.getSelection().anchorNode; + if (anchorNode) { + let node = anchorNode.nodeType === 3 ? anchorNode.parentNode : anchorNode; + while (node) { + if (node.id === sel) { + isInSel = true; + break; + } + node = node.parentNode; + } + } + + modal.show(); + + // Set the textarea value + const textarea = form.querySelector('textarea'); + if (textarea) { + let commentBody = localStorage.getItem('comment_body-' + ident); + if (commentBody) { + textarea.value = commentBody; + } + else { + textarea.value = "@{" + owner + "}" + ((!isInSel || quote.length === 0) ? " " : "\n[quote]" + quote + "[/quote]\n"); + } + + textarea.focus(); + } } + function doscroll(parent, hidden) { var id; var x = '#hide-comments-outer-' + hidden.toString(); @@ -1345,6 +1850,7 @@ function doscroll(parent, hidden) { var c = '#collapsed-comments-' + x; if($(c).length !== 0 && (! $(c).is(':visible'))) { showHideComments(x); + collapseHeight(); pos += $(c).height(); } } @@ -1542,11 +2048,13 @@ function post_comment(id) { window.location.href = data.reload; } + close_modal(); localStorage.removeItem("comment_body-" + id); $("#comment-edit-preview-" + id).hide(); $("#comment-edit-text-" + id).val('').blur().attr('placeholder', aStr.comment); - $('#wall-item-comment-wrapper-' + id).before(data.html); + $('#wall-item-sub-thread-wrapper-' + data.thr_parent_id).append(data.html); + updateRelativeTime('.autotime'); $('body').css('cursor', 'unset'); collapseHeight(); diff --git a/view/pdl/mod_dreport.pdl b/view/pdl/mod_dreport.pdl new file mode 100644 index 000000000..a75fa334a --- /dev/null +++ b/view/pdl/mod_dreport.pdl @@ -0,0 +1,7 @@ +[template]doubleleft[/template] +[region=aside] +[widget=notifications][var=sys_only]1[/var][/widget] +[/region] +[region=content] +$content +[/region] diff --git a/view/php/default.php b/view/php/default.php index 8d2c092a3..324981dff 100644 --- a/view/php/default.php +++ b/view/php/default.php @@ -11,28 +11,28 @@ */ ?> <!DOCTYPE html > -<html prefix="og: http://ogp.me/ns#" <?php if(x($page,'color_mode')) echo $page['color_mode'] ?>> +<html prefix="og: http://ogp.me/ns#" <?php if(!empty($page['color_mode'])) echo $page['color_mode'] ?>> <head> - <title><?php if(x($page,'title')) echo $page['title'] ?></title> + <title><?php if(!empty($page['title'])) echo $page['title'] ?></title> <script>var baseurl="<?php echo z_root() ?>";</script> - <?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?> + <?php if(!empty($page['htmlhead'])) echo $page['htmlhead'] ?> </head> <body <?php if($page['direction']) echo 'dir="rtl"' ?> > - <?php if(x($page,'banner')) echo $page['banner']; ?> - <header><?php if(x($page,'header')) echo $page['header']; ?></header> - <?php if(x($page,'nav')) echo $page['nav']; ?> + <?php if(!empty($page['banner'])) echo $page['banner']; ?> + <header><?php if(!empty($page['header'])) echo $page['header']; ?></header> + <?php if(!empty($page['nav'])) echo $page['nav']; ?> <main> <div class="content"> <div class="columns"> - <aside id="region_1" class="d-none d-lg-block"><div class="aside_spacer_top_left"></div><div class="aside_spacer_left"><div id="left_aside_wrapper" class="aside_wrapper"><?php if(x($page,'aside')) echo $page['aside']; ?></div></div></aside> - <section id="region_2"><?php if(x($page,'content')) echo $page['content']; ?> + <aside id="region_1" class="d-none d-lg-block"><div class="aside_spacer_top_left"></div><div class="aside_spacer_left"><div id="left_aside_wrapper" class="aside_wrapper"><?php if(!empty($page['aside'])) echo $page['aside']; ?></div></div></aside> + <section id="region_2"><?php if(!empty($page['content'])) echo $page['content']; ?> <div id="page-footer"></div> <div id="pause"></div> </section> - <aside id="region_3" class="d-none d-xl-block"><div class="aside_spacer_top_right"></div><div class="aside_spacer_right"><div id="right_aside_wrapper" class="aside_wrapper"><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></div></div></aside> + <aside id="region_3" class="d-none d-xl-block"><div class="aside_spacer_top_right"></div><div class="aside_spacer_right"><div id="right_aside_wrapper" class="aside_wrapper"><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></div></div></aside> </div> </div> </main> - <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer> + <footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer> </body> </html> diff --git a/view/theme/redbasic/css/style.css b/view/theme/redbasic/css/style.css index 97fe40c9c..36023e511 100644 --- a/view/theme/redbasic/css/style.css +++ b/view/theme/redbasic/css/style.css @@ -508,8 +508,8 @@ footer { } .contact-block-img { - width: 2.94rem; - height: 2.94rem; + width: 2.92rem; + height: 2.92rem; margin-bottom: 3px; } @@ -953,6 +953,7 @@ a .drop-icons:hover { } .wall-item-pinned i { + display: inline-block; transform: rotate(45deg); } diff --git a/view/theme/redbasic/js/redbasic.js b/view/theme/redbasic/js/redbasic.js index 0859aae73..144714b50 100644 --- a/view/theme/redbasic/js/redbasic.js +++ b/view/theme/redbasic/js/redbasic.js @@ -53,7 +53,7 @@ document.addEventListener('DOMContentLoaded', function () { testElem.style.display = 'none'; testElem.id = 'css3-calc'; document.body.appendChild(testElem); - + if (testElem.offsetWidth === 10) { window.addEventListener('resize', function () { if (window.innerWidth < 992) { @@ -134,17 +134,6 @@ document.addEventListener('DOMContentLoaded', function () { } }); - document.querySelectorAll('.notifications-btn').forEach(function (element) { - element.addEventListener('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - let navCollapse = document.getElementById('navbar-collapse-2'); - if (navCollapse && navCollapse.classList.contains('show')) { - navCollapse.classList.remove('show'); - } - }); - }); - $("input[data-role=cat-tagsinput]").tagsinput({ tagClass: 'badge rounded-pill bg-warning text-dark' }); diff --git a/view/theme/redbasic/schema/Focus-Boxy.css b/view/theme/redbasic/schema/Focus-Boxy.css index 290518e8e..826e53e66 100644 --- a/view/theme/redbasic/schema/Focus-Boxy.css +++ b/view/theme/redbasic/schema/Focus-Boxy.css @@ -1,4 +1,5 @@ -.comment .wall-item-body { +.comment .wall-item-body, +.comment .wall-item-tools-left { padding-left: 3.4rem; } @@ -37,3 +38,8 @@ border-right: 0; border-left: 0; } + +.contact-block-img { + width: 2.89rem; + height: 2.89rem; +} diff --git a/view/tpl/app.tpl b/view/tpl/app.tpl index b078aa66d..12f8a7905 100644 --- a/view/tpl/app.tpl +++ b/view/tpl/app.tpl @@ -10,12 +10,12 @@ <div class="app-tools"> <form action="{{$hosturl}}appman" method="post"> <input type="hidden" name="papp" value="{{$app.papp}}" /> - {{if $action_label}}<button type="submit" name="install" value="{{$action_label}}" class="btn btn-{{if $installed}}outline-secondary{{else}}success{{/if}} btn-sm" title="{{$action_label}}" ><i class="bi{{if $installed}} bi-arrow-repeat{{else}} bi-arrow-down-circle{{/if}}" ></i> {{$action_label}}</button>{{/if}} - {{if $edit}}<input type="hidden" name="appid" value="{{$app.guid}}" /><button type="submit" name="edit" value="{{$edit}}" class="btn btn-outline-secondary btn-sm" title="{{$edit}}" ><i class="bi bi-pencil" ></i></button>{{/if}} - {{if $delete}}<button type="submit" name="delete" value="{{if $deleted}}{{$undelete}}{{else}}{{$delete}}{{/if}}" class="btn btn-outline-secondary btn-sm" title="{{if $deleted}}{{$undelete}}{{else}}{{$delete}}{{/if}}" ><i class="bi bi-trash drop-icons"></i></button>{{/if}} - {{if $settings_url}}<a href="{{$settings_url}}/?f=&rpath={{$rpath}}" class="btn btn-outline-secondary btn-sm"><i class="bi bi-gear"></i></a>{{/if}} - {{if $feature}}<button type="submit" name="feature" value="nav_featured_app" class="btn btn-outline-secondary btn-sm" title="{{if $featured}}{{$remove}}{{else}}{{$add}}{{/if}}"><i class="bi bi-star{{if $featured}} text-warning{{/if}}"></i></button>{{/if}} - {{if $pin}}<button type="submit" name="pin" value="nav_pinned_app" class="btn btn-outline-secondary btn-sm" title="{{if $pinned}}{{$remove_nav}}{{else}}{{$add_nav}}{{/if}}"><i class="bi bi-pin{{if $pinned}} text-success{{/if}}"></i></button>{{/if}} + {{if $action_label}}<button type="submit" name="install" value="{{$action_label}}" class="btn btn-{{if $installed}}outline-secondary{{else}}success{{/if}} btn-sm border-0" title="{{$action_label}}" ><i class="bi{{if $installed}} bi-arrow-repeat{{else}} bi-arrow-down-circle{{/if}}" ></i> {{$action_label}}</button>{{/if}} + {{if $edit}}<input type="hidden" name="appid" value="{{$app.guid}}" /><button type="submit" name="edit" value="{{$edit}}" class="btn btn-outline-secondary btn-sm border-0" title="{{$edit}}" ><i class="bi bi-pencil" ></i></button>{{/if}} + {{if $delete}}<button type="submit" name="delete" value="{{if $deleted}}{{$undelete}}{{else}}{{$delete}}{{/if}}" class="btn btn-outline-danger btn-sm border-0" title="{{if $deleted}}{{$undelete}}{{else}}{{$delete}}{{/if}}" ><i class="bi bi-trash"></i></button>{{/if}} + {{if $settings_url}}<a href="{{$settings_url}}/?f=&rpath={{$rpath}}" class="btn btn-outline-secondary btn-sm border-0"><i class="bi bi-gear"></i></a>{{/if}} + {{if $feature}}<button type="submit" name="feature" value="nav_featured_app" class="btn btn-outline-secondary btn-sm border-0" title="{{if $featured}}{{$remove}}{{else}}{{$add}}{{/if}}"><i class="bi bi-star{{if $featured}} text-warning{{/if}}"></i></button>{{/if}} + {{if $pin}}<button type="submit" name="pin" value="nav_pinned_app" class="btn btn-outline-secondary btn-sm border-0" title="{{if $pinned}}{{$remove_nav}}{{else}}{{$add_nav}}{{/if}}"><i class="bi bi-pin{{if $pinned}} text-success{{/if}}"></i></button>{{/if}} </form> </div> {{/if}} diff --git a/view/tpl/breadcrumb.tpl b/view/tpl/breadcrumb.tpl index 205b712d9..f1332b4e2 100644 --- a/view/tpl/breadcrumb.tpl +++ b/view/tpl/breadcrumb.tpl @@ -1,5 +1,5 @@ <nav aria-label="breadcrumb"> - <ol class="breadcrumb bg-transparent"> + <ol class="breadcrumb bg-transparent section-content-wrapper"> {{foreach $breadcrumbs as $breadcrumb}} {{if $breadcrumb@last}} <li class="breadcrumb-item active h3 pt-3 pb-3" aria-current="page">{{$breadcrumb.name}}</li> diff --git a/view/tpl/comment_item.tpl b/view/tpl/comment_item.tpl index 6b7e163eb..53036b5cd 100644 --- a/view/tpl/comment_item.tpl +++ b/view/tpl/comment_item.tpl @@ -1,16 +1,12 @@ - {{if $threaded}} - <div class="comment-wwedit-wrapper threaded" id="comment-edit-wrapper-{{$id}}" style="display: block;"> - {{else}} - <div class="comment-wwedit-wrapper" id="comment-edit-wrapper-{{$id}}" style="display: block;"> - {{/if}} - <form class="comment-edit-form" style="display: block;" id="comment-edit-form-{{$id}}" action="item" method="post" onsubmit="post_comment({{$id}}); return false;"> + <div class="comment-wwedit-wrapper{{if $threaded}} threaded{{/if}}" id="comment-edit-wrapper-{{$id}}"> + <form class="comment-edit-form" id="comment-edit-form-{{$id}}" action="item" method="post" onsubmit="post_comment({{$id}}); return false;"> <input type="hidden" name="type" value="{{$type}}" /> <input type="hidden" name="profile_uid" value="{{$profile_uid}}" /> <input type="hidden" name="parent" value="{{$parent}}" /> <input type="hidden" name="return" value="{{$return_path}}" /> <input type="hidden" name="jsreload" value="{{$jsreload}}" /> <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" /> - {{if $anoncomments && ! $observer}} + {{if $anoncomments && !$observer}} <div id="comment-edit-anon-{{$id}}" style="display: none;" > {{include file="field_input.tpl" field=$anonname}} {{include file="field_input.tpl" field=$anonmail}} @@ -74,5 +70,5 @@ </div> <div class="clear"></div> </form> + <div id="comment-edit-preview-{{$id}}" class="comment-edit-preview mt-4"></div> </div> - <div id="comment-edit-preview-{{$id}}" class="comment-edit-preview mt-4"></div> diff --git a/view/tpl/connection_template.tpl b/view/tpl/connection_template.tpl index 5ae290da8..1f8d98ab8 100644 --- a/view/tpl/connection_template.tpl +++ b/view/tpl/connection_template.tpl @@ -7,10 +7,9 @@ {{/foreach}} {{/if}} <span id="contact-role-{{$contact.id}}" class="badge rounded-pill bg-warning text-dark me-1" title="{{$role_label}}">{{$contact.role}}</span> - <button type="button" class="btn btn-outline-secondary btn-sm contact-edit" title="{{$contact.edit_hover}}" data-id="{{$contact.id}}"> + <button type="button" class="btn btn-outline-secondary btn-sm border-0 contact-edit" title="{{$contact.edit_hover}}" data-id="{{$contact.id}}"> <i class="bi bi-pencil contact-edit-icon-{{$contact.id}}"></i> <div class="spinner-wrapper contact-edit-rotator-{{$contact.id}}" style="vertical-align: text-bottom; margin-right: 2px"><div class="spinner s"></div></div> - {{$contact.edit}} </button> </div> diff --git a/view/tpl/conv_frame.tpl b/view/tpl/conv_frame.tpl index 4237c671b..0acf7b24c 100644 --- a/view/tpl/conv_frame.tpl +++ b/view/tpl/conv_frame.tpl @@ -18,4 +18,22 @@ </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> +<div class="modal" id="reactions" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h3 class="modal-title" id="reactions_title"></h3> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button> + </div> + <div class="modal-header" id="reactions_action"> + </div> + <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 --> {{include file="contact_edit_modal.tpl"}} diff --git a/view/tpl/conv_item.tpl b/view/tpl/conv_item.tpl index da1e5a472..c97c53c41 100644 --- a/view/tpl/conv_item.tpl +++ b/view/tpl/conv_item.tpl @@ -1,10 +1,10 @@ -{{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">{{$item.hide_text}}</span> <span id="hide-comments-total-{{$item.id}}" class="hide-comments-label align-middle">{{$item.num_comments}}</span> + <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> <div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;"> {{/if}} - <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite{{/if}} clearfix{{if $item.is_contained}} is-contained{{/if}}{{if $item.is_new && !$item.event && !$item.photo && !$item.title && !$item.is_comment}} is-new{{/if}}" data-b64mids='{{$item.mids}}'> + <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry{{else}} u-comment h-cite{{/if}} clearfix{{if $item.is_contained}} is-contained{{/if}}{{if $item.is_new && !$item.event && !$item.photo && !$item.title && !$item.is_comment}} is-new{{/if}}" data-b64mids='{{$item.mids}}'> <a name="item_{{$item.id}}" ></a> <div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" > <div class="rounded wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}"> @@ -28,7 +28,7 @@ {{/if}} <div class="p-2 wall-item-head{{if !$item.title && !$item.event && !$item.photo}} rounded-top{{/if}} clearfix"> <div class="lh-sm text-end float-end"> - <div class="wall-item-ago opacity-75" id="wall-item-ago-{{$item.id}}"> + <div class="wall-item-ago text-body-secondary" id="wall-item-ago-{{$item.id}}"> {{if $item.location}} {{$item.location}} {{/if}} @@ -51,7 +51,7 @@ {{/if}} <small class="autotime" title="{{$item.isotime}}"><time class="dt-published" datetime="{{$item.isotime}}">{{$item.localtime}}</time>{{if $item.expiretime}} {{$item.expiretime}}{{/if}}</small> </div> - {{if $item.thr_parent_uuid}} + {{if !$item.threaded && $item.thr_parent_uuid}} <a href="javascript:doscroll('{{$item.thr_parent_uuid}}',{{$item.parent}});" class="ms-3" title="{{$item.top_hint}}"><i class="bi bi-chevron-double-up"></i></a> {{/if}} {{if $item.pinned}} @@ -85,9 +85,9 @@ </div> {{/if}} <div class="text-truncate"> - <a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}} {{$item.via}} <a href="{{$item.owner_url}}" title="{{$item.olinktitle}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}} + <a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}} {{$item.via}} <a href="{{$item.owner_url}}" title="{{$item.owner_addr}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}} </div> - <small class="lh-sm text-truncate d-block wall-item-addr opacity-75">{{$item.author_id}}</small> + <small class="lh-sm text-truncate d-block wall-item-addr text-body-secondary">{{$item.author_id}}</small> </div> </div> {{if $item.divider}} @@ -110,47 +110,10 @@ <div class="p-2 wall-item-tools d-flex justify-content-between"> <div class="wall-item-tools-left hstack gap-1" id="wall-item-tools-left-{{$item.id}}"> {{foreach $item.responses as $verb=>$response}} - {{if $item.reactions_allowed || (!$item.reactions_allowed && $response.count)}} - <div class=""> - <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.my_responses.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}"{{if $response.modal}} data-bs-toggle="modal" data-bs-target="#{{$verb}}Modal-{{$item.id}}"{{else if $response.count}} data-bs-toggle="dropdown"{{elseif $item.reactions_allowed}} onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;"{{/if}} id="wall-item-{{$verb}}-{{$item.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> - {{if $response.modal}} - <div class="modal" id="{{$verb}}Modal-{{$item.id}}"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h3 class="modal-title">{{$response.count}} {{$response.button.label}}</h3> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button> - </div> - {{if $item.reactions_allowed && !($verb === 'announce' && $item.my_responses.$verb)}} {{** undo announce is not yet supported **}} - <div class="modal-header"> - <a href="#" class="text-reset" onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;">{{if $item.my_responses.$verb}}- {{$item.reaction_str.1}}{{else}}+ {{$item.reaction_str.0}}{{/if}}</a> - </div> - {{/if}} - <div class="modal-body response-list"> - <ul class="nav nav-pills flex-column"> - {{foreach $response.list as $liker}} - {{$liker}} - {{/foreach}} - </ul> - </div> - <div class="modal-footer clear"> - <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{$item.modal_dismiss}}</button> - </div> - </div><!-- /.modal-content --> - </div><!-- /.modal-dialog --> - </div><!-- /.modal --> - {{else}} - <div class="dropdown-menu"> - {{if $item.reactions_allowed && !($verb === 'announce' && $item.my_responses.$verb)}} {{** undo announce is not yet supported **}} - <a href="#" class="text-reset dropdown-item" onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;">{{if $item.my_responses.$verb}}- {{$item.reaction_str.1}}{{else}}+ {{$item.reaction_str.0}}{{/if}}</a> - <div class="dropdown-divider"></div> - {{/if}} - {{foreach $response.list as $liker}}{{$liker}}{{/foreach}} - </div> - {{/if}} - </div> + {{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-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}} {{/foreach}} {{if $item.toplevel && $item.emojis && $item.reactions}} @@ -192,7 +155,7 @@ </div> {{/if}} {{if $item.reply_to}} - <button type="button" title="{{$item.reply_to.0}}" class="btn btn-sm btn-link link-secondary" onclick="doreply({{$item.parent}}, {{$item.id}}, '{{$item.author_id}}', '{{$item.reply_to.2}} {{$item.name|escape:javascript}}');"> + <button type="button" title="{{$item.reply_to.0}}" class="btn btn-sm btn-link link-secondary" onclick="doreply({{$item.parent}}, {{$item.id}}, '{{$item.author_id}}', '{{$item.reply_to.2}}: {{$item.name|escape:javascript}}');"> <i class="bi bi-arrow-90deg-left generic-icons" ></i> </button> {{/if}} @@ -228,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> @@ -246,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> @@ -255,17 +221,34 @@ </div> </div> </div> - {{if $item.toplevel}} + {{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}} {{/foreach}} - {{/if}} + </div> {{if $item.comment}} - <div id="wall-item-comment-wrapper-{{$item.id}}" class="p-2 rounded wall-item-comment-wrapper{{if $item.children}} wall-item-comment-wrapper-wc{{/if}}"> + <div id="wall-item-comment-wrapper-{{$item.id}}" class="p-2 rounded wall-item-comment-wrapper{{if $item.children}} wall-item-comment-wrapper-wc{{/if}}{{if $item.comment_hidden}} d-none{{/if}}"> {{$item.comment}} </div> {{/if}} + + {{else}} + <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}} + {{/foreach}} + </div> + {{/if}} </div> -{{if $item.comment_lastcollapsed}} +{{if !$item.threaded && $item.comment_lastcollapsed}} </div> {{/if}} diff --git a/view/tpl/direntry.tpl b/view/tpl/direntry.tpl index 076d2739f..05fc30ef7 100644 --- a/view/tpl/direntry.tpl +++ b/view/tpl/direntry.tpl @@ -2,16 +2,16 @@ <div class="section-subtitle-wrapper clearfix"> <div class="directory-actions float-end"> {{if $entry.censor_2}} - <a class="directory-censor directory-censor-hide btn btn-outline-danger btn-sm {{$entry.censor_2_class}}" href="{{$entry.censor_2}}"> {{$entry.censor_2_label}}</a> + <a class="directory-censor directory-censor-hide btn btn-outline-danger btn-sm {{$entry.censor_2_class}} border-0" href="{{$entry.censor_2}}"> {{$entry.censor_2_label}}</a> {{/if}} {{if $entry.censor}} - <a class="directory-censor directory-censor-unsafe btn btn-outline-warning btn-sm {{$entry.censor_class}}" href="{{$entry.censor}}"> {{$entry.censor_label}}</a> + <a class="directory-censor directory-censor-unsafe btn btn-outline-warning btn-sm {{$entry.censor_class}} border-0" href="{{$entry.censor}}"> {{$entry.censor_label}}</a> {{/if}} {{if $entry.ignlink}} - <a class="directory-ignore btn btn-info btn-sm" href="{{$entry.ignlink}}"> {{$entry.ignore_label}}</a> + <a class="directory-ignore btn btn-info btn-sm border-0" href="{{$entry.ignlink}}"> {{$entry.ignore_label}}</a> {{/if}} {{if $entry.connect}} - <a class="btn btn-success btn-sm" href="{{$entry.connect}}"><i class="bi bi-plus-lg connect-icon"></i> {{$entry.conn_label}}</a> + <a class="btn btn-success btn-sm border-0" href="{{$entry.connect}}"><i class="bi bi-plus-lg connect-icon"></i> {{$entry.conn_label}}</a> {{/if}} </div> <h3>{{if $entry.public_forum}}<i class="bi bi-chat-quote" title="{{$entry.forum_label}} @{{$entry.nickname}}+"></i> {{/if}}<a href='{{$entry.profile_link}}' >{{$entry.name}}</a>{{if $entry.online}} <i class="bi bi-asterisk online-now" title="{{$entry.online}}"></i>{{/if}}</h3> diff --git a/view/tpl/dreport.tpl b/view/tpl/dreport.tpl index 0b8cfdb11..848a07737 100644 --- a/view/tpl/dreport.tpl +++ b/view/tpl/dreport.tpl @@ -14,7 +14,7 @@ </div> <div> - <table> + <table class="table table-hover table-borderless"> {{if $entries}} {{foreach $entries as $e}} <tr> diff --git a/view/tpl/head.tpl b/view/tpl/head.tpl index 18941f454..f34d0564e 100644 --- a/view/tpl/head.tpl +++ b/view/tpl/head.tpl @@ -16,7 +16,6 @@ var justifiedGalleryActive = false; {{if $channel_hash}}var channelHash = '{{$channel_hash}}';{{/if}} var channelId = {{if $channel_id}}{{$channel_id}}{{else}}false{{/if}};{{* Used in e.g. autocomplete *}} - var preloadImages = {{$preload_images}}; var auto_save_draft = {{$auto_save_draft}}; {{if $module}}var module = '{{$module}}';{{/if}} </script> diff --git a/view/tpl/jot-header.tpl b/view/tpl/jot-header.tpl index 2c2b662be..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){ @@ -211,6 +213,7 @@ $.get('{{$baseurl}}/share/' + id, function(data) { $('#like-rotator-' + id).hide(); updateInit(); + close_modal(); }); } @@ -413,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) => { @@ -441,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 @@ -474,7 +500,6 @@ .then(ddata => { if (ddata.status) { addActiveEditorText(ddata.photolink); - preview_post(); } else { console.error("{{$modalerrorlink}}: " + ddata.errormsg); } @@ -501,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 => { @@ -550,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 38168ccfe..0e438f450 100644 --- a/view/tpl/js_strings.tpl +++ b/view/tpl/js_strings.tpl @@ -5,8 +5,6 @@ 'delitem' : "{{$delitem}}", 'itemdel' : "{{$itemdel}}", 'comment' : "{{$comment}}", - 'showmore' : "{{$showmore}}", - 'showfewer' : "{{$showfewer}}", 'divgrowmore' : "{{$divgrowmore}}", 'divgrowless' : "{{$divgrowless}}", 'pwshort' : "{{$pwshort}}", @@ -38,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 2156c1f4c..be66c00d4 100644 --- a/view/tpl/notifications_widget.tpl +++ b/view/tpl/notifications_widget.tpl @@ -10,26 +10,31 @@ document.addEventListener("DOMContentLoaded", function() { let notificationsWrapper = document.getElementById('notifications_wrapper'); let notificationsParent = notificationsWrapper ? notificationsWrapper.parentElement.id : null; - let notificationsBtn = document.querySelector('.notifications-btn'); + let notificationsBtn = document.querySelectorAll('.notifications-btn'); // Event listener for notifications button if (notificationsBtn) { - notificationsBtn.addEventListener('click', function() { - // Remove the 'd-none' class to show the notifications wrapper - notificationsWrapper.classList.remove('d-none'); - - // Check if the notifications wrapper has the 'fs' class - if (notificationsWrapper.classList.contains('fs')) { - // Prepend the notifications wrapper back to its original parent and hide it - document.getElementById(notificationsParent).appendChild(notificationsWrapper); - notificationsWrapper.classList.add('d-none'); - } else { - // Otherwise, prepend the notifications wrapper to 'main' - document.querySelector('main').prepend(notificationsWrapper); - } + notificationsBtn.forEach(function (element) { + element.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Remove the 'd-none' class to show the notifications wrapper + notificationsWrapper.classList.remove('d-none'); + + // Check if the notifications wrapper has the 'fs' class + if (notificationsWrapper.classList.contains('fs')) { + // Prepend the notifications wrapper back to its original parent and hide it + document.getElementById(notificationsParent).appendChild(notificationsWrapper); + notificationsWrapper.classList.add('d-none'); + } else { + // Otherwise, prepend the notifications wrapper to 'main' + document.querySelector('main').prepend(notificationsWrapper); + } - // Toggle the 'fs' class - notificationsWrapper.classList.toggle('fs'); + // Toggle the 'fs' class + notificationsWrapper.classList.toggle('fs'); + }); }); } @@ -83,6 +88,7 @@ } else { if (!document.hidden) { + sse_fallback(); sse_fallback_interval = setInterval(sse_fallback, updateInterval); } @@ -621,17 +627,23 @@ } // Update visibility of notification button and sections - let notificationsBtn = document.querySelector('.notifications-btn'); + let notificationsBtn = document.querySelectorAll('.notifications-btn'); let noNotifications = document.querySelector('#no_notifications'); let notifications = document.querySelector('#notifications'); let navbarCollapse = document.querySelector('#navbar-collapse-1'); if (any_available) { - notificationsBtn.style.opacity = 1; + notificationsBtn.forEach(btn => { + btn.style.opacity = 1; + }); noNotifications.style.display = 'none'; notifications.style.display = 'block'; } else { - notificationsBtn.style.opacity = 0.5; + if (notificationsBtn) { + notificationsBtn.forEach(btn => { + btn.style.opacity = 0.5; + }); + } if (navbarCollapse) navbarCollapse.classList.remove('show'); noNotifications.style.display = 'block'; notifications.style.display = 'none'; @@ -713,7 +725,7 @@ <div class="text-truncate pe-1"> <strong title="{2} - {3}">{2}</strong> </div> - <small class="autotime-narrow opacity-75" title="{5}"></small> + <small class="autotime-narrow text-body-secondary" title="{5}"></small> </div> <div class="text-truncate">{4}</div> </div> diff --git a/view/tpl/pinned_item.tpl b/view/tpl/pinned_item.tpl index aa5b94664..db3a175da 100644 --- a/view/tpl/pinned_item.tpl +++ b/view/tpl/pinned_item.tpl @@ -1,6 +1,6 @@ <div id="pinned-wrapper-{{$id}}" class="pinned-item toplevel_item generic-content-wrapper h-entry" data-b64mids='{{$mids}}'> <div class="wall-item-outside-wrapper" id="pinned-item-outside-wrapper-{{$id}}"> - <div class="clearfix wall-item-content-wrapper" id="pinned-item-content-wrapper-{{$id}}"> + <div class="wall-item-content-wrapper" id="pinned-item-content-wrapper-{{$id}}"> {{if $photo}} <div class="wall-photo-item" id="pinned-photo-item-{{$id}}"> {{$photo}} @@ -26,17 +26,51 @@ {{/if}} </div> {{if ! $is_new}} - <hr class="m-0"> + <hr class="m-0"> {{/if}} {{/if}} - <div class="p-2 lh-sm d-flex wall-item-head{{if !$title && !$event && !$photo}} rounded-top{{/if}}{{if $is_new && !$event}} wall-item-head-new{{/if}}" > - <div class="wall-item-info pe-2" id="wall-item-info-{{$id}}" > - <div class="wall-item-photo-wrapper{{if $owner_url}} wwfrom{{/if}} h-card p-author" id="wall-item-photo-wrapper-{{$id}}"> - <img src="{{$thumb}}" class="fakelink wall-item-photo{{$sparkle}} u-photo p-name" id="wall-item-photo-{{$id}}" alt="{{$name}}" loading="lazy" data-bs-toggle="dropdown" /> - {{if $thread_author_menu}} - <i class="bi bi-caret-down wall-item-photo-caret cursor-pointer" data-bs-toggle="dropdown"></i> + <div class="p-2 wall-item-head{{if !$title && !$event && !$photo}} rounded-top{{/if}}{{if $is_new && !$event}} wall-item-head-new{{/if}}" > + <div class="lh-sm text-end float-end"> + <div class="wall-item-ago text-body-secondary" id="pinned-item-ago-{{$id}}"> + {{if $location}} + {{$location}} + {{/if}} + {{if $editedtime}} + <i class="bi bi-pencil" title="{{$editedtime}}"></i> + {{/if}} + {{if $verified}} + <i class="bi bi-shield-check" title="{{$verified}}"></i> + {{elseif $forged}} + <i class="bi bi-shield-exclamation text-danger" title="{{$forged}}"></i> + {{/if}} + {{if $no_comment}} + <i class="bi bi-ban" title="{{$no_comment}}"></i> + {{/if}} + {{if $delayed}} + <i class="bi bi-clock" title="{{$delayed}}"></i> + {{/if}} + {{if $expiretime}} + <i class="bi bi-clock-history" title="{{$expiretime}}"></i> + {{/if}} + <small class="autotime" title="{{$isotime}}"><time class="dt-published" datetime="{{$isotime}}">{{$localtime}}</time>{{if $expiretime}} {{$expiretime}}{{/if}}</small> + </div> + {{if $pinned}} + <div class="wall-item-pinned" title="{{$pinned}}" id="pinned-item-pinned-{{$id}}"><i class="bi bi-pin-fill"></i></div> + {{/if}} + </div> + <div class="float-start wall-item-info pe-2" id="pinned-item-info-{{$id}}" > + <div class="wall-item-photo-wrapper{{if $owner_url}} wwfrom{{/if}} h-card p-author" id="pinned-item-photo-wrapper-{{$id}}"> + {{if $item.contact_id}} + <div class="spinner-wrapper contact-edit-rotator contact-edit-rotator-{{$contact_id}}"><div class="spinner s"></div></div> + {{/if}} + <img src="{{$thumb}}" class="fakelink wall-item-photo{{$sparkle}} u-photo p-name" id="pinned-item-photo-{{$id}}" alt="{{$name}}" loading="lazy" data-bs-toggle="dropdown" /> + {{if $item.author_is_group_actor}} + <i class="bi bi-chat-quote-fill wall-item-photo-group-actor" title="{{$author_is_group_actor}}"></i> + {{/if}} + {{if $item.thread_author_menu}} + <i class="bi bi-caret-down-fill wall-item-photo-caret cursor-pointer" data-bs-toggle="dropdown"></i> <div class="dropdown-menu"> - {{foreach $thread_author_menu as $mitem}} + {{foreach $item.thread_author_menu as $mitem}} <a class="dropdown-item{{if $mitem.class}} {{$mitem.class}}{{/if}}" {{if $mitem.href}}href="{{$mitem.href}}"{{/if}} {{if $mitem.action}}onclick="{{$mitem.action}}"{{/if}} {{if $mitem.title}}title="{{$mitem.title}}"{{/if}}{{if $mitem.data}} {{$mitem.data}}{{/if}}>{{$mitem.title}}</a> {{/foreach}} </div> @@ -45,159 +79,57 @@ </div> <div class="wall-item-author text-truncate"> <a href="{{$profile_url}}" title="{{$linktitle}}" class="wall-item-name-link u-url"><span class="wall-item-name" id="pinned-item-name-{{$id}}" >{{$name}}</span></a>{{if $owner_url}} {{$via}} <a href="{{$owner_url}}" title="{{$olinktitle}}" class="wall-item-name-link"><span class="wall-item-name" id="pinned-item-ownername-{{$id}}">{{$owner_name}}</span></a>{{/if}}<br> - <small class="wall-item-addr opacity-75">{{$author_id}}</small> - </div> - <div class="text-end ms-auto"> - <div class="wall-item-ago text-nowrap opacity-75" id="wall-item-ago-{{$id}}"> - {{if $editedtime}} - <i class="bi bi-pencil"></i> - {{/if}} - {{if $delayed}} - <i class="bi fa-clock-o"></i> - {{/if}} - {{if $location}} - <small class="wall-item-location p-location" id="wall-item-location-{{$id}}">{{$location}}</small> - {{/if}} - {{if $verified}} - <i class="bi bi-check-lg text-success" title="{{$verified}}"></i> - {{elseif $forged}} - <i class="bi fa-exclamation text-danger" title="{{$forged}}"></i> - {{/if}} - <small class="autotime" title="{{$isotime}}"><time class="dt-published" datetime="{{$isotime}}">{{$localtime}}</time>{{if $editedtime}} {{$editedtime}}{{/if}}{{if $expiretime}} {{$expiretime}}{{/if}}</small> - </div> - <div class="wall-item-pinned" title="{{$pinned}}" id="wall-item-pinned-{{$id}}"><i class="bi fa-thumb-tack"></i></div> + <small class="wall-item-addr text-body-secondary">{{$author_id}}</small> </div> </div> {{if $divider}} - <hr class="wall-item-divider"> + <hr class="wall-item-divider"> {{/if}} {{if $body}} - <div class="p-2 wall-item-content clearfix" id="pinned-item-content-{{$id}}"> - <div class="wall-item-body e-content" id="pinned-item-body-{{$id}}" > - {{$body}} - </div> + <div class="p-2 wall-item-content clearfix" id="pinned-item-content-{{$id}}"> + <div class="wall-item-body e-content" id="pinned-item-body-{{$id}}" > + {{$body}} </div> + </div> {{/if}} {{if $has_tags}} - <div class="p-2 wall-item-tools clearfix"> - <div class="body-tags"> - <span class="tag">{{$mentions}} {{$tags}} {{$categories}} {{$folders}}</span> - </div> + <div class="p-2 wall-item-tools clearfix"> + <div class="body-tags"> + <span class="tag">{{$mentions}} {{$tags}} {{$categories}} {{$folders}}</span> </div> + </div> {{/if}} - <div class="p-2 clearfix wall-item-tools"> - <div class="float-end wall-item-tools-right"> - <div class="btn-group"> - <div id="pinned-rotator-{{$id}}" class="spinner-wrapper"> - <div class="spinner s"></div> - </div> - </div> - <div class="btn-group"> - {{if $isevent}} - <div class="btn-group"> - <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-attend-menu-{{$id}}" title="{{$attend_title}}"> - <i class="bi fa-calendar-check-o"></i> - </button> - <div class="dropdown-menu dropdown-menu-end"> - <a class="dropdown-item" href="#" title="{{$attend.0}}" onclick="itemAddToCal({{$id}}); dolike({{$id}},'attendyes'); return false;"> - <i class="item-act-list bi bi-check-lg{{if $my_responses.attend}} ivoted{{/if}}" ></i> {{$attend.0}} - </a> - <a class="dropdown-item" href="#" title="{{$attend.1}}" onclick="itemAddToCal({{$id}}), dolike({{$id}},'attendno'); return false;"> - <i class="item-act-list bi bi-x-lg{{if $my_responses.attendno}} ivoted{{/if}}" ></i> {{$attend.1}} - </a> - <a class="dropdown-item" href="#" title="{{$attend.2}}" onclick="itemAddToCal({{$id}}); dolike({{$id}},'attendmaybe'); return false;"> - <i class="item-act-list bi bi-question-lg{{if $my_responses.attendmaybe}} ivoted{{/if}}" ></i> {{$attend.2}} - </a> - </div> - </div> - {{/if}} - {{if $canvote}} - <div class="btn-group"> - <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-consensus-menu-{{$id}}" title="{{$vote_title}}"> - <i class="bi bi-check-square"></i> - </button> - <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-consensus-menu-{{$id}}"> - <a class="dropdown-item" href="#" title="{{$conlabels.0}}" onclick="dolike({{$id}},'agree'); return false;"> - <i class="item-act-list bi bi-check-lg{{if $my_responses.agree}} ivoted{{/if}}" ></i> {{$conlabels.0}} - </a> - <a class="dropdown-item" href="#" title="{{$conlabels.1}}" onclick="dolike({{$id}},'disagree'); return false;"> - <i class="item-act-list bi bi-x-lg{{if $my_responses.disagree}} ivoted{{/if}}" ></i> {{$conlabels.1}} - </a> - <a class="dropdown-item" href="#" title="{{$conlabels.2}}" onclick="dolike({{$id}},'abstain'); return false;"> - <i class="item-act-list bi bi-question-lg{{if $my_responses.abstain}} ivoted{{/if}}" ></i> {{$conlabels.2}} - </a> - </div> - </div> - {{/if}} - <div class="btn-group"> - <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-menu-{{$id}}"> - <i class="bi bi-gear"></i> - </button> - <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-menu-{{$id}}"> - {{if $share}} - <a class="dropdown-item" href="#" onclick="jotShare({{$id}},{{$item_type}}); return false;"><i class="generic-icons-nav bi fa-retweet" title="{{$share.0}}"></i>{{$share.0}}</a> - {{/if}} - {{if $embed}} - <a class="dropdown-item" href="#" onclick="jotEmbed({{$id}},{{$item_type}}); return false;"><i class="generic-icons-nav bi fa-share" title="{{$embed.0}}"></i>{{$embed.0}}</a> - {{/if}} - {{if $plink}} - <a class="dropdown-item" href="{{$plink.href}}" title="{{$plink.title}}" class="u-url"><i class="generic-icons-nav bi bi-box-arrow-up-right"></i>{{$plink.title}}</a> - {{/if}} - {{if $pinme}} - <a class="dropdown-item dropdown-item-pinnable" href="#" onclick="dopin({{$id}}); return false;"><i class="generic-icons-nav bi fa-thumb-tack"></i>{{$pinme}}</a> - {{/if}} - {{if $hide}} - <a class="dropdown-item" href="#" onclick="dopinhide({{$id}}); return false;" class="u-url"><i class="generic-icons-nav bi fa-remove"></i>{{$hide}}</a> - {{/if}} - </div> + <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="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}} + <div class=""> + <div id="like-rotator-{{$id}}" class="spinner-wrapper"> + <div class="spinner s"></div> </div> </div> </div> - {{if $responses || $attachments}} - <div class="wall-item-tools-left btn-group" id="pinned-item-tools-left-{{$id}}"> - {{if $attachments}} - <div class="wall-item-tools-left btn-group" id="pinned-item-tools-left-{{$id}}"> - <div class="btn-group"> - <button type="button" class="btn btn-outline-secondary btn-sm wall-item-like dropdown-toggle" data-bs-toggle="dropdown" id="pinned-attachment-menu-{{$id}}"> - <i class="bi bi-paperclip"></i> - </button> - <div class="dropdown-menu">{{$attachments}}</div> - </div> - </div> - {{/if}} - {{foreach $responses as $verb=>$response}} - {{if $response.count}} - <div class="btn-group"> - <button type="button" class="btn btn-outline-secondary btn-sm wall-item-like dropdown-toggle"{{if $response.modal}} data-bs-toggle="modal" data-bs-target="#{{$verb}}Modal-{{$id}}"{{else}} data-bs-toggle="dropdown"{{/if}} id="pinned-item-{{$verb}}-{{$id}}">{{$response.count}} {{$response.button}}</button> - {{if $response.modal}} - <div class="modal" id="pinned-{{$verb}}Modal-{{$id}}"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h3 class="modal-title">{{$response.count}} {{$response.button}}</h3> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button> - </div> - <div class="modal-body response-list"> - <ul class="nav nav-pills flex-column"> - {{foreach $response.list as $liker}}<li class="nav-item">{{$liker}}</li>{{/foreach}} - </ul> - </div> - <div class="modal-footer clear"> - <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{$modal_dismiss}}</button> - </div> - </div> - </div> - </div> - {{else}} - <div class="dropdown-menu"> - {{foreach $response.list as $liker}}{{$liker}}{{/foreach}} - </div> - {{/if}} - </div> + <div class="wall-item-tools-right hstack gap-1" id="pinned-item-tools-right-{{$id}}"> + {{if $attachments}} + <div class=""> + <button type="button" class="btn btn-sm btn-link link-secondary wall-item-attach" data-bs-toggle="dropdown" id="pinned-attachment-menu-{{$id}}"><i class="bi bi-paperclip generic-icons"></i></button> + <div class="dropdown-menu dropdown-menu-end">{{$attachments}}</div> + </div> + {{/if}} + <div class=""> + <button type="button" class="btn btn-sm btn-link link-secondary" data-bs-toggle="dropdown" id="wall-item-menu-{{$item.id}}"> + <i class="bi bi-three-dots-vertical generic-icons"></i> + </button> + <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-menu-{{$item.id}}"> + {{if $plink}} + <a class="dropdown-item" href="{{$plink.href}}" title="{{$plink.title}}" class="u-url"><i class="generic-icons-nav bi bi-box-arrow-up-right"></i>{{$plink.title}}</a> {{/if}} - {{/foreach}} + </div> </div> - {{/if}} + </div> </div> </div> </div> diff --git a/view/tpl/search_item.tpl b/view/tpl/search_item.tpl index d693c4d37..a40b25554 100644 --- a/view/tpl/search_item.tpl +++ b/view/tpl/search_item.tpl @@ -22,7 +22,7 @@ {{/if}} <div class="p-2 wall-item-head{{if !$item.title && !$item.event && !$item.photo}} rounded-top{{/if}}{{if $item.is_new && !$item.event && !$item.is_comment}} wall-item-head-new{{/if}}" > <div class="lh-sm text-end float-end"> - <div class="wall-item-ago opacity-75" id="wall-item-ago-{{$item.id}}"> + <div class="wall-item-ago text-body-secondary" id="wall-item-ago-{{$item.id}}"> {{if $item.location}} {{$item.location}} {{/if}} @@ -83,7 +83,7 @@ <div class="text-truncate"> <a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}} {{$item.via}} <a href="{{$item.owner_url}}" title="{{$item.olinktitle}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}} </div> - <small class="lh-sm text-truncate d-block wall-item-addr opacity-75">{{$item.author_id}}</small> + <small class="lh-sm text-truncate d-block wall-item-addr text-body-secondary">{{$item.author_id}}</small> </div> </div> {{if $item.divider}} diff --git a/view/tpl/settings_display.tpl b/view/tpl/settings_display.tpl index d745d14b5..f77dfd409 100644 --- a/view/tpl/settings_display.tpl +++ b/view/tpl/settings_display.tpl @@ -61,8 +61,8 @@ {{include file="field_checkbox.tpl" field=$nosmile}} {{include file="field_checkbox.tpl" field=$title_tosource}} {{include file="field_checkbox.tpl" field=$user_scalable}} - {{include file="field_checkbox.tpl" field=$preload_images}} {{include file="field_checkbox.tpl" field=$start_menu}} + {{include file="field_checkbox.tpl" field=$thread_allow}} <div class="settings-submit-wrapper" > <button type="submit" name="submit" class="btn btn-primary">{{$submit}}</button> </div> |