esifawafwu
This commit is contained in:
@@ -3829,6 +3829,25 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Sub-tab nav + background mode bar: scroll horizontally inside themselves instead of overflowing the sidebar */
|
||||
.f0ck-tuner-subtabs-nav,
|
||||
.f0ck-tuner-mode-bar {
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
.f0ck-tuner-subtabs-nav::-webkit-scrollbar,
|
||||
.f0ck-tuner-mode-bar::-webkit-scrollbar { height: 4px; }
|
||||
.f0ck-tuner-subtabs-nav::-webkit-scrollbar-thumb,
|
||||
.f0ck-tuner-mode-bar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); }
|
||||
/* Buttons keep their natural width (grow to fill when there is room); mode-bar buttons set flex:1 inline */
|
||||
.f0ck-tuner-subtabs-nav > .f0ck-tuner-subtab-btn { flex: 1 0 auto; }
|
||||
.f0ck-tuner-mode-bar > .f0ck-tuner-mode-btn { flex: 1 0 auto !important; }
|
||||
.f0ck-tuner-header > .f0ck-tuner-close { flex-shrink: 0; }
|
||||
|
||||
.f0ck-tuner-subtab-btn,
|
||||
.f0ck-tuner-mode-btn {
|
||||
flex: 1;
|
||||
@@ -3958,6 +3977,112 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ── Tuner: style guide (square, flat, theme colours, no glow) + touch-friendly sliders ── */
|
||||
.f0ck-audio-tuner-panel,
|
||||
.f0ck-audio-tuner-panel *,
|
||||
.f0ck-audio-tuner-panel *::before,
|
||||
.f0ck-audio-tuner-panel *::after { border-radius: 0 !important; }
|
||||
|
||||
.f0ck-audio-tuner-panel:not(.in-sidebar) {
|
||||
background: var(--bg, #111);
|
||||
border: 1px solid var(--accent, #99ff00);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
.f0ck-audio-tuner-panel { font-family: inherit; color: var(--text-color, var(--white, #e6e6e6)); }
|
||||
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-header { border-bottom-color: rgba(255, 255, 255, 0.1); }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-section-title {
|
||||
color: var(--text-muted, #8a8f98);
|
||||
letter-spacing: 0.12em;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-section-title::before {
|
||||
content: ''; width: 3px; height: 10px; background: var(--accent, #99ff00); flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Sub-tabs + mode switch: segmented control, active = solid accent, no glow */
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-subtabs-nav,
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-mode-bar {
|
||||
background: rgba(0, 0, 0, 0.35) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-subtab-btn,
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-mode-btn { color: var(--text-muted, #8e8e93); }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-subtab-btn:hover,
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-mode-btn:hover { color: var(--text-color, #fff); background: rgba(255, 255, 255, 0.06); }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-subtab-btn.active,
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-mode-btn.active { background: var(--accent, #99ff00); color: #000; box-shadow: none; }
|
||||
|
||||
/* Panels / cards */
|
||||
.f0ck-audio-tuner-panel :is(.f0ck-tuner-presets-bar, .f0ck-danmaku-debug-card) {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Inputs & selects */
|
||||
.f0ck-audio-tuner-panel :is(.f0ck-tuner-preset-select, .f0ck-tuner-preset-input, input[type="text"], input[type="number"], select) {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: var(--text-color, #fff);
|
||||
min-height: 30px;
|
||||
}
|
||||
.f0ck-audio-tuner-panel :is(.f0ck-tuner-preset-select, .f0ck-tuner-preset-input, input[type="text"], input[type="number"], select):focus {
|
||||
border-color: var(--accent, #99ff00); outline: none;
|
||||
}
|
||||
|
||||
/* Buttons: primary = solid accent (outline on hover), the rest outlined; no glows */
|
||||
.f0ck-audio-tuner-panel :is(.f0ck-tuner-btn-copy, .f0ck-danmaku-debug-btn.btn-primary) {
|
||||
background: var(--accent, #99ff00); border: 1px solid var(--accent, #99ff00); color: #000; filter: none;
|
||||
}
|
||||
.f0ck-audio-tuner-panel :is(.f0ck-tuner-btn-copy, .f0ck-danmaku-debug-btn.btn-primary):hover {
|
||||
background: transparent; color: var(--accent, #99ff00); filter: none;
|
||||
}
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-btn-save-default { background: transparent; border-color: var(--accent, #99ff00); }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-btn-save-default:hover { box-shadow: none; }
|
||||
.f0ck-audio-tuner-panel :is(button, select, input):focus-visible { outline: 2px solid var(--accent, #99ff00); outline-offset: 1px; }
|
||||
|
||||
/* Value readout next to each label */
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-row-label { color: var(--text-muted, #bbb); }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-row-label span:last-child {
|
||||
color: var(--accent, #99ff00); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Sliders: flat track, square accent thumb, generous hit area.
|
||||
touch-action pan-y: vertical drags still scroll the panel, horizontal drags move the slider. */
|
||||
.f0ck-audio-tuner-panel input[type="range"] {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
width: 100%; height: 22px; margin: 0; padding: 0;
|
||||
background: transparent; cursor: pointer; touch-action: pan-y; display: block;
|
||||
}
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-webkit-slider-runnable-track {
|
||||
height: 4px; background: rgba(255, 255, 255, 0.15); border: 0;
|
||||
}
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
width: 14px; height: 14px; margin-top: -5px;
|
||||
background: var(--accent, #99ff00); border: 0; box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.45);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
.f0ck-audio-tuner-panel input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.15); }
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-moz-range-track { height: 4px; background: rgba(255, 255, 255, 0.15); border: 0; }
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-moz-range-progress { height: 4px; background: var(--accent, #99ff00); }
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-moz-range-thumb {
|
||||
width: 14px; height: 14px; background: var(--accent, #99ff00); border: 0; box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.f0ck-audio-tuner-panel input[type="range"]:focus { outline: none; }
|
||||
.f0ck-audio-tuner-panel input[type="range"]:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--text-color, #fff); outline-offset: 1px; }
|
||||
|
||||
/* Touch screens: bigger thumb and row so the slider is easy to grab */
|
||||
@media (pointer: coarse) {
|
||||
.f0ck-audio-tuner-panel input[type="range"] { height: 34px; }
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-webkit-slider-thumb { width: 22px; height: 22px; margin-top: -9px; }
|
||||
.f0ck-audio-tuner-panel input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }
|
||||
.f0ck-audio-tuner-panel .f0ck-tuner-row { margin-bottom: 6px; }
|
||||
}
|
||||
|
||||
.sidebar-video-details {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
@@ -5281,6 +5406,7 @@ body.layout-legacy .blahlol {
|
||||
background: var(--bg);
|
||||
border-left: 1px solid var(--nav-border-color);
|
||||
border-right: 1px solid var(--nav-border-color);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
body.layout-legacy .comments-list:not(:has(.comment)) {
|
||||
|
||||
+77
-11
@@ -369,6 +369,55 @@
|
||||
return finishData;
|
||||
}
|
||||
|
||||
// ── Add a passkey to the current anonymous identity ───────────────────
|
||||
|
||||
async addPasskey() {
|
||||
if (!this.supported) throw new Error('WebAuthn / Passkeys are not supported in this browser.');
|
||||
|
||||
const csrfToken = (window.f0ckSession && window.f0ckSession.csrf_token)
|
||||
|| document.querySelector('meta[name="csrf-token"]')?.content || '';
|
||||
const beginRes = await fetch('/api/v2/anon/passkey/add/begin', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const beginData = await beginRes.json();
|
||||
if (!beginData.success) throw new Error(beginData.msg || 'Server error');
|
||||
|
||||
const rawChallenge = beginData.options.challenge;
|
||||
const opts = beginData.options;
|
||||
|
||||
const cred = await navigator.credentials.create({ publicKey: {
|
||||
rp: opts.rp,
|
||||
user: {
|
||||
id: b64urlToArr(opts.user.id),
|
||||
name: opts.user.name,
|
||||
displayName: opts.user.displayName
|
||||
},
|
||||
challenge: b64urlToArr(rawChallenge),
|
||||
pubKeyCredParams: opts.pubKeyCredParams,
|
||||
timeout: opts.timeout || 60000,
|
||||
excludeCredentials: (opts.excludeCredentials || []).map(c => ({ type: c.type, id: b64urlToArr(c.id) })),
|
||||
authenticatorSelection: opts.authenticatorSelection,
|
||||
attestation: opts.attestation || 'none'
|
||||
}});
|
||||
|
||||
const finishRes = await fetch('/api/v2/anon/passkey/add/finish', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
||||
body: JSON.stringify({
|
||||
challenge: rawChallenge,
|
||||
credentialId: arrToB64url(cred.rawId),
|
||||
clientDataJSON: arrToB64url(cred.response.clientDataJSON),
|
||||
attestationObject: arrToB64url(cred.response.attestationObject),
|
||||
hw_fingerprint: await getHardwareFingerprint()
|
||||
})
|
||||
});
|
||||
const finishData = await finishRes.json();
|
||||
if (!finishData.success) throw new Error(finishData.msg || 'Registration failed');
|
||||
return finishData;
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
_hasLocalPasskey() {
|
||||
@@ -490,12 +539,26 @@
|
||||
const res = await fetch('/api/v2/anon/identity');
|
||||
const data = await res.json();
|
||||
const fpEl = document.getElementById('anon-pk-fp-display');
|
||||
const credEl = document.getElementById('anon-pk-cred-display');
|
||||
if (fpEl) fpEl.textContent = data.fingerprint ? data.fingerprint.slice(7, 15) : 'none';
|
||||
if (credEl) credEl.textContent = data.credential_id || '—';
|
||||
this._renderPasskeyCount(data.passkey_count, data.passkey_max);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
_renderPasskeyCount(count, max) {
|
||||
const countEl = document.getElementById('anon-pk-count');
|
||||
const maxEl = document.getElementById('anon-pk-max');
|
||||
const addBtn = document.getElementById('anon-pk-add-btn');
|
||||
if (countEl && typeof count === 'number') countEl.textContent = count;
|
||||
if (maxEl && typeof max === 'number') maxEl.textContent = max;
|
||||
if (addBtn && typeof count === 'number' && typeof max === 'number') {
|
||||
const full = count >= max;
|
||||
addBtn.disabled = full;
|
||||
addBtn.innerHTML = full
|
||||
? '<i class="fa-solid fa-lock"></i> Limit reached'
|
||||
: '<i class="fa-solid fa-plus"></i> Add passkey';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async init() {
|
||||
@@ -568,24 +631,27 @@
|
||||
const modalOverlay = document.getElementById('anon-passkey-modal');
|
||||
if (modalOverlay) modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) this.closeModal(); });
|
||||
|
||||
// Add-passkey button inside modal (for anonymous users to add a second passkey)
|
||||
// Add-passkey button inside modal — attaches a new passkey to this identity (max enforced server-side)
|
||||
const addBtn = document.getElementById('anon-pk-add-btn');
|
||||
if (addBtn) {
|
||||
addBtn.addEventListener('click', async () => {
|
||||
const errEl = document.getElementById('anon-pk-error');
|
||||
if (errEl) errEl.style.display = 'none';
|
||||
addBtn.disabled = true;
|
||||
try {
|
||||
await this.register();
|
||||
if (typeof window.showToastNotification === 'function') window.showToastNotification('New passkey registered!');
|
||||
this._refreshModalContent();
|
||||
const data = await this.addPasskey();
|
||||
if (typeof window.showToastNotification === 'function') window.showToastNotification('Passkey added');
|
||||
this._renderPasskeyCount(data.passkey_count, data.passkey_max);
|
||||
} catch (err) {
|
||||
console.warn('[ANON_PASSKEY] Modal add passkey error:', err);
|
||||
if (err && err.name === 'NotAllowedError') {
|
||||
alert('Passkey creation was cancelled or blocked. If Bitwarden or another password manager is locked, please unlock it.');
|
||||
} else {
|
||||
alert('Failed to add passkey: ' + ((err && err.message) || 'Unknown error'));
|
||||
if (errEl) {
|
||||
errEl.style.display = '';
|
||||
errEl.textContent = (err && err.name === 'NotAllowedError')
|
||||
? 'Cancelled or blocked. Unlock your password manager and try again.'
|
||||
: ((err && err.message) || 'Failed to add passkey.');
|
||||
}
|
||||
} finally {
|
||||
addBtn.disabled = false;
|
||||
this._refreshModalContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+39
-14
@@ -1,3 +1,12 @@
|
||||
// Pages whose content depends on the rating/mime filter and must reload when it changes.
|
||||
// Profile pages count even when both previews are empty (then no .posts grid is rendered).
|
||||
// Elements whose horizontal drags belong to them (sliders, sideways-scrolling rows): swipe gestures ignore touches starting here.
|
||||
window.F0CK_NO_SWIPE_SELECTOR = 'input[type="range"], .f0ck-tuner-subtabs-nav, .f0ck-tuner-mode-bar, [data-no-swipe]';
|
||||
|
||||
window.isFilterReloadableView = function () {
|
||||
return !!document.querySelector('.posts, .tags-grid') || /^\/user\/[^/]+\/?$/.test(window.location.pathname);
|
||||
};
|
||||
|
||||
|
||||
// Normalize percent-encoded characters in the URL bar that are safe to show decoded.
|
||||
// Runs immediately so the address bar is clean before any other JS runs.
|
||||
@@ -1437,7 +1446,7 @@ window.cancelAnimFrame = (function () {
|
||||
window.flashMessage('ALL MODE ACTIVATED');
|
||||
gridCacheMap.clear();
|
||||
const isOnaraOpen = document.body.classList.contains('onara-modal-open');
|
||||
const isGridView = document.querySelector('.posts, .tags-grid');
|
||||
const isGridView = window.isFilterReloadableView();
|
||||
const isItemView = document.getElementById('prev') || document.getElementById('next') || (typeof isItemPath === 'function' ? isItemPath(window.location.pathname) : (!window.location.pathname.startsWith('/4/') && /^\/\d+(?:[?#]|$)/.test(window.location.pathname)));
|
||||
let reloadPromise = null;
|
||||
if (isOnaraOpen) {
|
||||
@@ -1501,7 +1510,7 @@ window.cancelAnimFrame = (function () {
|
||||
window.flashMessage(label);
|
||||
gridCacheMap.clear();
|
||||
const isOnaraOpen = document.body.classList.contains('onara-modal-open');
|
||||
const isGridView = document.querySelector('.posts, .tags-grid');
|
||||
const isGridView = window.isFilterReloadableView();
|
||||
const isItemView = document.getElementById('prev') || document.getElementById('next') || (typeof isItemPath === 'function' ? isItemPath(window.location.pathname) : (!window.location.pathname.startsWith('/4/') && /^\/\d+(?:[?#]|$)/.test(window.location.pathname)));
|
||||
let reloadPromise = null;
|
||||
if (isOnaraOpen) {
|
||||
@@ -13173,7 +13182,7 @@ window.cancelAnimFrame = (function () {
|
||||
|
||||
// Refresh content
|
||||
const isOnaraOpen = document.body.classList.contains('onara-modal-open');
|
||||
const isGridView = document.querySelector('.posts, .tags-grid');
|
||||
const isGridView = window.isFilterReloadableView();
|
||||
const isItemView = document.getElementById('prev') || document.getElementById('next');
|
||||
|
||||
let reloadPromise = null;
|
||||
@@ -13789,6 +13798,7 @@ window.cancelAnimFrame = (function () {
|
||||
}, 50);
|
||||
|
||||
const updateDomAfterSave = (resData) => {
|
||||
const itemPath = (resData && resData.item_path) || itemId;
|
||||
try {
|
||||
const finalRating = resData.rating || selectedRating || 'untagged';
|
||||
|
||||
@@ -13838,11 +13848,11 @@ window.cancelAnimFrame = (function () {
|
||||
sep.textContent = '→';
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = `/${itemId}`;
|
||||
a.href = `/${itemPath}`;
|
||||
a.className = 'location-link is-rehosted';
|
||||
a.title = `View rehosted post #${itemId} on f0ckm`;
|
||||
a.title = `View rehosted post /${itemPath} on f0ckm`;
|
||||
a.style.cssText = 'color: #4ade80; font-weight: 700;';
|
||||
a.innerHTML = `<i class="fa-solid fa-check"></i> #${itemId}`;
|
||||
a.innerHTML = `<i class="fa-solid fa-check"></i> /${itemPath}`;
|
||||
|
||||
locEl.appendChild(sep);
|
||||
locEl.appendChild(a);
|
||||
@@ -13860,8 +13870,8 @@ window.cancelAnimFrame = (function () {
|
||||
if (rehostBtn) {
|
||||
rehostBtn.outerHTML = `
|
||||
<span class="chan-rehost-btn-group" style="display:inline-flex;gap:4px;align-items:center;">
|
||||
<a href="/${itemId}" class="chan-rehost-action-btn rehosted" title="View Post #${itemId}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||
<i class="fa-solid fa-check"></i> #${itemId}
|
||||
<a href="/${itemPath}" class="chan-rehost-action-btn rehosted" title="View /${itemPath}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||
<i class="fa-solid fa-check"></i> /${itemPath}
|
||||
</a>
|
||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="${itemId}" title="Edit rating" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
@@ -13985,8 +13995,8 @@ window.cancelAnimFrame = (function () {
|
||||
|
||||
btn.outerHTML = `
|
||||
<span class="chan-rehost-btn-group" style="display:inline-flex;gap:4px;align-items:center;">
|
||||
<a href="/${data.item_id}" class="chan-rehost-action-btn rehosted" title="View Post #${data.item_id}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||
<i class="fa-solid fa-check"></i> #${data.item_id}
|
||||
<a href="/${data.item_path || data.item_id}" class="chan-rehost-action-btn rehosted" title="View /${data.item_path || data.item_id}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||
<i class="fa-solid fa-check"></i> /${data.item_path || data.item_id}
|
||||
</a>
|
||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="${data.item_id}" title="Edit rating" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
@@ -13997,7 +14007,7 @@ window.cancelAnimFrame = (function () {
|
||||
window.showToast(data.repost ? 'Already on site — linked to existing post' : 'Rehosted successfully!');
|
||||
}
|
||||
if (typeof window.openChanRehostModal === 'function') {
|
||||
const newAnchor = document.querySelector(`.chan-rehost-edit-btn[data-item-id="${data.item_id}"]`) || document.querySelector(`.chan-rehost-action-btn[href="/${data.item_id}"]`);
|
||||
const newAnchor = document.querySelector(`.chan-rehost-edit-btn[data-item-id="${data.item_id}"]`) || document.querySelector(`.chan-rehost-action-btn[href="/${data.item_path || data.item_id}"]`);
|
||||
window.openChanRehostModal({ itemId: data.item_id, anchorEl: newAnchor });
|
||||
}
|
||||
} else {
|
||||
@@ -16561,6 +16571,19 @@ window.cancelAnimFrame = (function () {
|
||||
document.addEventListener('touchstart', e => {
|
||||
if (window.f0ckSession?.disable_swiping) return;
|
||||
if (document.body.classList.contains('modal-open')) return;
|
||||
|
||||
// Sliders and horizontally scrolling rows own their horizontal drags: never start a
|
||||
// sidebar-drag or item-swipe from them (the swipe's preventDefault would freeze the slider)
|
||||
if (e.target?.closest?.(window.F0CK_NO_SWIPE_SELECTOR)) {
|
||||
swipeRT.xDown = null;
|
||||
swipeRT.yDown = null;
|
||||
swipeRT.xDiff = 0;
|
||||
swipeRT.yDiff = 0;
|
||||
swipeRT.isSidebarCandidate = false;
|
||||
swipeRT.isDraggingSidebar = false;
|
||||
swipeRT.startedInSidebarZone = true; // also blocks prev/next navigation on touchend
|
||||
return;
|
||||
}
|
||||
|
||||
const touch = e.touches[0];
|
||||
|
||||
@@ -17474,13 +17497,15 @@ window.cancelAnimFrame = (function () {
|
||||
if (document.body.classList.contains('modal-open')) return;
|
||||
if (!isLSActive()) return;
|
||||
|
||||
swipeRT.isDraggingLeftSidebar = false;
|
||||
swipeRT.isLeftSidebarCandidate = false;
|
||||
if (e.target?.closest?.(window.F0CK_NO_SWIPE_SELECTOR)) return;
|
||||
|
||||
const { sb, layout } = getLS();
|
||||
if (!sb || !layout) return;
|
||||
|
||||
const touch = e.touches[0];
|
||||
const isHidden = layout.classList.contains('sidebar-hidden');
|
||||
swipeRT.isDraggingLeftSidebar = false;
|
||||
swipeRT.isLeftSidebarCandidate = false;
|
||||
|
||||
if (isHidden) {
|
||||
if (touch.clientX < 60) {
|
||||
@@ -20145,7 +20170,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
// Trigger reload context-aware
|
||||
const fromModal = !!menu.closest('#excluded-tags-overlay');
|
||||
const postsEl = document.querySelector('.posts, .tags-grid');
|
||||
const postsEl = window.isFilterReloadableView();
|
||||
let reloadPromise = null;
|
||||
|
||||
// Set flag so hideAllModals (called inside loadPageAjax) skips the filter overlay
|
||||
|
||||
@@ -1455,7 +1455,7 @@
|
||||
${item.is_external && item.external_board && item.external_tid
|
||||
? `<a class="scroll-id-link" href="https://boards.4chan.org/${item.external_board}/thread/${item.external_tid}#p${item.external_id}" target="_blank">/${item.external_board}/thread/${item.external_tid}</a>`
|
||||
: (item.local_id
|
||||
? `<a class="scroll-id-link" href="/${item.local_id}" target="_blank">#${item.local_id}</a>`
|
||||
? `<a class="scroll-id-link" href="/${item.local_path || item.local_id}" target="_blank">/${item.local_path || item.local_id}</a>`
|
||||
: `<a class="scroll-id-link" href="/abyss/${item.id}">#${item.id}</a>`)}
|
||||
</div>`;
|
||||
slide.appendChild(meta);
|
||||
@@ -1486,7 +1486,7 @@
|
||||
</button>
|
||||
${item.is_external ? (
|
||||
item.local_id
|
||||
? `<a class="scroll-btn rehost-btn success" href="/${item.local_id}" target="_blank" title="${_i.already_added || 'Already added'}">
|
||||
? `<a class="scroll-btn rehost-btn success" href="/${item.local_path || item.local_id}" target="_blank" title="${_i.already_added || 'Already added'}">
|
||||
<div class="scroll-btn-icon"><i class="fa-solid fa-check"></i></div>
|
||||
<span class="scroll-btn-label">${_i.view_label || 'View'}</span>
|
||||
</a>`
|
||||
@@ -1761,6 +1761,7 @@
|
||||
|
||||
const external_media_url = `https://i.4cdn.org/${data.board}/${p.tim}${ext}`;
|
||||
const local_id = allRehosts[external_media_url] || null;
|
||||
const local_path = (local_id && data.rehost_paths && data.rehost_paths[local_id]) || local_id;
|
||||
|
||||
return {
|
||||
id: `${data.board}/${p.no}`,
|
||||
@@ -1770,6 +1771,7 @@
|
||||
external_source: '4chan',
|
||||
is_external: true,
|
||||
local_id,
|
||||
local_path,
|
||||
external_media_url,
|
||||
mime: isVideo ? 'video/unknown' : (isImage ? 'image/unknown' : 'application/octet-stream'),
|
||||
dest: `/api/v2/scroller/external/4chan/${data.board}/media/${p.tim}${ext}`,
|
||||
@@ -1972,7 +1974,7 @@
|
||||
// Update button to link to the new site-internal post
|
||||
setTimeout(() => {
|
||||
btn.outerHTML = `
|
||||
<a href="/${data.item_id}" target="_blank" class="scroll-btn rehost-btn success" style="text-decoration:none;">
|
||||
<a href="/${data.item_path || data.item_id}" target="_blank" class="scroll-btn rehost-btn success" style="text-decoration:none;">
|
||||
<div class="scroll-btn-icon"><i class="fa-solid fa-arrow-up-right-from-square"></i></div>
|
||||
<span class="scroll-btn-label">View</span>
|
||||
</a>
|
||||
@@ -1983,8 +1985,8 @@
|
||||
if (slide) {
|
||||
const idLink = slide.querySelector('.scroll-id-link');
|
||||
if (idLink) {
|
||||
idLink.href = `/${data.item_id}`;
|
||||
idLink.textContent = `#${data.item_id}`;
|
||||
idLink.href = `/${data.item_path || data.item_id}`;
|
||||
idLink.textContent = `/${data.item_path || data.item_id}`;
|
||||
}
|
||||
// Reflect rehoster's username and local timestamp
|
||||
if (window.scrollerUsername) {
|
||||
|
||||
@@ -3401,7 +3401,7 @@ window.initUploadForm = (selector) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRedirectToItem) {
|
||||
if (shouldRedirectToItem && !lastData?.manual_approval) {
|
||||
const isMultiNonAlbumShitpost = isShitpost && !isAlbum && (uploadedResults.length > 1 || successCount > 1 || totalFilesToUpload > 1);
|
||||
let targetUrl;
|
||||
if (isMultiNonAlbumShitpost) {
|
||||
@@ -3416,7 +3416,7 @@ window.initUploadForm = (selector) => {
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
}
|
||||
// else: stay on current page
|
||||
// else: stay on current page (including manual_approval pending)
|
||||
}
|
||||
} else {
|
||||
restoreBtn();
|
||||
|
||||
+5
-2
@@ -60,6 +60,9 @@
|
||||
(window.f0ckSession && window.f0ckSession.user && document.querySelector('#a_username[data-username]')?.dataset?.username?.toLowerCase() === window.f0ckSession.user.toLowerCase())
|
||||
);
|
||||
|
||||
// Anonymous uploaders may change the rating of their own item without full manage rights
|
||||
const canRate = canManage || !!document.querySelector('#tags[data-can-rate="true"]');
|
||||
|
||||
// Only remove existing dynamically generated tags
|
||||
[...inner.querySelectorAll(".badge")].forEach(tag => {
|
||||
// Don't remove the one containing the add/toggle buttons, and don't remove the autocomplete input itself
|
||||
@@ -100,7 +103,7 @@
|
||||
span.classList.add('rating-tag', `is-${tag.normalized}`);
|
||||
span.dataset.rating = tag.normalized;
|
||||
if (activePostId) span.dataset.itemId = activePostId;
|
||||
if (canManage) {
|
||||
if (canRate) {
|
||||
span.classList.add('can-cycle');
|
||||
}
|
||||
} else {
|
||||
@@ -144,7 +147,7 @@
|
||||
const hasRating = !!lastRatingTag;
|
||||
if (!hasRating) {
|
||||
const untaggedSpan = document.createElement("span");
|
||||
untaggedSpan.className = `badge badge-untagged rating-tag is-untagged${canManage ? ' can-cycle' : ''}`;
|
||||
untaggedSpan.className = `badge badge-untagged rating-tag is-untagged${canRate ? ' can-cycle' : ''}`;
|
||||
untaggedSpan.dataset.rating = 'untagged';
|
||||
if (activePostId) untaggedSpan.dataset.itemId = activePostId;
|
||||
const lbl = document.createElement("span");
|
||||
|
||||
Reference in New Issue
Block a user