feat(chan): represent replies/backlinks and fix styling for reply cards with media
This commit is contained in:
+47
-5
@@ -48,13 +48,37 @@ export default (router, tpl) => {
|
||||
|
||||
function formatComment(com) {
|
||||
if (!com) return '';
|
||||
// Format quote lines
|
||||
let formatted = com.replace(/(?:^|<br\s*\/?>)((?:>|>)((?!>|>)[^\n<]+))/g, '$1<span class="chan-quote">>$2</span>');
|
||||
// Format post cross-references >>123456
|
||||
formatted = formatted.replace(/(?:>|>)>(\d+)/g, '<a href="#p$1" class="chan-ref" data-post="$1">>>$1</a>');
|
||||
let formatted = com;
|
||||
// 1. Normalize existing 4chan intra-thread quotelinks to standard format
|
||||
formatted = formatted.replace(/<a\s+[^>]*href="#p(\d+)"[^>]*>.*?<\/a>/gi, '___CHANREF_$1___');
|
||||
// 2. Normalize cross-thread/cross-board quotelinks if any
|
||||
formatted = formatted.replace(/<a\s+[^>]*href="(\/[^"]+)"[^>]*>(.*?)<\/a>/gi, '<a href="$1" class="chan-ref chan-cross-ref" target="_blank">$2</a>');
|
||||
// 3. Format any unlinked quotes >>123456
|
||||
formatted = formatted.replace(/(?<!>)>>(\d+)\b/g, '___CHANREF_$1___');
|
||||
// 4. Also support plain >>123456 if plain text was supplied
|
||||
formatted = formatted.replace(/(?<![>\w])>>(\d+)\b/g, '___CHANREF_$1___');
|
||||
// 5. Restore CHANREF placeholders
|
||||
formatted = formatted.replace(/___CHANREF_(\d+)___/g, '<a href="#p$1" class="chan-ref quotelink" data-post="$1">>>$1</a>');
|
||||
// 6. Format quote lines (greentext)
|
||||
formatted = formatted.replace(/(^|<br\s*\/?>)>(?!>)([^\n<]+)/g, '$1<span class="chan-quote">>$2</span>');
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function extractQuotes(com) {
|
||||
if (!com) return [];
|
||||
const quotes = new Set();
|
||||
for (const m of com.matchAll(/href="#p(\d+)"/g)) {
|
||||
quotes.add(Number(m[1]));
|
||||
}
|
||||
for (const m of com.matchAll(/(?<!>)>>(\d+)\b/g)) {
|
||||
quotes.add(Number(m[1]));
|
||||
}
|
||||
for (const m of com.matchAll(/(?<![>\w])>>(\d+)\b/g)) {
|
||||
quotes.add(Number(m[1]));
|
||||
}
|
||||
return Array.from(quotes);
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// 0. GET /4 -> redirect to default board catalogue
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
@@ -145,6 +169,21 @@ export default (router, tpl) => {
|
||||
});
|
||||
|
||||
function formatPosts(posts, board, rehostMap, userFavSet, targetPostNo = null, lang = 'en') {
|
||||
// Build reply mapping (targetPostNo -> array of replier post numbers)
|
||||
const postNoSet = new Set(posts.map(p => p.no));
|
||||
const replyMap = new Map();
|
||||
|
||||
for (const p of posts) {
|
||||
if (!p.com) continue;
|
||||
const quotes = extractQuotes(p.com);
|
||||
for (const qNo of quotes) {
|
||||
if (postNoSet.has(qNo) && qNo !== p.no) {
|
||||
if (!replyMap.has(qNo)) replyMap.set(qNo, []);
|
||||
replyMap.get(qNo).push(p.no);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posts.map(p => {
|
||||
const ext = (p.ext || '').toLowerCase();
|
||||
const isVideo = ['.webm', '.mp4'].includes(ext);
|
||||
@@ -154,6 +193,7 @@ export default (router, tpl) => {
|
||||
const localId = rehostInfo ? rehostInfo.id : null;
|
||||
const effectiveTime = (localId && rehostInfo?.stamp) ? Number(rehostInfo.stamp) : p.time;
|
||||
const isoStr = new Date(effectiveTime * 1000).toISOString();
|
||||
const postReplies = replyMap.get(p.no) || [];
|
||||
|
||||
return {
|
||||
no: p.no,
|
||||
@@ -181,7 +221,9 @@ export default (router, tpl) => {
|
||||
local_id: localId,
|
||||
rehosted: !!localId,
|
||||
user_has_favorited: localId ? userFavSet.has(Number(localId)) : false,
|
||||
is_onara_active: targetPostNo ? p.no === targetPostNo : false
|
||||
is_onara_active: targetPostNo ? p.no === targetPostNo : false,
|
||||
replies: postReplies,
|
||||
replies_count: postReplies.length
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user