mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor(markdown): replace react-markdown with custom imageboard-style text parser
This commit is contained in:
@@ -21,36 +21,14 @@ export function getTextColorForBackground(rgb: string): string {
|
||||
return brightness > 125 ? 'black' : 'white';
|
||||
}
|
||||
|
||||
export const formatMarkdown = (content: string): string => {
|
||||
let md = content;
|
||||
if (md) {
|
||||
// Check if the content already contains valid Markdown patterns
|
||||
const alreadyFormattedPattern = /\n \n/;
|
||||
|
||||
// help the user by formatting the content if it's not already formatted
|
||||
if (!alreadyFormattedPattern.test(md)) {
|
||||
// Replace single newline with "/n /n" if followed by a newline
|
||||
md = md.replace(/\n(?=\n)/g, '\n \n');
|
||||
// Replace single newline with double newline if between two characters
|
||||
md = md.replace(/\n(?=\S)/g, '\n\n');
|
||||
}
|
||||
}
|
||||
return md;
|
||||
};
|
||||
|
||||
export function removeMarkdown(md: string): string {
|
||||
return md
|
||||
.replace(/\[([^\]]*?)][[(].*?[)\]]/g, '$1') // Remove links
|
||||
.replace(/[*_~`]/g, '') // Remove emphasis and code markers
|
||||
.replace(/^#+\s*(.+)$/gm, '$1') // Remove headers
|
||||
.replace(/^\s*[-*+]\s+/gm, '') // Remove list markers
|
||||
.replace(/^\s*>\s+/gm, '') // Remove blockquotes
|
||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') // Remove reference-style links
|
||||
.replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, '$1$3$4$6') // Remove atx-style headers
|
||||
.replace(/([*]+)(\S)(.*?\S)?\1/g, '$2$3') // Remove * emphasis
|
||||
.replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, '$1$3$4$5') // Remove _ emphasis
|
||||
.replace(/(`{3,})(.*?)\1/gm, '$2') // Remove code blocks
|
||||
.replace(/`(.+?)`/g, '$1') // Remove inline code
|
||||
.replace(/~(.*?)~/g, '$1') // Remove strike through
|
||||
.replace(/<spoiler>(.*?)<\/spoiler>/gs, '$1') // spoiler tags - keep inner text
|
||||
.replace(/\[([^\]]*?)\]\([^)]*\)/g, '$1') // [text](url) -> text
|
||||
.replace(/ /g, ' ') // -> space
|
||||
.replace(/^>\s*/gm, '') // greentext at line start
|
||||
.replace(/[*_]/g, '') // bold/italic markers
|
||||
.replace(/```[\s\S]*?```/g, (m) => m.slice(3, -3)) // code blocks - keep content
|
||||
.replace(/`([^`]*)`/g, '$1') // inline code - keep content
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -181,54 +181,3 @@ export const isValidCrossboardPattern = (pattern: string): boolean => {
|
||||
// Check if it's just a full address pattern: >>>/board.eth
|
||||
return isValidDomain(pathPart) || isValidIPNSKey(pathPart);
|
||||
};
|
||||
|
||||
// Transform >>{number} post number patterns to markdown links with special anchor
|
||||
const preprocessPostNumberPatterns = (content: string): string => {
|
||||
// Match >> followed by digits, avoid overlap with greentext (>>>), cross-board (>>>/), URLs, CID-like patterns
|
||||
return content.replace(QUOTE_NUMBER_REGEX, (_, num) => `[>>${num}](#q-${num})`);
|
||||
};
|
||||
|
||||
// Preprocess content to convert plain text 5chan cross-board patterns to markdown links
|
||||
export const preprocess5chanPatterns = (content: string): string => {
|
||||
const withPostNumbers = preprocessPostNumberPatterns(content);
|
||||
// Pattern to match ">>>/something" or ">>>/something/cid"
|
||||
// Negative lookbehind prevents matching patterns that are already part of URLs
|
||||
// Matches: >>>/directory/, >>>/directory/cid (46 chars), >>>/address, >>>/address/cid (46 chars)
|
||||
const pattern = /(?<!https?:\/\/[^\s]*)>>>\/([a-zA-Z0-9]{1,10}\/(?:[a-zA-Z0-9]{46})?|[a-zA-Z0-9\-.]+(?:\/[a-zA-Z0-9]{46})?)[.,:;!?]*/g;
|
||||
|
||||
return withPostNumbers.replace(pattern, (match, capturedPath) => {
|
||||
// Remove any trailing punctuation from the captured path
|
||||
const cleanPath = capturedPath.replace(/[.,:;!?]+$/, '');
|
||||
const fullPattern = `>>>/${cleanPath}`;
|
||||
|
||||
if (isValidCrossboardPattern(fullPattern)) {
|
||||
// Generate internal route based on pattern type
|
||||
let internalRoute: string;
|
||||
|
||||
// Directory with trailing slash: >>>/biz/ → /biz
|
||||
if (/^[a-zA-Z0-9]{1,10}\/$/.test(cleanPath)) {
|
||||
internalRoute = `/${cleanPath.slice(0, -1)}`;
|
||||
}
|
||||
// Directory + CID: >>>/biz/cid → /biz/thread/cid (CID is 46 chars)
|
||||
else if (/^[a-zA-Z0-9]{1,10}\/[a-zA-Z0-9]{46}$/.test(cleanPath)) {
|
||||
const [code, cid] = cleanPath.split('/');
|
||||
internalRoute = `/${code}/thread/${cid}`;
|
||||
}
|
||||
// Full address + CID: >>>/board.eth/cid → /board.eth/thread/cid (CID is 46 chars)
|
||||
else if (/^[^/]+\/[a-zA-Z0-9]{46}$/.test(cleanPath)) {
|
||||
const [address, cid] = cleanPath.split('/');
|
||||
internalRoute = `/${address}/thread/${cid}`;
|
||||
}
|
||||
// Just a full address: >>>/board.eth → /board.eth
|
||||
else {
|
||||
internalRoute = `/${cleanPath}`;
|
||||
}
|
||||
|
||||
// Preserve trailing punctuation outside the link
|
||||
const trailingPunctuation = match.slice(fullPattern.length);
|
||||
return `[${fullPattern}](${internalRoute})${trailingPunctuation}`;
|
||||
}
|
||||
|
||||
return match;
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user