Compare commits
24 Commits
svelte
...
chat-overh
| Author | SHA1 | Date | |
|---|---|---|---|
| f803ec27a5 | |||
| e61e24dfe8 | |||
| 44af6ae061 | |||
| 0739d72334 | |||
| 848055446a | |||
| 88e6b6efb9 | |||
| eea70ed62e | |||
| b6c906636c | |||
| 55b5a0ba9f | |||
| 5d60d80fc9 | |||
| 4dfb80b149 | |||
| b319f21cd4 | |||
| 28ed8d985c | |||
| 0f5e795793 | |||
| 78d14a1b56 | |||
| 3f149531da | |||
| 7a4432277d | |||
| 84eee7e13d | |||
| 2206b3eb86 | |||
| 5529724982 | |||
| 9c6070afff | |||
| 5bfa336773 | |||
| 517444f9ce | |||
| edb92ffa2a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ public/tag_cache
|
|||||||
config.json
|
config.json
|
||||||
bundle.css
|
bundle.css
|
||||||
public/s/fonts/impact.woff
|
public/s/fonts/impact.woff
|
||||||
|
f0ck-svelte/*
|
||||||
@@ -99,6 +99,8 @@ services:
|
|||||||
container_name: f0ckm-nginx
|
container_name: f0ckm-nginx
|
||||||
profiles:
|
profiles:
|
||||||
- f0ckm-nginx
|
- f0ckm-nginx
|
||||||
|
environment:
|
||||||
|
- ENABLE_IPV6=true
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,9 @@
|
|||||||
/* Meme Creator Styles */
|
/* Meme Creator Styles */
|
||||||
|
|
||||||
.meme-creator-container {
|
.meme-creator-container {
|
||||||
|
box-sizing: border-box !important;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
max-width: 1200px;
|
max-width: 1100px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -111,6 +112,7 @@
|
|||||||
|
|
||||||
.canvas-wrapper {
|
.canvas-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-width: 650px;
|
||||||
min-width: 0; /* Allow shrinking */
|
min-width: 0; /* Allow shrinking */
|
||||||
background: #000;
|
background: #000;
|
||||||
border: 2px solid var(--nav-bg, #2b2b2b);
|
border: 2px solid var(--nav-bg, #2b2b2b);
|
||||||
@@ -238,22 +240,49 @@ canvas#memeCanvas {
|
|||||||
color: var(--white, #fff);
|
color: var(--white, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Stacking - Simple 2-Row Layout */
|
/* Mobile Stacking - Clean Flexbox Column Layout */
|
||||||
@media (max-width: 950px) {
|
@media (max-width: 950px) {
|
||||||
.meme-editor-layout {
|
.meme-editor-layout {
|
||||||
display: grid !important;
|
display: flex !important;
|
||||||
grid-template-columns: 1fr !important;
|
flex-direction: column !important;
|
||||||
grid-template-rows: 0.6fr auto !important;
|
align-items: center !important;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
.meme-controls {
|
.meme-controls {
|
||||||
|
box-sizing: border-box !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
grid-row: 2;
|
max-width: 500px !important;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
.canvas-wrapper {
|
.canvas-wrapper {
|
||||||
|
box-sizing: border-box !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
grid-row: 1;
|
max-width: 650px !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
overflow: visible !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack early when sidebar is open (sidebar is 300px, so 950 + 300 = 1250px)
|
||||||
|
This prevents the canvas from shrinking too much when the sidebar takes space. */
|
||||||
|
@media (max-width: 1250px) {
|
||||||
|
body:not(.sidebar-right-hidden) .meme-editor-layout {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
body:not(.sidebar-right-hidden) .meme-controls {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 500px !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
body:not(.sidebar-right-hidden) .canvas-wrapper {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 650px !important;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
@@ -294,20 +323,19 @@ canvas#memeCanvas {
|
|||||||
|
|
||||||
/* Sidebar space reservation for meme pages */
|
/* Sidebar space reservation for meme pages */
|
||||||
.meme-layout-wrapper {
|
.meme-layout-wrapper {
|
||||||
|
box-sizing: border-box !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: padding-right 0.3s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1000px) {
|
||||||
/* Reserve space for the fixed sidebar so content doesn't flow behind it */
|
/* Reserve space for the fixed sidebar so content doesn't flow behind it */
|
||||||
.meme-layout-wrapper {
|
body:not(.sidebar-right-hidden) .meme-layout-wrapper {
|
||||||
padding-right: 300px;
|
padding-right: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapse reserved space when sidebar is hidden */
|
|
||||||
body.sidebar-right-hidden .meme-layout-wrapper {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Collapse reserved space when sidebar is hidden */
|
||||||
|
body.sidebar-right-hidden .meme-layout-wrapper {
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class CommentSystem {
|
|||||||
document.body.classList.remove('sidebar-left-hidden');
|
document.body.classList.remove('sidebar-left-hidden');
|
||||||
const layout = this.container.closest('.item-layout-container');
|
const layout = this.container.closest('.item-layout-container');
|
||||||
if (layout) layout.classList.remove('sidebar-hidden');
|
if (layout) layout.classList.remove('sidebar-hidden');
|
||||||
} else if (isHidden) {
|
} else if (isHidden && (window.innerWidth >= 1000 || !document.body.classList.contains('layout-modern'))) {
|
||||||
this.container.classList.add('faded-out');
|
this.container.classList.add('faded-out');
|
||||||
this.container.style.display = 'none';
|
this.container.style.display = 'none';
|
||||||
document.body.classList.add('sidebar-left-hidden');
|
document.body.classList.add('sidebar-left-hidden');
|
||||||
@@ -596,7 +596,7 @@ class CommentSystem {
|
|||||||
if (cached) cached.content = fullContent;
|
if (cached) cached.content = fullContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentEl.dataset.raw = this.escapeHtml(fullContent);
|
contentEl.dataset.raw = fullContent;
|
||||||
contentEl.innerHTML = this.renderCommentContent(fullContent, commentId);
|
contentEl.innerHTML = this.renderCommentContent(fullContent, commentId);
|
||||||
CommentSystem.autoplayConvertedGifs(contentEl);
|
CommentSystem.autoplayConvertedGifs(contentEl);
|
||||||
CommentSystem.playEmojiVideos(contentEl);
|
CommentSystem.playEmojiVideos(contentEl);
|
||||||
@@ -622,6 +622,7 @@ class CommentSystem {
|
|||||||
|
|
||||||
const contentEl = el.querySelector('.comment-content');
|
const contentEl = el.querySelector('.comment-content');
|
||||||
if (contentEl) {
|
if (contentEl) {
|
||||||
|
contentEl.dataset.raw = data.content;
|
||||||
contentEl.innerHTML = this.renderCommentContent(data.content, data.comment_id);
|
contentEl.innerHTML = this.renderCommentContent(data.content, data.comment_id);
|
||||||
CommentSystem.autoplayConvertedGifs(contentEl);
|
CommentSystem.autoplayConvertedGifs(contentEl);
|
||||||
CommentSystem.playEmojiVideos(contentEl);
|
CommentSystem.playEmojiVideos(contentEl);
|
||||||
@@ -857,26 +858,14 @@ class CommentSystem {
|
|||||||
* border-right) is fully restored after the effect completes.
|
* border-right) is fully restored after the effect completes.
|
||||||
*
|
*
|
||||||
* @param {Element} el - The .comment element to animate.
|
* @param {Element} el - The .comment element to animate.
|
||||||
* @param {boolean} entering - True to also add 'comment-entering' (own post);
|
* @param {boolean} entering - Unused (kept for compatibility).
|
||||||
* false for 'new-item-fade' only (live others).
|
|
||||||
*/
|
*/
|
||||||
static _animateNewComment(el, entering = false) {
|
static _animateNewComment(el, entering = false) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
el.classList.add('new-item-fade');
|
||||||
const classes = entering
|
setTimeout(() => {
|
||||||
? ['comment-entering', 'new-item-fade']
|
el.classList.remove('new-item-fade');
|
||||||
: ['new-item-fade'];
|
}, 2500);
|
||||||
|
|
||||||
// Reset in case the element already has a stale animation
|
|
||||||
classes.forEach(c => el.classList.remove(c));
|
|
||||||
void el.offsetWidth; // force reflow so re-adding the class restarts the animation
|
|
||||||
|
|
||||||
classes.forEach(c => el.classList.add(c));
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
classes.forEach(c => el.classList.remove(c));
|
|
||||||
};
|
|
||||||
el.addEventListener('animationend', cleanup, { once: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +881,6 @@ class CommentSystem {
|
|||||||
if (el) {
|
if (el) {
|
||||||
// Remove highlight from any previously highlighted or newly-posted comment
|
// Remove highlight from any previously highlighted or newly-posted comment
|
||||||
document.querySelectorAll('.comment-highlighted').forEach(c => c.classList.remove('comment-highlighted'));
|
document.querySelectorAll('.comment-highlighted').forEach(c => c.classList.remove('comment-highlighted'));
|
||||||
document.querySelectorAll('.comment-entering').forEach(c => c.classList.remove('comment-entering'));
|
|
||||||
document.querySelectorAll('.new-item-fade').forEach(c => c.classList.remove('new-item-fade'));
|
document.querySelectorAll('.new-item-fade').forEach(c => c.classList.remove('new-item-fade'));
|
||||||
|
|
||||||
// Always smooth-scroll so there's no jarring jump
|
// Always smooth-scroll so there's no jarring jump
|
||||||
@@ -1038,7 +1026,7 @@ class CommentSystem {
|
|||||||
preview.dataset.id = targetId;
|
preview.dataset.id = targetId;
|
||||||
|
|
||||||
// Remove temporary animation/highlight classes
|
// Remove temporary animation/highlight classes
|
||||||
preview.classList.remove('new-item-fade', 'comment-highlighted', 'comment-entering');
|
preview.classList.remove('new-item-fade', 'comment-highlighted');
|
||||||
|
|
||||||
// Remove input forms or action buttons
|
// Remove input forms or action buttons
|
||||||
const actions = preview.querySelector('.comment-actions');
|
const actions = preview.querySelector('.comment-actions');
|
||||||
@@ -1173,7 +1161,12 @@ class CommentSystem {
|
|||||||
const contentEl = body.querySelector('.comment-content');
|
const contentEl = body.querySelector('.comment-content');
|
||||||
if (contentEl) {
|
if (contentEl) {
|
||||||
const LINE_MAX = 200;
|
const LINE_MAX = 200;
|
||||||
const rawText = (contentEl.dataset.raw || '').trim();
|
let rawText = (contentEl.dataset.raw || '').trim();
|
||||||
|
if (rawText.includes('>') || rawText.includes('<') || rawText.includes('&')) {
|
||||||
|
const txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = rawText;
|
||||||
|
rawText = txt.value;
|
||||||
|
}
|
||||||
// Preserve all lines but cap any single line exceeding LINE_MAX chars
|
// Preserve all lines but cap any single line exceeding LINE_MAX chars
|
||||||
const lines = rawText.split('\n').map(line =>
|
const lines = rawText.split('\n').map(line =>
|
||||||
line.length > LINE_MAX ? line.substring(0, LINE_MAX) + '\u2026' : line
|
line.length > LINE_MAX ? line.substring(0, LINE_MAX) + '\u2026' : line
|
||||||
@@ -1794,7 +1787,7 @@ class CommentSystem {
|
|||||||
const renderedContent = quoteEmojis
|
const renderedContent = quoteEmojis
|
||||||
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n))
|
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n))
|
||||||
: quoteContent;
|
: quoteContent;
|
||||||
return `<span class="greentext">>${renderedContent}</span>`;
|
return `<span class="greentext">>${renderedContent.replace(/>/g, '>')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Per-line limit to prevent marked.parse recursion on single giant lines
|
// 2. Per-line limit to prevent marked.parse recursion on single giant lines
|
||||||
@@ -2199,7 +2192,7 @@ class CommentSystem {
|
|||||||
if (!unsafe) return '';
|
if (!unsafe) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = unsafe;
|
div.textContent = unsafe;
|
||||||
return div.innerHTML;
|
return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCommentAttachments(files, content = '') {
|
renderCommentAttachments(files, content = '') {
|
||||||
@@ -3336,9 +3329,15 @@ class CommentSystem {
|
|||||||
params.append('has_poll', '1');
|
params.append('has_poll', '1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||||||
|
if (csrfToken) params.append('csrf_token', csrfToken);
|
||||||
|
|
||||||
const res = await fetch('/api/comments', {
|
const res = await fetch('/api/comments', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||||
|
},
|
||||||
body: params
|
body: params
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3808,9 +3807,12 @@ class CommentSystem {
|
|||||||
toggleComments() {
|
toggleComments() {
|
||||||
if (!this.container) return;
|
if (!this.container) return;
|
||||||
|
|
||||||
if (document.body.classList.contains('layout-modern') && typeof window.toggleSidebarLeft === 'function') {
|
if (document.body.classList.contains('layout-modern')) {
|
||||||
window.toggleSidebarLeft();
|
if (window.innerWidth < 1000) return;
|
||||||
return;
|
if (typeof window.toggleSidebarLeft === 'function') {
|
||||||
|
window.toggleSidebarLeft();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout = this.container.closest('.item-layout-container');
|
const layout = this.container.closest('.item-layout-container');
|
||||||
@@ -4457,20 +4459,71 @@ class CommentSystem {
|
|||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: reliable touch handling for tab buttons on mobile
|
// Helper: reliable touch handling for tab buttons on mobile (supports hold-to-action)
|
||||||
const addTabTouch = (btn, cb) => {
|
let lastHoldTime = 0;
|
||||||
|
const addTabTouch = (btn, cb, onHold) => {
|
||||||
let ts = null;
|
let ts = null;
|
||||||
|
let holdTimer = null;
|
||||||
|
let holdFired = false;
|
||||||
btn.addEventListener('touchstart', (e) => {
|
btn.addEventListener('touchstart', (e) => {
|
||||||
|
holdFired = false;
|
||||||
ts = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
ts = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||||
|
if (onHold) {
|
||||||
|
holdTimer = setTimeout(() => {
|
||||||
|
holdFired = true;
|
||||||
|
lastHoldTime = Date.now();
|
||||||
|
try { navigator.vibrate?.(40); } catch(err) {}
|
||||||
|
onHold();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
|
btn.addEventListener('touchmove', (e) => {
|
||||||
|
if (!ts) return;
|
||||||
|
const dx = Math.abs(e.touches[0].clientX - ts.x);
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - ts.y);
|
||||||
|
if (dx > 10 || dy > 10) {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
ts = null;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
btn.addEventListener('touchend', (e) => {
|
btn.addEventListener('touchend', (e) => {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
if (holdFired) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
holdFired = false;
|
||||||
|
ts = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!ts) return;
|
if (!ts) return;
|
||||||
const dx = Math.abs(e.changedTouches[0].clientX - ts.x);
|
const dx = Math.abs(e.changedTouches[0].clientX - ts.x);
|
||||||
const dy = Math.abs(e.changedTouches[0].clientY - ts.y);
|
const dy = Math.abs(e.changedTouches[0].clientY - ts.y);
|
||||||
ts = null;
|
ts = null;
|
||||||
if (dx < 10 && dy < 10) { e.preventDefault(); cb(); }
|
if (dx < 10 && dy < 10) { e.preventDefault(); cb(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btn.addEventListener('touchcancel', () => {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
holdFired = false;
|
||||||
|
ts = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFavDefault = (tabEl) => {
|
||||||
|
const KEY = 'f0ck_emoji_default_tab';
|
||||||
|
const isDefault = localStorage.getItem(KEY) === '__favs__';
|
||||||
|
if (isDefault) {
|
||||||
|
localStorage.removeItem(KEY);
|
||||||
|
tabEl.classList.remove('ep-tab-default');
|
||||||
|
window._showEmojiToast?.('Favorites unset as default tab');
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(KEY, '__favs__');
|
||||||
|
tabEl.classList.add('ep-tab-default');
|
||||||
|
window._showEmojiToast?.('Favorites set as default tab');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build tab buttons — Favorites first
|
// Build tab buttons — Favorites first
|
||||||
@@ -4482,23 +4535,13 @@ class CommentSystem {
|
|||||||
favTab.innerHTML = '<i class="fa-solid fa-star" style="color:var(--emoji-fav-color,#f5c518);font-size:1.1em;"></i>';
|
favTab.innerHTML = '<i class="fa-solid fa-star" style="color:var(--emoji-fav-color,#f5c518);font-size:1.1em;"></i>';
|
||||||
favTab.addEventListener('mousedown', e => e.preventDefault());
|
favTab.addEventListener('mousedown', e => e.preventDefault());
|
||||||
favTab.addEventListener('click', () => showTab('__favs__'));
|
favTab.addEventListener('click', () => showTab('__favs__'));
|
||||||
addTabTouch(favTab, () => showTab('__favs__'));
|
addTabTouch(favTab, () => showTab('__favs__'), () => toggleFavDefault(favTab));
|
||||||
// Right-click on fav tab → set/unset as default
|
// Right-click on fav tab → set/unset as default
|
||||||
favTab.addEventListener('contextmenu', (e) => {
|
favTab.addEventListener('contextmenu', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (navigator.maxTouchPoints > 0) return;
|
if (Date.now() - lastHoldTime < 1000) return;
|
||||||
const KEY = 'f0ck_emoji_default_tab';
|
toggleFavDefault(favTab);
|
||||||
const isDefault = localStorage.getItem(KEY) === '__favs__';
|
|
||||||
if (isDefault) {
|
|
||||||
localStorage.removeItem(KEY);
|
|
||||||
favTab.classList.remove('ep-tab-default');
|
|
||||||
window._showEmojiToast?.('Favorites unset as default tab');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(KEY, '__favs__');
|
|
||||||
favTab.classList.add('ep-tab-default');
|
|
||||||
window._showEmojiToast?.('Favorites set as default tab');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// Mark as default if already set
|
// Mark as default if already set
|
||||||
if (localStorage.getItem('f0ck_emoji_default_tab') === '__favs__') {
|
if (localStorage.getItem('f0ck_emoji_default_tab') === '__favs__') {
|
||||||
|
|||||||
@@ -2307,9 +2307,49 @@ window.cancelAnimFrame = (function () {
|
|||||||
hidden = hiddenValue === 'true';
|
hidden = hiddenValue === 'true';
|
||||||
}
|
}
|
||||||
document.body.classList.toggle('sidebar-right-hidden', hidden);
|
document.body.classList.toggle('sidebar-right-hidden', hidden);
|
||||||
|
|
||||||
|
// Always force-hide on very small screens regardless of saved preference
|
||||||
|
if (window.innerWidth <= 599) {
|
||||||
|
document.body.classList.add('sidebar-right-hidden');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
window.initSidebarRightToggle();
|
window.initSidebarRightToggle();
|
||||||
|
|
||||||
|
// Auto-hide sidebar on very small screens (≤599px), restore on wider
|
||||||
|
(() => {
|
||||||
|
const mq = window.matchMedia('(max-width: 599px)');
|
||||||
|
const applyBreakpoint = (e) => {
|
||||||
|
// Freeze transitions so the class change is instant (no sliding during resize)
|
||||||
|
const sidebar = document.querySelector('.global-sidebar-right, .item-sidebar-right, .index-sidebar-right');
|
||||||
|
const wrappers = document.querySelectorAll('.index-layout-wrapper, .item-layout-container, .pagination-container-fluid, .pagewrapper');
|
||||||
|
if (sidebar) sidebar.style.setProperty('transition', 'none', 'important');
|
||||||
|
wrappers.forEach(w => w.style.setProperty('transition', 'none', 'important'));
|
||||||
|
|
||||||
|
if (e.matches) {
|
||||||
|
// Entering narrow: force-hide without touching localStorage
|
||||||
|
document.body.classList.add('sidebar-right-hidden');
|
||||||
|
} else {
|
||||||
|
// Leaving narrow: restore from saved preference
|
||||||
|
const saved = localStorage.getItem('sidebarRightHidden');
|
||||||
|
const hidden = saved === 'true';
|
||||||
|
document.body.classList.toggle('sidebar-right-hidden', hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force reflow then restore transitions
|
||||||
|
if (sidebar) void sidebar.offsetWidth;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (sidebar) sidebar.style.removeProperty('transition');
|
||||||
|
wrappers.forEach(w => {
|
||||||
|
w.style.removeProperty('transition');
|
||||||
|
w.style.removeProperty('padding-right');
|
||||||
|
w.style.removeProperty('right');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
mq.addEventListener('change', applyBreakpoint);
|
||||||
|
applyBreakpoint(mq); // run once on init
|
||||||
|
})();
|
||||||
|
|
||||||
const loadPageAjax = async (url, replace = true, options = {}) => {
|
const loadPageAjax = async (url, replace = true, options = {}) => {
|
||||||
if (isNavigating) return;
|
if (isNavigating) return;
|
||||||
|
|
||||||
@@ -2398,7 +2438,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
const isAdmin = !!pathname.match(/^\/admin/);
|
const isAdmin = !!pathname.match(/^\/admin/);
|
||||||
const isMod = !!pathname.match(/^\/mod/);
|
const isMod = !!pathname.match(/^\/mod/);
|
||||||
const isSettings = pathname.match(/\/settings\/?(?:$|\?)/);
|
const isSettings = pathname.match(/\/settings\/?(?:$|\?)/);
|
||||||
const isStatic = pathname.match(/\/(about|rules|terms|upload|subscriptions|stats|docs|discord|ranking|meme|memes)($|\/|\?)/);
|
const isStatic = pathname.match(/\/(about|rules|terms|upload|subscriptions|stats|docs|discord|ranking|meme|memes|chat)($|\/|\?)/);
|
||||||
const isUpload = pathname.match(/\/upload\/?(?:$|\?)/);
|
const isUpload = pathname.match(/\/upload\/?(?:$|\?)/);
|
||||||
const parts = pathname.split('/').filter(Boolean);
|
const parts = pathname.split('/').filter(Boolean);
|
||||||
const isItem = !pathname.match(/\/p\//) && (
|
const isItem = !pathname.match(/\/p\//) && (
|
||||||
@@ -2406,8 +2446,9 @@ window.cancelAnimFrame = (function () {
|
|||||||
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
|
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
|
||||||
);
|
);
|
||||||
const isMessages = !!pathname.match(/^\/messages(\/|$)/);
|
const isMessages = !!pathname.match(/^\/messages(\/|$)/);
|
||||||
|
const isChat = !!pathname.match(/^\/chat(\/|$)/);
|
||||||
const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
|
const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
|
||||||
const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isAbyss;
|
const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isChat && !isAbyss;
|
||||||
|
|
||||||
if (isItem) {
|
if (isItem) {
|
||||||
isNavigating = false;
|
isNavigating = false;
|
||||||
@@ -4039,7 +4080,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes)/) || pathname.startsWith('/abyss'))) {
|
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes|chat)/) || pathname.startsWith('/abyss'))) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
@@ -4427,6 +4468,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (window.renderTags) {
|
if (window.renderTags) {
|
||||||
window.renderTags(res.tags);
|
window.renderTags(res.tags);
|
||||||
}
|
}
|
||||||
|
// Evict the cached item HTML so navigating back fetches fresh content
|
||||||
|
if (window.invalidateItemCache) {
|
||||||
|
window.invalidateItemCache(postid);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
revert();
|
revert();
|
||||||
window.flashMessage('Error: ' + (res.msg || 'Failed to update rating'), 3000, 'error');
|
window.flashMessage('Error: ' + (res.msg || 'Failed to update rating'), 3000, 'error');
|
||||||
@@ -4489,7 +4534,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
||||||
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1]))
|
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1]))
|
||||||
);
|
);
|
||||||
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking');
|
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking') || p.startsWith('/chat');
|
||||||
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
|
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
|
||||||
|
|
||||||
window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike });
|
window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike });
|
||||||
@@ -6277,8 +6322,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
|
|
||||||
// Freeze all transitions (override CSS !important) so the class toggle
|
// Freeze sidebar AND wrapper transitions so the class toggle
|
||||||
// doesn't trigger an additional right-property animation.
|
// doesn't trigger a second padding-right animation.
|
||||||
|
const wrappers = getSidebarWrappers();
|
||||||
|
wrappers.forEach(w => w.style.setProperty('transition', 'none', 'important'));
|
||||||
sidebar.style.setProperty('transition', 'none', 'important');
|
sidebar.style.setProperty('transition', 'none', 'important');
|
||||||
|
|
||||||
if (stateChanging) {
|
if (stateChanging) {
|
||||||
@@ -6296,6 +6343,11 @@ window.cancelAnimFrame = (function () {
|
|||||||
// Restore CSS-controlled transitions on the next frame
|
// Restore CSS-controlled transitions on the next frame
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
sidebar.style.removeProperty('transition');
|
sidebar.style.removeProperty('transition');
|
||||||
|
wrappers.forEach(w => {
|
||||||
|
w.style.removeProperty('transition');
|
||||||
|
w.style.removeProperty('padding-right');
|
||||||
|
w.style.removeProperty('right');
|
||||||
|
});
|
||||||
swipeRT.isDraggingSidebar = false;
|
swipeRT.isDraggingSidebar = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -6533,6 +6585,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
const settle = () => {
|
const settle = () => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
|
// Freeze sidebar AND wrapper transitions so the class toggle
|
||||||
|
// doesn't trigger a second padding-right animation.
|
||||||
|
const wrappers = getSidebarWrappers();
|
||||||
|
wrappers.forEach(w => w.style.setProperty('transition', 'none', 'important'));
|
||||||
sidebar.style.setProperty('transition', 'none', 'important');
|
sidebar.style.setProperty('transition', 'none', 'important');
|
||||||
|
|
||||||
if (stateChanging) {
|
if (stateChanging) {
|
||||||
@@ -6545,6 +6601,11 @@ window.cancelAnimFrame = (function () {
|
|||||||
sidebar.style.transform = '';
|
sidebar.style.transform = '';
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
sidebar.style.removeProperty('transition');
|
sidebar.style.removeProperty('transition');
|
||||||
|
wrappers.forEach(w => {
|
||||||
|
w.style.removeProperty('transition');
|
||||||
|
w.style.removeProperty('padding-right');
|
||||||
|
w.style.removeProperty('right');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6612,15 +6673,55 @@ window.cancelAnimFrame = (function () {
|
|||||||
});
|
});
|
||||||
window.addEventListener('resize', syncEdgeZone, { passive: true });
|
window.addEventListener('resize', syncEdgeZone, { passive: true });
|
||||||
|
|
||||||
// Touchend on edgeZone: instant tap-to-toggle (no 300ms synthetic click delay)
|
// Returns true if (clientX, clientY) is within the pill's hit area.
|
||||||
|
// The pill is #sidebar-drag-zone::after — 4×40px, centered on pointer:fine,
|
||||||
|
// right-aligned with padding-right:6px on pointer:coarse.
|
||||||
|
const isWithinPillArea = (clientX, clientY) => {
|
||||||
|
const rect = edgeZone.getBoundingClientRect();
|
||||||
|
const pillH = 40;
|
||||||
|
const hitPad = 12; // generous padding so it's easy to hit
|
||||||
|
// Vertical: pill is always centered in the drag zone
|
||||||
|
const pillCenterY = rect.top + rect.height / 2;
|
||||||
|
if (clientY < pillCenterY - pillH / 2 - hitPad || clientY > pillCenterY + pillH / 2 + hitPad) return false;
|
||||||
|
// Horizontal: right-aligned on touch, centered on mouse
|
||||||
|
const isCoarse = window.matchMedia('(pointer: coarse)').matches;
|
||||||
|
const pillCenterX = isCoarse ? rect.right - 6 - 2 : rect.left + rect.width / 2;
|
||||||
|
return clientX >= pillCenterX - 2 - hitPad && clientX <= pillCenterX + 2 + hitPad;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Touchend: tap on the pill toggles sidebar; rest of drag zone only responds to swipe
|
||||||
edgeZone.addEventListener('touchend', e => {
|
edgeZone.addEventListener('touchend', e => {
|
||||||
// Only if it was a clean tap (no significant swipe movement)
|
|
||||||
if (Math.abs(swipeRT.xDiff || 0) < 15 && Math.abs(swipeRT.yDiff || 0) < 15) {
|
if (Math.abs(swipeRT.xDiff || 0) < 15 && Math.abs(swipeRT.yDiff || 0) < 15) {
|
||||||
e.preventDefault();
|
e.preventDefault(); // always prevent 300ms synthetic click
|
||||||
edgeZone.dispatchEvent(new Event('click'));
|
const touch = e.changedTouches[0];
|
||||||
|
if (touch && isWithinPillArea(touch.clientX, touch.clientY)) {
|
||||||
|
window.toggleSidebarRight();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
|
// Helper: all layout wrappers that have padding-right animated by sidebar state
|
||||||
|
const getSidebarWrappers = () => {
|
||||||
|
// At ≤999px: sidebar is a pure overlay — only pagination tracks it.
|
||||||
|
if (window.innerWidth <= 599) {
|
||||||
|
return document.querySelectorAll('.pagination-container-fluid');
|
||||||
|
}
|
||||||
|
// At ≥600px: exclude pagewrapper when a dedicated layout wrapper is present.
|
||||||
|
// CSS already zeroes pagewrapper.padding-right via :has on those pages,
|
||||||
|
// so including it here causes double-padding (pagewrapper + index-layout-wrapper both
|
||||||
|
// get 300px snapped, making the grid 600px too narrow, then jumping back on settle).
|
||||||
|
const hasDedicatedWrapper = !!(
|
||||||
|
document.querySelector('.index-layout-wrapper') ||
|
||||||
|
document.querySelector('.item-layout-container') ||
|
||||||
|
document.querySelector('.meme-layout-wrapper') ||
|
||||||
|
document.querySelector('.messages-convo-page')
|
||||||
|
);
|
||||||
|
const sel = hasDedicatedWrapper
|
||||||
|
? '.index-layout-wrapper, .meme-layout-wrapper, .messages-convo-page, .pagination-container-fluid'
|
||||||
|
: '.pagination-container-fluid, .pagewrapper';
|
||||||
|
return document.querySelectorAll(sel);
|
||||||
|
};
|
||||||
|
|
||||||
// Smoothly toggle sidebar open/closed
|
// Smoothly toggle sidebar open/closed
|
||||||
window.toggleSidebarRight = () => {
|
window.toggleSidebarRight = () => {
|
||||||
const sidebar = document.querySelector('.global-sidebar-right, .item-sidebar-right, .index-sidebar-right');
|
const sidebar = document.querySelector('.global-sidebar-right, .item-sidebar-right, .index-sidebar-right');
|
||||||
@@ -6630,6 +6731,29 @@ window.cancelAnimFrame = (function () {
|
|||||||
const sidebarWidth = sidebar.offsetWidth || 300;
|
const sidebarWidth = sidebar.offsetWidth || 300;
|
||||||
const targetTranslate = isHidden ? -sidebarWidth : sidebarWidth;
|
const targetTranslate = isHidden ? -sidebarWidth : sidebarWidth;
|
||||||
|
|
||||||
|
// At ≤599px the sidebar is a pure overlay — never animate wrapper padding
|
||||||
|
const animateWrappers = window.innerWidth > 599;
|
||||||
|
|
||||||
|
// Grid/pagewrapper: snap instantly in both directions (no animated reflow).
|
||||||
|
// Pagination always animates — it tracks sidebar position with no reflow cost.
|
||||||
|
const wrappers = getSidebarWrappers();
|
||||||
|
if (animateWrappers) {
|
||||||
|
wrappers.forEach(w => {
|
||||||
|
const isPagination = w.classList.contains('pagination-container-fluid');
|
||||||
|
const isMessages = w.classList.contains('messages-convo-page');
|
||||||
|
|
||||||
|
const prop = (isPagination || isMessages) ? 'right' : 'padding-right';
|
||||||
|
const targetVal = isHidden ? sidebarWidth + 'px' : '0px';
|
||||||
|
|
||||||
|
// Lightweight layouts (pagination, messages) animate smoothly.
|
||||||
|
// Heavy/reflow-intensive layouts snap instantly to avoid reflow lag.
|
||||||
|
const shouldAnimate = (isPagination || isMessages);
|
||||||
|
w.style.setProperty('transition', shouldAnimate ? `${prop} 0.3s ease-in-out` : 'none', 'important');
|
||||||
|
w.style.setProperty(prop, targetVal, 'important');
|
||||||
|
});
|
||||||
|
void sidebar.offsetWidth; // settle layout before sidebar transform begins
|
||||||
|
}
|
||||||
|
|
||||||
sidebar.style.setProperty('transition', 'transform 0.3s ease-in-out', 'important');
|
sidebar.style.setProperty('transition', 'transform 0.3s ease-in-out', 'important');
|
||||||
sidebar.style.transform = `translateX(${targetTranslate}px)`;
|
sidebar.style.transform = `translateX(${targetTranslate}px)`;
|
||||||
|
|
||||||
@@ -6637,13 +6761,22 @@ window.cancelAnimFrame = (function () {
|
|||||||
const settle = () => {
|
const settle = () => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
|
// Freeze wrapper transitions so the class commit doesn't re-trigger them
|
||||||
|
wrappers.forEach(w => w.style.setProperty('transition', 'none', 'important'));
|
||||||
sidebar.style.setProperty('transition', 'none', 'important');
|
sidebar.style.setProperty('transition', 'none', 'important');
|
||||||
const toHidden = !isHidden;
|
const toHidden = !isHidden;
|
||||||
document.body.classList.toggle('sidebar-right-hidden', toHidden);
|
document.body.classList.toggle('sidebar-right-hidden', toHidden);
|
||||||
localStorage.setItem('sidebarRightHidden', toHidden);
|
localStorage.setItem('sidebarRightHidden', toHidden);
|
||||||
void sidebar.offsetWidth;
|
void sidebar.offsetWidth;
|
||||||
sidebar.style.transform = '';
|
sidebar.style.transform = '';
|
||||||
requestAnimationFrame(() => sidebar.style.removeProperty('transition'));
|
requestAnimationFrame(() => {
|
||||||
|
sidebar.style.removeProperty('transition');
|
||||||
|
wrappers.forEach(w => {
|
||||||
|
w.style.removeProperty('transition');
|
||||||
|
w.style.removeProperty('padding-right');
|
||||||
|
w.style.removeProperty('right');
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEnd = ev => {
|
const onEnd = ev => {
|
||||||
@@ -6655,9 +6788,11 @@ window.cancelAnimFrame = (function () {
|
|||||||
setTimeout(settle, 350);
|
setTimeout(settle, 350);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Click on edgeZone: smoothly toggle sidebar open/closed
|
// Click on edgeZone: only toggle when the click lands on the pill
|
||||||
edgeZone.addEventListener('click', () => {
|
edgeZone.addEventListener('click', (e) => {
|
||||||
window.toggleSidebarRight();
|
if (isWithinPillArea(e.clientX, e.clientY)) {
|
||||||
|
window.toggleSidebarRight();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// </mouse-sidebar-drag>
|
// </mouse-sidebar-drag>
|
||||||
|
|
||||||
@@ -6677,7 +6812,9 @@ window.cancelAnimFrame = (function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isLSActive = () =>
|
const isLSActive = () =>
|
||||||
document.body.classList.contains('layout-modern') && !!document.querySelector('.item-sidebar-left');
|
document.body.classList.contains('layout-modern') &&
|
||||||
|
!!document.querySelector('.item-sidebar-left') &&
|
||||||
|
window.innerWidth >= 1000;
|
||||||
|
|
||||||
// Prepare sidebar for drag — keeps it in the CSS grid so overflow:hidden clips translateX.
|
// Prepare sidebar for drag — keeps it in the CSS grid so overflow:hidden clips translateX.
|
||||||
// OPEN: removes sidebar-hidden/display:none, places sidebar at translateX(-sw), reflows twice.
|
// OPEN: removes sidebar-hidden/display:none, places sidebar at translateX(-sw), reflows twice.
|
||||||
@@ -6936,6 +7073,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.toggleSidebarLeft = () => {
|
window.toggleSidebarLeft = () => {
|
||||||
|
if (window.innerWidth < 1000) return;
|
||||||
const { sb, layout } = getLS();
|
const { sb, layout } = getLS();
|
||||||
if (!sb || !layout) return;
|
if (!sb || !layout) return;
|
||||||
const isHidden = layout.classList.contains('sidebar-hidden');
|
const isHidden = layout.classList.contains('sidebar-hidden');
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1976,6 +1976,73 @@ if (window.__dmLoaded) {
|
|||||||
dmGridArea = document.createElement('div');
|
dmGridArea = document.createElement('div');
|
||||||
dmGridArea.className = 'emoji-picker-grid';
|
dmGridArea.className = 'emoji-picker-grid';
|
||||||
|
|
||||||
|
// Helper: reliable touch handling for tab buttons on mobile (supports hold-to-action)
|
||||||
|
let lastDmHoldTime = 0;
|
||||||
|
const addDmTabTouch = (btn, cb, onHold) => {
|
||||||
|
let ts = null;
|
||||||
|
let holdTimer = null;
|
||||||
|
let holdFired = false;
|
||||||
|
btn.addEventListener('touchstart', (e) => {
|
||||||
|
holdFired = false;
|
||||||
|
ts = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||||
|
if (onHold) {
|
||||||
|
holdTimer = setTimeout(() => {
|
||||||
|
holdFired = true;
|
||||||
|
lastDmHoldTime = Date.now();
|
||||||
|
try { navigator.vibrate?.(40); } catch(err) {}
|
||||||
|
onHold();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
btn.addEventListener('touchmove', (e) => {
|
||||||
|
if (!ts) return;
|
||||||
|
const dx = Math.abs(e.touches[0].clientX - ts.x);
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - ts.y);
|
||||||
|
if (dx > 10 || dy > 10) {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
ts = null;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
btn.addEventListener('touchend', (e) => {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
if (holdFired) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
holdFired = false;
|
||||||
|
ts = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ts) return;
|
||||||
|
const dx = Math.abs(e.changedTouches[0].clientX - ts.x);
|
||||||
|
const dy = Math.abs(e.changedTouches[0].clientY - ts.y);
|
||||||
|
ts = null;
|
||||||
|
if (dx < 10 && dy < 10) { e.preventDefault(); cb(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.addEventListener('touchcancel', () => {
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
holdFired = false;
|
||||||
|
ts = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDmFavDefault = (tabEl) => {
|
||||||
|
const KEY = 'f0ck_emoji_default_tab';
|
||||||
|
const isDefault = localStorage.getItem(KEY) === '__favs__';
|
||||||
|
if (isDefault) {
|
||||||
|
localStorage.removeItem(KEY);
|
||||||
|
tabEl.classList.remove('ep-tab-default');
|
||||||
|
window._showEmojiToast?.('Favorites unset as default tab');
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(KEY, '__favs__');
|
||||||
|
tabEl.classList.add('ep-tab-default');
|
||||||
|
window._showEmojiToast?.('Favorites set as default tab');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ── Favorites tab ──
|
// ── Favorites tab ──
|
||||||
const favsTab = document.createElement('button');
|
const favsTab = document.createElement('button');
|
||||||
favsTab.className = 'ep-tab ep-tab-favs';
|
favsTab.className = 'ep-tab ep-tab-favs';
|
||||||
@@ -1985,22 +2052,13 @@ if (window.__dmLoaded) {
|
|||||||
favsTab.innerHTML = '<i class="fa-solid fa-star" style="font-size:14px;color:var(--emoji-fav-color,#f5c518);"></i>';
|
favsTab.innerHTML = '<i class="fa-solid fa-star" style="font-size:14px;color:var(--emoji-fav-color,#f5c518);"></i>';
|
||||||
favsTab.addEventListener('mousedown', e => e.preventDefault());
|
favsTab.addEventListener('mousedown', e => e.preventDefault());
|
||||||
favsTab.addEventListener('click', () => showDmTab('__favs__', packs));
|
favsTab.addEventListener('click', () => showDmTab('__favs__', packs));
|
||||||
|
addDmTabTouch(favsTab, () => showDmTab('__favs__', packs), () => toggleDmFavDefault(favsTab));
|
||||||
// Right-click on fav tab → set/unset as default
|
// Right-click on fav tab → set/unset as default
|
||||||
favsTab.addEventListener('contextmenu', (e) => {
|
favsTab.addEventListener('contextmenu', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (navigator.maxTouchPoints > 0) return;
|
if (Date.now() - lastDmHoldTime < 1000) return;
|
||||||
const KEY = 'f0ck_emoji_default_tab';
|
toggleDmFavDefault(favsTab);
|
||||||
const isDefault = localStorage.getItem(KEY) === '__favs__';
|
|
||||||
if (isDefault) {
|
|
||||||
localStorage.removeItem(KEY);
|
|
||||||
favsTab.classList.remove('ep-tab-default');
|
|
||||||
window._showEmojiToast?.('Favorites unset as default tab');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(KEY, '__favs__');
|
|
||||||
favsTab.classList.add('ep-tab-default');
|
|
||||||
window._showEmojiToast?.('Favorites set as default tab');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// Mark as default if already set
|
// Mark as default if already set
|
||||||
if (localStorage.getItem('f0ck_emoji_default_tab') === '__favs__') {
|
if (localStorage.getItem('f0ck_emoji_default_tab') === '__favs__') {
|
||||||
@@ -2028,6 +2086,7 @@ if (window.__dmLoaded) {
|
|||||||
}
|
}
|
||||||
tab.addEventListener('mousedown', e => e.preventDefault());
|
tab.addEventListener('mousedown', e => e.preventDefault());
|
||||||
tab.addEventListener('click', () => showDmTab(pack.id ?? null, packs));
|
tab.addEventListener('click', () => showDmTab(pack.id ?? null, packs));
|
||||||
|
addDmTabTouch(tab, () => showDmTab(pack.id ?? null, packs));
|
||||||
dmTabBar.appendChild(tab);
|
dmTabBar.appendChild(tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1000,9 +1000,10 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const itemId = slide.dataset.id;
|
const itemId = slide.dataset.id;
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
const resp = await fetch(`/api/v2/tags/${itemId}/${encodeURIComponent(tag)}`, {
|
const resp = await fetch(`/api/v2/tags/${itemId}/${encodeURIComponent(tag)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'x-csrf-token': window.scrollerCsrf || '' }
|
headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) }
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) pill.remove();
|
if (data.success) pill.remove();
|
||||||
@@ -1157,10 +1158,14 @@
|
|||||||
if (nowFaved) flashFav(slide);
|
if (nowFaved) flashFav(slide);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
const resp = await fetch('/api/v2/togglefav', {
|
const resp = await fetch('/api/v2/togglefav', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: {
|
||||||
body: `postid=${id}`
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||||
|
},
|
||||||
|
body: `postid=${id}${csrfToken ? `&csrf_token=${encodeURIComponent(csrfToken)}` : ''}`
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
// Sync count to server truth (handles race conditions)
|
// Sync count to server truth (handles race conditions)
|
||||||
@@ -1582,9 +1587,10 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const itemId = slide.dataset.id;
|
const itemId = slide.dataset.id;
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
const resp = await fetch(`/api/v2/tags/${itemId}/${encodeURIComponent(t)}`, {
|
const resp = await fetch(`/api/v2/tags/${itemId}/${encodeURIComponent(t)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'x-csrf-token': window.scrollerCsrf || '' }
|
headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) }
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) pill.remove();
|
if (data.success) pill.remove();
|
||||||
@@ -1850,11 +1856,12 @@
|
|||||||
else if (item.external_board === 'gif') rating = 'nsfw';
|
else if (item.external_board === 'gif') rating = 'nsfw';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
const resp = await fetch('/api/v2/scroller/rehost', {
|
const resp = await fetch('/api/v2/scroller/rehost', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'x-csrf-token': window.scrollerCsrf || ''
|
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
url: item.external_media_url || item.dest,
|
url: item.external_media_url || item.dest,
|
||||||
@@ -1966,9 +1973,10 @@
|
|||||||
showShareToast(info.toast);
|
showShareToast(info.toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
fetch(`/api/v2/tags/${id}/cycle-rating`, {
|
fetch(`/api/v2/tags/${id}/cycle-rating`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'x-csrf-token': window.scrollerCsrf || '' }
|
headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) }
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -2119,7 +2127,12 @@
|
|||||||
const contentEl = commentEl.querySelector('.comment-content');
|
const contentEl = commentEl.querySelector('.comment-content');
|
||||||
if (!contentEl) return;
|
if (!contentEl) return;
|
||||||
|
|
||||||
const raw = (contentEl.dataset.raw || '').replace(/<br\s*\/?>/gi, '\n').trim();
|
let raw = (contentEl.dataset.raw || '').replace(/<br\s*\/?>/gi, '\n').trim();
|
||||||
|
if (raw.includes('>') || raw.includes('<') || raw.includes('&')) {
|
||||||
|
const txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = raw;
|
||||||
|
raw = txt.value;
|
||||||
|
}
|
||||||
const lines = raw.split('\n');
|
const lines = raw.split('\n');
|
||||||
const quote = `>>${id}\n${lines.map(line => `>${line}`).join('\n')}\n`;
|
const quote = `>>${id}\n${lines.map(line => `>${line}`).join('\n')}\n`;
|
||||||
|
|
||||||
@@ -2787,11 +2800,16 @@
|
|||||||
if (!content || !commentsItemId || commentsPosting) return;
|
if (!content || !commentsItemId || commentsPosting) return;
|
||||||
commentsPosting = true; commentSendBtn.disabled = true;
|
commentsPosting = true; commentSendBtn.disabled = true;
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
let postBody = `item_id=${commentsItemId}&content=${encodeURIComponent(content)}`;
|
let postBody = `item_id=${commentsItemId}&content=${encodeURIComponent(content)}`;
|
||||||
if (replyToCommentId) postBody += `&parent_id=${replyToCommentId}`;
|
if (replyToCommentId) postBody += `&parent_id=${replyToCommentId}`;
|
||||||
|
if (csrfToken) postBody += `&csrf_token=${encodeURIComponent(csrfToken)}`;
|
||||||
const resp = await fetch('/api/comments', {
|
const resp = await fetch('/api/comments', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||||
|
},
|
||||||
body: postBody
|
body: postBody
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
@@ -2899,10 +2917,14 @@
|
|||||||
if (addTagSendBtn) addTagSendBtn.disabled = true;
|
if (addTagSendBtn) addTagSendBtn.disabled = true;
|
||||||
closeSugg();
|
closeSugg();
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
const resp = await fetch(`/api/v2/tags/${targetId}`, {
|
const resp = await fetch(`/api/v2/tags/${targetId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: {
|
||||||
body: `tagname=${encodeURIComponent(tag)}`
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||||
|
},
|
||||||
|
body: `tagname=${encodeURIComponent(tag)}${csrfToken ? `&csrf_token=${encodeURIComponent(csrfToken)}` : ''}`
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -3658,7 +3680,11 @@
|
|||||||
if (sMarkAll) {
|
if (sMarkAll) {
|
||||||
sMarkAll.addEventListener('click', async () => {
|
sMarkAll.addEventListener('click', async () => {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/notifications/read', { method: 'POST' });
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
|
await fetch('/api/notifications/read', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) }
|
||||||
|
});
|
||||||
updateScrollerNotifBadge(0);
|
updateScrollerNotifBadge(0);
|
||||||
sCachedNotifs = sCachedNotifs.map(n => ({ ...n, is_read: true }));
|
sCachedNotifs = sCachedNotifs.map(n => ({ ...n, is_read: true }));
|
||||||
updateScrollerTabBadges(sCachedNotifs);
|
updateScrollerTabBadges(sCachedNotifs);
|
||||||
@@ -3673,7 +3699,12 @@
|
|||||||
if (!item) return;
|
if (!item) return;
|
||||||
const nid = item.dataset.id;
|
const nid = item.dataset.id;
|
||||||
if (nid && item.classList.contains('unread')) {
|
if (nid && item.classList.contains('unread')) {
|
||||||
fetch(`/api/notifications/${nid}/read`, { method: 'POST', keepalive: true }).catch(() => {});
|
const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || '';
|
||||||
|
fetch(`/api/notifications/${nid}/read`, {
|
||||||
|
method: 'POST',
|
||||||
|
keepalive: true,
|
||||||
|
headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) }
|
||||||
|
}).catch(() => {});
|
||||||
item.classList.remove('unread');
|
item.classList.remove('unread');
|
||||||
// Update cache
|
// Update cache
|
||||||
const cached = sCachedNotifs.find(n => String(n.id) === String(nid));
|
const cached = sCachedNotifs.find(n => String(n.id) === String(nid));
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
if (!unsafe) return '';
|
if (!unsafe) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = unsafe;
|
div.textContent = unsafe;
|
||||||
return div.innerHTML;
|
return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
const playSidebarEmojiVideos = (container) => {
|
const playSidebarEmojiVideos = (container) => {
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
const rendered = quoteEmojis
|
const rendered = quoteEmojis
|
||||||
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n))
|
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n))
|
||||||
: quoteContent;
|
: quoteContent;
|
||||||
return `<span class="greentext">>${rendered}</span>`;
|
return `<span class="greentext">>${rendered.replace(/>/g, '>')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-line limit to prevent marked.parse recursion on single giant lines
|
// Per-line limit to prevent marked.parse recursion on single giant lines
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ if (!window.UserCommentSystem) {
|
|||||||
const trimmed = line.trimStart();
|
const trimmed = line.trimStart();
|
||||||
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
||||||
const quoteContent = line.substring(line.indexOf('>') + 1);
|
const quoteContent = line.substring(line.indexOf('>') + 1);
|
||||||
return `<span class="greentext">>${quoteContent}</span>`;
|
return `<span class="greentext">>${quoteContent.replace(/>/g, '>')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-line limit
|
// Per-line limit
|
||||||
@@ -450,7 +450,7 @@ if (!window.UserCommentSystem) {
|
|||||||
if (!unsafe) return '';
|
if (!unsafe) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = unsafe;
|
div.textContent = unsafe;
|
||||||
return div.innerHTML;
|
return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Meme",
|
"meme": "Meme",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Suchen",
|
"search": "Suchen",
|
||||||
"random": "Zufall",
|
"random": "Zufall",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Meme",
|
"meme": "Meme",
|
||||||
"halls": "Halls",
|
"halls": "Halls",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "meme",
|
"meme": "meme",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Zoeken",
|
"search": "Zoeken",
|
||||||
"random": "Willekeurig",
|
"random": "Willekeurig",
|
||||||
"profile": "profiel",
|
"profile": "profiel",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Memen",
|
"meme": "Memen",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Etiketten",
|
"tags": "Etiketten",
|
||||||
|
"chat": "Geschwafel",
|
||||||
"search": "Suche",
|
"search": "Suche",
|
||||||
"random": "Zufall",
|
"random": "Zufall",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export default router => {
|
|||||||
|
|
||||||
group.post(/$/, lib.loggedin, async (req, res) => {
|
group.post(/$/, lib.loggedin, async (req, res) => {
|
||||||
// assign and/or create tag
|
// assign and/or create tag
|
||||||
if (!req.params.postid || !req.post.tagname) {
|
const rawTagname = req.post?.tagname || req.body?.tagname;
|
||||||
|
if (!req.params.postid || !rawTagname) {
|
||||||
return res.json({
|
return res.json({
|
||||||
success: false,
|
success: false,
|
||||||
msg: 'missing postid or tag'
|
msg: 'missing postid or tag'
|
||||||
@@ -33,7 +34,7 @@ export default router => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const postid = +req.params.postid;
|
const postid = +req.params.postid;
|
||||||
const tagname = req.post.tagname?.trim();
|
const tagname = rawTagname.trim();
|
||||||
const protectedTags = ['sfw', 'nsfw', 'nsfl'];
|
const protectedTags = ['sfw', 'nsfw', 'nsfl'];
|
||||||
|
|
||||||
if (protectedTags.includes(tagname.toLowerCase())) {
|
if (protectedTags.includes(tagname.toLowerCase())) {
|
||||||
|
|||||||
@@ -47,6 +47,25 @@ function buildBgHostRegex() {
|
|||||||
|
|
||||||
export default (router, tpl) => {
|
export default (router, tpl) => {
|
||||||
|
|
||||||
|
// GET /chat — Dedicated site chat page
|
||||||
|
router.get('/chat', async (req, res) => {
|
||||||
|
if (!cfg.websrv.enable_global_chat) {
|
||||||
|
return res.reply({ code: 404, body: 'Not found' });
|
||||||
|
}
|
||||||
|
if (!req.session) {
|
||||||
|
return res.redirect('/login');
|
||||||
|
}
|
||||||
|
if (req.session.banned) {
|
||||||
|
return res.reply({ code: 403, body: 'Access denied: You are banned' });
|
||||||
|
}
|
||||||
|
return res.html(tpl.render('chat', {
|
||||||
|
session: req.session,
|
||||||
|
hidePagination: true,
|
||||||
|
link: { main: '/chat', path: '/chat' },
|
||||||
|
domain: cfg.main.url.domain
|
||||||
|
}, req));
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/chat — fetch recent messages
|
// GET /api/chat — fetch recent messages
|
||||||
router.get('/api/chat', async (req, res) => {
|
router.get('/api/chat', async (req, res) => {
|
||||||
if (!cfg.websrv.enable_global_chat) {
|
if (!cfg.websrv.enable_global_chat) {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export default (router, tpl) => {
|
|||||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest' || query.ajax) {
|
if (req.headers['x-requested-with'] === 'XMLHttpRequest' || query.ajax) {
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
html: tpl.render('tag-cards', { toptags: tags, session: (req.session && req.session.user) ? { ...req.session } : false }, req),
|
html: tpl.render('tag-cards', { toptags: tags, user: req.session?.user ? { user: req.session.user } : null, session: (req.session && req.session.user) ? { ...req.session } : false }, req),
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
hasMore: tags.length === TAGS_PER_PAGE
|
hasMore: tags.length === TAGS_PER_PAGE
|
||||||
});
|
});
|
||||||
@@ -128,6 +128,7 @@ export default (router, tpl) => {
|
|||||||
phrase,
|
phrase,
|
||||||
tmp: null,
|
tmp: null,
|
||||||
hidePagination: true,
|
hidePagination: true,
|
||||||
|
user: req.session?.user ? { user: req.session.user } : null,
|
||||||
session: (req.session && req.session.user) ? { ...req.session } : false,
|
session: (req.session && req.session.user) ? { ...req.session } : false,
|
||||||
page_meta: {
|
page_meta: {
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
|
|||||||
9
views/chat.html
Normal file
9
views/chat.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@include(snippets/header)
|
||||||
|
<div class="pagewrapper">
|
||||||
|
<div id="main" class="chat-page">
|
||||||
|
<div id="site-chat-container" class="site-chat-container">
|
||||||
|
<!-- Site chat widget is mounted here by globalchat.js -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@include(snippets/footer)
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.meme-title { font-family: 'Impact', 'VCR', sans-serif; }
|
.meme-title { font-family: 'Impact', 'VCR', sans-serif; }
|
||||||
|
|
||||||
/* NUCLEAR VISIBILITY RESET: Override any and all parent constraints for this page */
|
/* NUCLEAR VISIBILITY RESET: Override any and all parent constraints for this page */
|
||||||
@media (max-width: 950px) {
|
@media (max-width: 1250px) {
|
||||||
/* Target all possible shell triggers */
|
/* Target all possible shell triggers */
|
||||||
html, body, .pagewrapper, #main, .layout-modern react-wrapper, .layout-legacy react-wrapper {
|
html, body, .pagewrapper, #main, .layout-modern react-wrapper, .layout-legacy react-wrapper {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<meta name="apple-mobile-web-app-title" content="{{ domain }}">
|
<meta name="apple-mobile-web-app-title" content="{{ domain }}">
|
||||||
|
<script>(function(){var h=document.documentElement;if(localStorage.getItem('hideItemRatings')==='true')h.classList.add('hide-item-ratings-active');if(localStorage.getItem('blurNsfw')==='true')h.classList.add('blur-nsfw-active');if(localStorage.getItem('blurNsfl')==='true')h.classList.add('blur-nsfl-active');if(localStorage.getItem('blurSfw')==='true')h.classList.add('blur-sfw-active');if(localStorage.getItem('blurUntagged')==='true')h.classList.add('blur-untagged-active');if(localStorage.getItem('blurDetail')!=='false')h.classList.add('blur-detail-active');if(localStorage.getItem('imageExpandOnClick')!=='false')h.classList.add('image-expand-active');})();</script>
|
||||||
<style>
|
<style>
|
||||||
html { background-color: #000; color: #fff; }
|
html { background-color: #000; color: #fff; }
|
||||||
@if(session && session.font)
|
@if(session && session.font)
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
}
|
}
|
||||||
@endif
|
@endif
|
||||||
:root {
|
:root {
|
||||||
|
--favicon-url: url('@if(custom_favicon && custom_favicon.length > 0){{ custom_favicon }}@else/s/img/favicon.gif@endif');
|
||||||
@if(session && session.font)
|
@if(session && session.font)
|
||||||
--font: 'CustomUserFont', monospace !important;
|
--font: 'CustomUserFont', monospace !important;
|
||||||
@elseif(typeof default_font !== 'undefined' && default_font && default_font.length > 0)
|
@elseif(typeof default_font !== 'undefined' && default_font && default_font.length > 0)
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tags"></i></a>
|
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tags"></i></a>
|
||||||
|
@if(enable_global_chat)
|
||||||
|
<a href="/chat" title="{{ t('nav.chat') }}"><i class="fa-solid fa-comments"></i></a>
|
||||||
|
@endif
|
||||||
@if(abyss_enabled)
|
@if(abyss_enabled)
|
||||||
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
Reference in New Issue
Block a user