aboutsummaryrefslogtreecommitdiffstats
path: root/view
diff options
context:
space:
mode:
Diffstat (limited to 'view')
-rw-r--r--view/css/conversation.css96
-rw-r--r--view/js/main.js388
-rw-r--r--view/tpl/conv_frame.tpl3
-rw-r--r--view/tpl/conv_item.tpl19
-rw-r--r--view/tpl/jot-header.tpl64
-rw-r--r--view/tpl/js_strings.tpl1
-rw-r--r--view/tpl/notifications_widget.tpl1
-rw-r--r--view/tpl/pinned_item.tpl2
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}}&nbsp;<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>&nbsp;{{$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}}