882 lines
28 KiB
HTML
882 lines
28 KiB
HTML
@include(snippets/header)
|
||
<div class="pagewrapper">
|
||
<div id="main" class="chan-page chan-thread-page">
|
||
|
||
<!-- Top Header Navigation -->
|
||
<div class="chan-header-bar chan-thread-header">
|
||
<div class="chan-thread-nav-row">
|
||
<a href="/4/{{ board }}/catalogue" class="chan-back-btn">
|
||
<i class="fa-solid fa-arrow-left"></i> /{{ board }}/ Catalogue
|
||
</a>
|
||
<div class="chan-thread-actions">
|
||
@if(first_media_no)
|
||
<a href="/4/{{ board }}/{{ tid }}/{{ first_media_no }}" class="chan-action-btn primary" title="Open thread media in player view">
|
||
<i class="fa-solid fa-play"></i> Player View
|
||
</a>
|
||
@endif
|
||
<a href="/scroller#{{ board }}/{{ tid }}" class="chan-action-btn" title="Open in vertical video scroller">
|
||
<i class="fa-solid fa-mobile-screen"></i> Scroller
|
||
</a>
|
||
<a href="https://boards.4chan.org/{{ board }}/thread/{{ tid }}" target="_blank" rel="noopener noreferrer" class="chan-action-btn" title="View original thread on 4chan">
|
||
<i class="fa-solid fa-arrow-up-right-from-square"></i> 4chan.org
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chan-thread-title-row">
|
||
<span class="chan-clover-icon"><i class="fa-solid fa-clover"></i></span>
|
||
<div class="chan-thread-title-info">
|
||
<h2>
|
||
@if(op.sub)
|
||
{{ op.sub }}
|
||
@else
|
||
Thread #{{ tid }}
|
||
@endif
|
||
</h2>
|
||
<div class="chan-thread-meta">
|
||
<span class="chan-meta-item"><i class="fa-solid fa-comments"></i> {{ posts.length }} posts</span>
|
||
<span class="chan-meta-item"><i class="fa-solid fa-image"></i> {{ media_count }} media files</span>
|
||
@if(rehosts_count > 0)
|
||
<span class="chan-meta-item" style="color: #4ade80;"><i class="fa-solid fa-check"></i> {{ rehosts_count }} rehosted</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter Toggle: All vs Media Only vs Rehosted -->
|
||
<div class="chan-filter-toggle">
|
||
<button type="button" class="chan-toggle-btn active" id="filter-all-btn">All Posts ({{ posts.length }})</button>
|
||
<button type="button" class="chan-toggle-btn" id="filter-media-btn">Media Only ({{ media_count }})</button>
|
||
@if(rehosts_count > 0)
|
||
<button type="button" class="chan-toggle-btn" id="filter-rehosted-btn" style="color: #4ade80;"><i class="fa-solid fa-check"></i> Rehosted ({{ rehosts_count }})</button>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Posts Stream -->
|
||
<div class="chan-posts-stream" id="chan-posts-stream">
|
||
@each(posts as p)
|
||
<article class="chan-post-card {{ p.no === tid ? 'is-op' : 'is-reply' }} {{ p.has_media ? 'has-media' : 'no-media' }} {{ p.local_id ? 'is-rehosted' : '' }}" id="p{{ p.no }}" data-no="{{ p.no }}" data-has-media="{{ p.has_media ? 1 : 0 }}" data-is-rehosted="{{ p.local_id ? 1 : 0 }}">
|
||
|
||
<!-- Post Header -->
|
||
<div class="chan-post-header">
|
||
<span class="chan-post-name">{{ p.name }}</span>
|
||
@if(p.trip)
|
||
<span class="chan-post-trip">{{ p.trip }}</span>
|
||
@endif
|
||
<time class="chan-post-time timeago" data-iso="{{ p.iso_str }}" tooltip="{{ p.iso_str }}" title="{{ p.date_str }}">{{ p.timeago }}</time>
|
||
<a href="#p{{ p.no }}" class="chan-post-no">No. {{ p.no }}</a>
|
||
@if(p.local_id)
|
||
<a href="/{{ p.local_id }}" target="_blank" class="chan-post-rehosted-pill" title="Already rehosted on f0ckm as Post #{{ p.local_id }}">
|
||
<i class="fa-solid fa-check"></i> #{{ p.local_id }}
|
||
</a>
|
||
@endif
|
||
@if(p.sub && p.no !== tid)
|
||
<span class="chan-reply-sub">{{ p.sub }}</span>
|
||
@endif
|
||
@if(p.has_media)
|
||
<div class="chan-post-media-badge">
|
||
<i class="fa-solid fa-paperclip"></i>
|
||
<span>{{ p.ext.toUpperCase().replace('.', '') }}</span>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- Post Content (Media + Comment) -->
|
||
<div class="chan-post-content">
|
||
@if(p.has_media)
|
||
<div class="chan-media-wrapper">
|
||
<div class="chan-thumb-box">
|
||
<a href="/4/{{ board }}/{{ tid }}/{{ p.no }}" class="chan-thumb-link" title="Open in player view">
|
||
@if(p.thumb)
|
||
<img src="{{ p.thumb }}" alt="Media #{{ p.no }}" class="chan-thumb-img" loading="lazy" />
|
||
@else
|
||
<div class="chan-no-thumb"><i class="fa-solid fa-file"></i></div>
|
||
@endif
|
||
@if(p.is_video)
|
||
<span class="chan-video-indicator"><i class="fa-solid fa-play"></i></span>
|
||
@endif
|
||
@if(p.local_id)
|
||
<span class="chan-thumb-rehost-badge" title="Rehosted on f0ckm as Post #{{ p.local_id }}"><i class="fa-solid fa-check"></i> #{{ p.local_id }}</span>
|
||
@endif
|
||
</a>
|
||
</div>
|
||
|
||
<div class="chan-file-info">
|
||
<div class="chan-filename-row">
|
||
<a href="{{ p.external_media_url }}" target="_blank" rel="noopener noreferrer" class="chan-file-download" title="Open full media in new tab">
|
||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||
<span class="chan-file-title">{{ p.filename || ('File ' + p.tim + p.ext) }}</span>
|
||
</a>
|
||
</div>
|
||
<div class="chan-file-meta-row">
|
||
@if(p.fsize)<span>{{ p.fsize }}</span>@endif
|
||
@if(p.w && p.h)<span>{{ p.w }}×{{ p.h }}</span>@endif
|
||
</div>
|
||
|
||
<!-- Rehost & Favorite actions -->
|
||
<div class="chan-rehost-container" id="rehost-box-{{ p.no }}">
|
||
@if(p.local_id)
|
||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;flex:1;">
|
||
<a href="/{{ p.local_id }}" class="chan-rehost-btn rehosted" target="_blank">
|
||
<i class="fa-solid fa-check"></i> Rehosted #{{ p.local_id }}
|
||
</a>
|
||
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ p.local_id }}" title="Edit rating, tags & comment" style="padding:4px 7px;border-radius:4px;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</button>
|
||
</span>
|
||
@else
|
||
<button type="button" class="chan-rehost-btn" data-url="{{ p.external_media_url }}" data-board="{{ board }}" data-tid="{{ tid }}" data-post="{{ p.no }}" data-filename="{{ p.filename || '' }}" data-width="{{ p.w || '' }}" data-height="{{ p.h || '' }}">
|
||
<i class="fa-solid fa-cloud-arrow-up"></i> Rehost
|
||
</button>
|
||
@endif
|
||
<button type="button" class="chan-card-fav-btn {{ p.user_has_favorited ? 'favorited' : '' }}" data-post="{{ p.no }}" data-local-id="{{ p.local_id || '' }}" data-url="{{ p.external_media_url }}" data-board="{{ board }}" data-tid="{{ tid }}" data-filename="{{ p.filename || '' }}" data-width="{{ p.w || '' }}" data-height="{{ p.h || '' }}" title="{{ p.user_has_favorited ? 'Remove from favorites' : 'Favorite' }}">
|
||
<i class="fa-{{ p.user_has_favorited ? 'solid' : 'regular' }} fa-heart"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Post Comment -->
|
||
@if(p.com_raw)
|
||
<div class="chan-post-comment">
|
||
{{ p.com_formatted }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
</article>
|
||
@endeach
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.chan-thread-page {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px 15px 80px;
|
||
}
|
||
.chan-thread-header {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
margin-bottom: 25px;
|
||
background: var(--bg-card, rgba(25, 25, 25, 0.85));
|
||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||
border-radius: 12px;
|
||
padding: 16px 20px;
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
.chan-thread-nav-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.chan-back-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 14px;
|
||
border-radius: 8px;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-main, #fff);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
font-size: 0.88rem;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-back-btn:hover {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
}
|
||
.chan-thread-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.chan-action-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-muted, #aaa);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-action-btn:hover {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
color: #fff;
|
||
}
|
||
.chan-action-btn.primary {
|
||
background: rgba(74, 222, 128, 0.15);
|
||
color: #4ade80;
|
||
border-color: rgba(74, 222, 128, 0.3);
|
||
}
|
||
.chan-action-btn.primary:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
|
||
.chan-thread-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||
padding-top: 14px;
|
||
}
|
||
.chan-thread-title-info h2 {
|
||
font-size: 1.35rem;
|
||
font-weight: 800;
|
||
margin: 0 0 4px;
|
||
color: #fff;
|
||
}
|
||
.chan-thread-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 0.82rem;
|
||
color: #888;
|
||
}
|
||
.chan-meta-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
/* Filter toggle */
|
||
.chan-filter-toggle {
|
||
display: inline-flex;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 8px;
|
||
padding: 3px;
|
||
gap: 3px;
|
||
}
|
||
.chan-toggle-btn {
|
||
background: none;
|
||
border: none;
|
||
padding: 5px 12px;
|
||
border-radius: 6px;
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-toggle-btn.active {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Posts stream */
|
||
.chan-posts-stream {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.chan-post-card {
|
||
background: var(--bg-card, rgba(25, 25, 25, 0.85));
|
||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||
border-radius: 10px;
|
||
padding: 14px 18px;
|
||
transition: border-color 0.2s, background-color 0.2s;
|
||
}
|
||
.chan-post-card.is-rehosted {
|
||
border-color: rgba(74, 222, 128, 0.3);
|
||
background: rgba(74, 222, 128, 0.03);
|
||
}
|
||
.chan-post-card.is-op {
|
||
border-left: 4px solid #4ade80;
|
||
background: rgba(30, 30, 30, 0.95);
|
||
}
|
||
.chan-post-card:target {
|
||
border-color: #4ade80;
|
||
background: rgba(74, 222, 128, 0.05);
|
||
}
|
||
|
||
.chan-post-rehosted-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 2px 7px;
|
||
border-radius: 4px;
|
||
background: rgba(74, 222, 128, 0.2);
|
||
color: #4ade80;
|
||
border: 1px solid rgba(74, 222, 128, 0.4);
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-post-rehosted-pill:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
|
||
.chan-thumb-rehost-badge {
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 6px;
|
||
background: rgba(16, 185, 129, 0.92);
|
||
color: #fff;
|
||
font-size: 0.68rem;
|
||
font-weight: 700;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
backdrop-filter: blur(4px);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||
z-index: 2;
|
||
}
|
||
|
||
.chan-post-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
font-size: 0.82rem;
|
||
margin-bottom: 10px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
padding-bottom: 6px;
|
||
}
|
||
.chan-post-name {
|
||
font-weight: 700;
|
||
color: #10b981;
|
||
}
|
||
.chan-post-trip {
|
||
color: #888;
|
||
font-weight: 600;
|
||
}
|
||
.chan-post-time {
|
||
color: #777;
|
||
}
|
||
.chan-post-no {
|
||
color: #aaa;
|
||
text-decoration: none;
|
||
font-family: monospace;
|
||
}
|
||
.chan-post-no:hover {
|
||
color: #4ade80;
|
||
text-decoration: underline;
|
||
}
|
||
.chan-reply-sub {
|
||
color: #fbbf24;
|
||
font-weight: 600;
|
||
}
|
||
.chan-post-media-badge {
|
||
margin-left: auto;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: #888;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.chan-post-body {
|
||
display: flex;
|
||
gap: 18px;
|
||
align-items: flex-start;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.chan-post-body {
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
/* Media Box */
|
||
.chan-media-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
width: 200px;
|
||
flex-shrink: 0;
|
||
}
|
||
.chan-media-thumb-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
aspect-ratio: 16 / 10;
|
||
background: #000;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
.chan-media-link {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.chan-media-thumb {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
transition: transform 0.2s;
|
||
}
|
||
.chan-media-thumb-wrap:hover .chan-media-thumb {
|
||
transform: scale(1.05);
|
||
}
|
||
.chan-media-play-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
color: #fff;
|
||
font-size: 1.5rem;
|
||
}
|
||
.chan-media-thumb-wrap:hover .chan-media-play-overlay {
|
||
opacity: 1;
|
||
}
|
||
.chan-media-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 0.75rem;
|
||
}
|
||
.chan-media-filename {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.chan-media-filename a {
|
||
color: #aaa;
|
||
text-decoration: none;
|
||
}
|
||
.chan-media-filename a:hover {
|
||
color: #4ade80;
|
||
}
|
||
.chan-media-meta {
|
||
color: #777;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* Rehost button */
|
||
.chan-rehost-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
border-radius: 6px;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
background: rgba(74, 222, 128, 0.15);
|
||
color: #4ade80;
|
||
border: 1px solid rgba(74, 222, 128, 0.3);
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
width: 100%;
|
||
flex: 1;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
.chan-rehost-btn:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
.chan-rehost-btn.rehosted {
|
||
background: rgba(74, 222, 128, 0.2);
|
||
color: #4ade80;
|
||
cursor: default;
|
||
}
|
||
.chan-rehost-btn.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.chan-rehost-container {
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
.chan-card-fav-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 28px;
|
||
width: 28px;
|
||
min-width: 28px;
|
||
padding: 0;
|
||
border-radius: 6px;
|
||
font-size: 0.85rem;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
color: #888;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
box-sizing: border-box;
|
||
}
|
||
.chan-card-fav-btn:hover {
|
||
background: rgba(255, 75, 75, 0.15);
|
||
border-color: rgba(255, 75, 75, 0.4);
|
||
color: #ff4b4b;
|
||
}
|
||
.chan-card-fav-btn.favorited {
|
||
background: rgba(255, 75, 75, 0.2);
|
||
border-color: rgba(255, 75, 75, 0.5);
|
||
color: #ff4b4b;
|
||
}
|
||
.chan-card-fav-btn.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Comments & Greentext */
|
||
.chan-post-comment {
|
||
color: #ddd;
|
||
font-size: 0.88rem;
|
||
line-height: 1.5;
|
||
word-break: break-word;
|
||
flex: 1;
|
||
}
|
||
.chan-quote,
|
||
.chan-post-comment .quote {
|
||
color: #789922;
|
||
}
|
||
.chan-ref,
|
||
.chan-post-comment .quotelink {
|
||
color: #d00;
|
||
text-decoration: underline;
|
||
font-family: monospace;
|
||
cursor: pointer;
|
||
}
|
||
.chan-ref:hover,
|
||
.chan-post-comment .quotelink:hover {
|
||
color: #ff3333;
|
||
}
|
||
.chan-post-comment s,
|
||
.chan-post-comment .spoiler {
|
||
background: #333;
|
||
color: #333;
|
||
padding: 0 3px;
|
||
border-radius: 2px;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
transition: color 0.15s;
|
||
}
|
||
.chan-post-comment s:hover,
|
||
.chan-post-comment .spoiler:hover {
|
||
color: #fff;
|
||
}
|
||
|
||
/* Quote preview popup */
|
||
.chan-quote-preview-popup {
|
||
position: fixed;
|
||
z-index: 99999;
|
||
max-width: 480px;
|
||
background: #1e1e1e;
|
||
border: 1px solid #4ade80;
|
||
border-radius: 8px;
|
||
padding: 12px 14px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.7);
|
||
pointer-events: none;
|
||
color: #eee;
|
||
font-size: 0.85rem;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
(() => {
|
||
// Toggle filter: All vs Media Only
|
||
const filterAllBtn = document.getElementById('filter-all-btn');
|
||
const filterMediaBtn = document.getElementById('filter-media-btn');
|
||
const filterRehostedBtn = document.getElementById('filter-rehosted-btn');
|
||
const posts = document.querySelectorAll('.chan-post-card');
|
||
|
||
if (filterAllBtn && filterMediaBtn) {
|
||
filterAllBtn.addEventListener('click', () => {
|
||
filterAllBtn.classList.add('active');
|
||
filterMediaBtn.classList.remove('active');
|
||
if (filterRehostedBtn) filterRehostedBtn.classList.remove('active');
|
||
posts.forEach(p => p.style.display = 'block');
|
||
});
|
||
|
||
filterMediaBtn.addEventListener('click', () => {
|
||
filterMediaBtn.classList.add('active');
|
||
filterAllBtn.classList.remove('active');
|
||
if (filterRehostedBtn) filterRehostedBtn.classList.remove('active');
|
||
posts.forEach(p => {
|
||
p.style.display = p.dataset.hasMedia === '1' ? 'block' : 'none';
|
||
});
|
||
});
|
||
|
||
if (filterRehostedBtn) {
|
||
filterRehostedBtn.addEventListener('click', () => {
|
||
filterRehostedBtn.classList.add('active');
|
||
filterAllBtn.classList.remove('active');
|
||
filterMediaBtn.classList.remove('active');
|
||
posts.forEach(p => {
|
||
p.style.display = p.dataset.isRehosted === '1' ? 'block' : 'none';
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// Inline Rehosting
|
||
document.addEventListener('click', async (e) => {
|
||
const btn = e.target.closest('.chan-rehost-btn:not(.rehosted)');
|
||
if (!btn) return;
|
||
|
||
e.preventDefault();
|
||
const url = btn.dataset.url;
|
||
const board = btn.dataset.board;
|
||
const tid = btn.dataset.tid;
|
||
const postNo = btn.dataset.post;
|
||
const filename = btn.dataset.filename;
|
||
if (!url) return;
|
||
|
||
btn.classList.add('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Rehosting…';
|
||
|
||
try {
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const resp = await fetch('/api/v2/scroller/rehost', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: new URLSearchParams({
|
||
url,
|
||
original_filename: filename || '',
|
||
width: btn.dataset.width || '',
|
||
height: btn.dataset.height || ''
|
||
})
|
||
});
|
||
|
||
const data = await resp.json();
|
||
if (data.success && data.item_id) {
|
||
btn.classList.remove('loading');
|
||
btn.classList.add('rehosted');
|
||
btn.outerHTML = '<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">' +
|
||
'<a href="/' + data.item_id + '" class="chan-rehost-btn rehosted" target="_blank">' +
|
||
'<i class="fa-solid fa-check"></i> Rehosted #' + data.item_id + '</a>' +
|
||
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + data.item_id + '" title="Edit rating, tags & comment" style="padding:4px 7px;border-radius:4px;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
||
'<i class="fa-solid fa-pen"></i></button></span>';
|
||
|
||
const postCard = document.getElementById('p' + postNo) || btn.closest('.chan-post-card');
|
||
if (postCard) {
|
||
const timeEl = postCard.querySelector('.chan-post-time, .timeago');
|
||
if (timeEl) {
|
||
const nowIso = new Date().toISOString();
|
||
timeEl.dataset.iso = nowIso;
|
||
const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString();
|
||
timeEl.setAttribute('tooltip', fullDate);
|
||
timeEl.setAttribute('title', fullDate);
|
||
timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now'));
|
||
}
|
||
}
|
||
|
||
if (typeof window.showToast === 'function') {
|
||
window.showToast(data.repost ? 'Already on site — linked to existing post' : 'Rehosted successfully!');
|
||
}
|
||
if (typeof window.openChanRehostModal === 'function') {
|
||
window.openChanRehostModal({ itemId: data.item_id, isPlayerView: false });
|
||
}
|
||
} else {
|
||
btn.classList.remove('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Retry';
|
||
alert(data.msg || data.message || 'Rehosting failed');
|
||
}
|
||
} catch (err) {
|
||
console.error('[CHAN] Rehost error:', err);
|
||
btn.classList.remove('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Error';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('click', (e) => {
|
||
const editBtn = e.target.closest('.chan-rehost-edit-btn');
|
||
if (editBtn) {
|
||
e.preventDefault();
|
||
e.stopImmediatePropagation();
|
||
const itemId = editBtn.dataset.itemId;
|
||
if (itemId && typeof window.openChanRehostModal === 'function') {
|
||
window.openChanRehostModal({ itemId: Number(itemId), isPlayerView: false });
|
||
}
|
||
}
|
||
});
|
||
|
||
// Favorite from Thread Card (Auto-rehost if needed, then toggle favorite)
|
||
document.addEventListener('click', async (e) => {
|
||
const favBtn = e.target.closest('.chan-card-fav-btn');
|
||
if (!favBtn || favBtn.classList.contains('loading')) return;
|
||
|
||
e.preventDefault();
|
||
|
||
if (window.f0ckSession?.is_anon && window.f0ckSession?.anon_permissions && window.f0ckSession.anon_permissions.favorite === false) {
|
||
if (typeof window.flashMessage === 'function') window.flashMessage('Anonymous favoriting is disabled.', 3000, 'error');
|
||
return;
|
||
}
|
||
if (!window.f0ckSession || !window.f0ckSession.user) {
|
||
if (typeof window.flashMessage === 'function') window.flashMessage('Login to favorite posts', 3000, 'warning');
|
||
return;
|
||
}
|
||
|
||
const postNo = favBtn.dataset.post;
|
||
let localId = favBtn.dataset.localId;
|
||
const mediaUrl = favBtn.dataset.url;
|
||
|
||
favBtn.classList.add('loading');
|
||
const origHtml = favBtn.innerHTML;
|
||
favBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
|
||
|
||
try {
|
||
// If not yet rehosted, auto-rehost first
|
||
if (!localId && mediaUrl) {
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: new URLSearchParams({
|
||
url: mediaUrl,
|
||
original_filename: favBtn.dataset.filename || '',
|
||
width: favBtn.dataset.width || '',
|
||
height: favBtn.dataset.height || ''
|
||
})
|
||
});
|
||
const rehostData = await rehostResp.json();
|
||
if (rehostData.success && rehostData.item_id) {
|
||
localId = rehostData.item_id;
|
||
favBtn.dataset.localId = localId;
|
||
|
||
// Update rehost box
|
||
const rehostBox = document.getElementById('rehost-box-' + postNo);
|
||
if (rehostBox) {
|
||
const rehostBtn = rehostBox.querySelector('.chan-rehost-btn');
|
||
if (rehostBtn) {
|
||
rehostBtn.outerHTML = '<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;flex:1;">' +
|
||
'<a href="/' + localId + '" class="chan-rehost-btn rehosted" target="_blank">' +
|
||
'<i class="fa-solid fa-check"></i> Rehosted #' + localId + '</a>' +
|
||
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + localId + '" title="Edit rating, tags & comment" style="padding:4px 7px;border-radius:4px;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
||
'<i class="fa-solid fa-pen"></i></button></span>';
|
||
}
|
||
}
|
||
// Add thumb badge if missing
|
||
const postCard = document.getElementById('p' + postNo) || favBtn.closest('.chan-post-card');
|
||
if (postCard) {
|
||
const thumbLink = postCard.querySelector('.chan-thumb-link');
|
||
if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) {
|
||
const badge = document.createElement('span');
|
||
badge.className = 'chan-thumb-rehost-badge';
|
||
badge.title = 'Rehosted on f0ckm as Post #' + localId;
|
||
badge.innerHTML = '<i class="fa-solid fa-check"></i> #' + localId;
|
||
thumbLink.appendChild(badge);
|
||
}
|
||
const timeEl = postCard.querySelector('.chan-post-time, .timeago');
|
||
if (timeEl) {
|
||
const nowIso = new Date().toISOString();
|
||
timeEl.dataset.iso = nowIso;
|
||
const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString();
|
||
timeEl.setAttribute('tooltip', fullDate);
|
||
timeEl.setAttribute('title', fullDate);
|
||
timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now'));
|
||
}
|
||
}
|
||
} else {
|
||
throw new Error(rehostData.msg || 'Rehost failed');
|
||
}
|
||
}
|
||
|
||
// Now toggle favorite
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const favResp = await fetch('/api/v2/togglefav', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: JSON.stringify({ postid: Number(localId), chan_url: mediaUrl })
|
||
});
|
||
const favData = await favResp.json();
|
||
favBtn.classList.remove('loading');
|
||
|
||
if (favData.success) {
|
||
const isNowFav = !!favData.favorited;
|
||
favBtn.classList.toggle('favorited', isNowFav);
|
||
favBtn.innerHTML = '<i class="fa-' + (isNowFav ? 'solid' : 'regular') + ' fa-heart"></i>';
|
||
favBtn.title = isNowFav ? 'Remove from favorites' : 'Favorite';
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage(isNowFav ? 'ADDED TO FAVORITES' : 'REMOVED FROM FAVORITES');
|
||
}
|
||
} else {
|
||
favBtn.innerHTML = origHtml;
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage(favData.msg || 'Failed to update favorite', 3000, 'error');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('[CHAN-CARD-FAV] Error:', err);
|
||
favBtn.classList.remove('loading');
|
||
favBtn.innerHTML = origHtml;
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage('Failed to update favorite', 3000, 'error');
|
||
}
|
||
}
|
||
});
|
||
|
||
// Quote hover preview
|
||
let previewEl = null;
|
||
document.addEventListener('mouseover', (e) => {
|
||
const ref = e.target.closest('.chan-ref, .quotelink');
|
||
if (!ref) return;
|
||
|
||
const targetPostNo = ref.dataset.post || ref.getAttribute('href')?.replace(/^#p?/, '');
|
||
if (!targetPostNo) return;
|
||
const targetCard = document.getElementById('p' + targetPostNo);
|
||
if (!targetCard) return;
|
||
|
||
if (!previewEl) {
|
||
previewEl = document.createElement('div');
|
||
previewEl.className = 'chan-quote-preview-popup';
|
||
document.body.appendChild(previewEl);
|
||
}
|
||
|
||
previewEl.innerHTML = targetCard.innerHTML;
|
||
previewEl.style.display = 'block';
|
||
|
||
const rect = ref.getBoundingClientRect();
|
||
let top = rect.bottom + 8;
|
||
let left = rect.left;
|
||
if (left + 480 > window.innerWidth) left = window.innerWidth - 490;
|
||
if (top + 200 > window.innerHeight) top = rect.top - 210;
|
||
|
||
previewEl.style.top = Math.max(10, top) + 'px';
|
||
previewEl.style.left = Math.max(10, left) + 'px';
|
||
});
|
||
|
||
document.addEventListener('mouseout', (e) => {
|
||
const ref = e.target.closest('.chan-ref, .quotelink');
|
||
if (ref && previewEl) {
|
||
previewEl.style.display = 'none';
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
@include(snippets/footer)
|