aboutsummaryrefslogtreecommitdiffstats
path: root/view
diff options
context:
space:
mode:
Diffstat (limited to 'view')
-rw-r--r--view/css/conversation.css114
-rw-r--r--view/css/mod_help.css2
-rw-r--r--view/js/autocomplete.js2
-rw-r--r--view/js/main.js644
-rw-r--r--view/pdl/mod_dreport.pdl7
-rw-r--r--view/php/default.php20
-rw-r--r--view/theme/redbasic/css/style.css5
-rw-r--r--view/theme/redbasic/js/redbasic.js13
-rw-r--r--view/theme/redbasic/schema/Focus-Boxy.css8
-rw-r--r--view/tpl/app.tpl12
-rw-r--r--view/tpl/breadcrumb.tpl2
-rw-r--r--view/tpl/comment_item.tpl12
-rw-r--r--view/tpl/connection_template.tpl3
-rw-r--r--view/tpl/conv_frame.tpl18
-rw-r--r--view/tpl/conv_item.tpl91
-rw-r--r--view/tpl/direntry.tpl8
-rw-r--r--view/tpl/dreport.tpl2
-rw-r--r--view/tpl/head.tpl1
-rw-r--r--view/tpl/jot-header.tpl65
-rw-r--r--view/tpl/js_strings.tpl3
-rw-r--r--view/tpl/notifications_widget.tpl52
-rw-r--r--view/tpl/pinned_item.tpl226
-rw-r--r--view/tpl/search_item.tpl4
-rw-r--r--view/tpl/settings_display.tpl2
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">&nbsp;' + 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>&nbsp;<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}}&nbsp;<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}}&nbsp;{{$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}}&nbsp;{{$item.via}}&nbsp;<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}}&nbsp;{{$item.via}}&nbsp;<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>&nbsp;{{/if}}<a href='{{$entry.profile_link}}' >{{$entry.name}}</a>{{if $entry.online}}&nbsp;<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>&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
@@ -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}}&nbsp;{{$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}}&nbsp;{{$via}}&nbsp;<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}}&nbsp;{{$editedtime}}{{/if}}{{if $expiretime}}&nbsp;{{$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}}&nbsp;{{$item.via}}&nbsp;<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>