swadhinbiswas Initial release v0.1.0 · 1/16/2026
ed7a4e4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import DOMPurify from 'dompurify';
/**
* Parse and sanitize 4chan HTML content
*/
export function parsePostContent(html: string | undefined): string {
if (!html) return '';
// Sanitize HTML to prevent XSS
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['br', 'span', 'a', 'wbr', 's', 'strong', 'em'],
ALLOWED_ATTR: ['class', 'href'],
});
return clean;
}
/**
* Extract quote links from post content
*/
export function extractQuoteLinks(html: string | undefined): number[] {
if (!html) return [];
const quoteRegex = />>(\d+)/g;
const matches = [...html.matchAll(quoteRegex)];
return matches.map((match) => parseInt(match[1], 10));
}
/**
* Check if a line is greentext
*/
export function isGreentext(line: string): boolean {
return line.trim().startsWith('>') && !line.trim().startsWith('>>');
}
/**
* Format post content for display (identify greentext, quote links)
*/
export function formatPostContent(html: string | undefined): {
content: string;
quoteLinks: number[];
hasGreentext: boolean;
} {
if (!html) {
return { content: '', quoteLinks: [], hasGreentext: false };
}
const content = parsePostContent(html);
const quoteLinks = extractQuoteLinks(html);
const lines = html.split('<br>');
const hasGreentext = lines.some(isGreentext);
return { content, quoteLinks, hasGreentext };
}
/**
* Extract first N characters of plain text from HTML
*/
export function extractPlainText(html: string | undefined, maxLength: number = 200): string {
if (!html) return '';
// Remove HTML tags
const text = html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'");
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...';
}
/**
* Convert >>12345 style quote links to clickable elements
*/
export function renderQuoteLinks(content: string, onQuoteClick?: (postNo: number) => void): string {
return content.replace(
/>>(\d+)/g,
(match, postNo) => {
return `<span class="quotelink" data-post-no="${postNo}">>>${postNo}</span>`;
}
);
}