Skip to content

Commit 5a47b18

Browse files
committed
Initial commit: Add project files
0 parents  commit 5a47b18

4 files changed

Lines changed: 165 additions & 0 deletions

File tree

content.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
const targetSelector = '.content-container';
2+
3+
let htmlPolicy = { createHTML: (string) => string };
4+
if (window.trustedTypes && window.trustedTypes.createPolicy) {
5+
try {
6+
htmlPolicy = window.trustedTypes.createPolicy('re-build-engine', {
7+
createHTML: (string) => string,
8+
});
9+
} catch (e) { console.warn(e); }
10+
}
11+
12+
function serializeToRawText(node) {
13+
// 1. 텍스트 노드면 값 그대로 반환
14+
if (node.nodeType === Node.TEXT_NODE) {
15+
return node.nodeValue;
16+
}
17+
18+
// 2. 요소 노드 처리
19+
if (node.nodeType === Node.ELEMENT_NODE) {
20+
const tagName = node.tagName.toUpperCase();
21+
22+
// <i> 태그 -> *내용*
23+
if (tagName === 'I' || tagName === 'EM') {
24+
return `*${Array.from(node.childNodes).map(serializeToRawText).join('')}*`;
25+
}
26+
27+
// <b>, <strong> 태그 -> **내용**
28+
if (tagName === 'B' || tagName === 'STRONG') {
29+
return `**${Array.from(node.childNodes).map(serializeToRawText).join('')}**`;
30+
}
31+
32+
// <br> 태그 -> 줄바꿈
33+
if (tagName === 'BR') {
34+
return '\n';
35+
}
36+
37+
// <span class="math-inline"> -> $data-math값$
38+
if (node.classList.contains('math-inline')) {
39+
const mathData = node.getAttribute('data-math');
40+
if (mathData) {
41+
// data-math 값이 있으면 $...$ 로 감싸서 반환
42+
return `$${mathData}$`;
43+
}
44+
// data-math가 없으면 내부 텍스트라도 건짐
45+
}
46+
47+
// 그 외 일반 태그(span 등)는 껍데기 벗기고 내용물만 재귀적으로 수집
48+
return Array.from(node.childNodes).map(serializeToRawText).join('');
49+
}
50+
51+
return '';
52+
}
53+
54+
function parseAndRender(rawText) {
55+
let processedHtml = rawText
56+
.replace(/&/g, "&amp;")
57+
.replace(/</g, "&lt;")
58+
.replace(/>/g, "&gt;");
59+
60+
// 1. 인라인 코드 보호 (`...`)
61+
// 코드 블록 안의 *, $ 가 파싱되는 것을 방지
62+
const codeBlocks = [];
63+
processedHtml = processedHtml.replace(/(`+)(.*?)\1/g, (match, tick, content) => {
64+
codeBlocks.push(`<code style="background:#eee; padding:2px 4px; border-radius:3px; font-family:monospace;">${content}</code>`);
65+
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
66+
});
67+
68+
// 2. LaTeX 수식 파싱 ($...$)
69+
if (typeof katex !== 'undefined') {
70+
processedHtml = processedHtml.replace(/(?<!\\)\$([^$]+?)\$/g, (match, latex) => {
71+
try {
72+
// HTML entity로 변환된 문자들을 다시 되돌려야 KaTeX가 인식함 (&lt; -> <)
73+
const cleanLatex = latex
74+
.replace(/&lt;/g, "<")
75+
.replace(/&gt;/g, ">")
76+
.replace(/&amp;/g, "&");
77+
78+
const rendered = katex.renderToString(cleanLatex, {
79+
throwOnError: false,
80+
output: 'html',
81+
displayMode: false
82+
});
83+
return `<span class="math-inline-wrapper">${rendered}</span>`;
84+
} catch (e) {
85+
return match;
86+
}
87+
});
88+
}
89+
90+
// 3. Markdown 서식 파싱
91+
// **Bold**
92+
processedHtml = processedHtml.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
93+
// *Italic* (Step 1에서 i 태그를 *로 바꿨으므로 여기서 다시 i 태그로 복구됨)
94+
processedHtml = processedHtml.replace(/(?<!\*)\*(?!\*)(.*?)\*/g, '<i>$1</i>');
95+
96+
// 4. 코드 블록 복구
97+
processedHtml = processedHtml.replace(/__CODE_BLOCK_(\d+)__/g, (match, index) => {
98+
return codeBlocks[index];
99+
});
100+
101+
// 5. 줄바꿈 처리
102+
processedHtml = processedHtml.replace(/\n/g, '<br>');
103+
104+
return processedHtml;
105+
}
106+
107+
function reRenderContent() {
108+
const container = document.querySelector(targetSelector);
109+
if (!container) return;
110+
111+
// 아직 건드리지 않은 p 태그들 수집
112+
const paragraphs = container.querySelectorAll('p:not([data-rerendered="true"])');
113+
114+
paragraphs.forEach(p => {
115+
// 기존 DOM 해체
116+
const rawText = serializeToRawText(p);
117+
118+
// 바꿀거 없으면 스킵
119+
if (!rawText.trim() || !rawText.match(/\*|\$|`/)) {
120+
p.setAttribute('data-rerendered', 'true');
121+
return;
122+
}
123+
124+
// 텍스트 재렌더링
125+
const newHtml = parseAndRender(rawText);
126+
127+
// DOM 덮어씌우기
128+
if (p.innerHTML !== newHtml) {
129+
p.innerHTML = htmlPolicy.createHTML(newHtml);
130+
}
131+
132+
// 처리 완료 표시
133+
p.setAttribute('data-rerendered', 'true');
134+
});
135+
}
136+
137+
const observer = new MutationObserver((mutations) => {
138+
const needsUpdate = mutations.some(m =>
139+
m.addedNodes.length > 0 ||
140+
(m.type === 'childList' && m.target.tagName === 'P')
141+
);
142+
if (needsUpdate) reRenderContent();
143+
});
144+
145+
observer.observe(document.body, {
146+
childList: true,
147+
subtree: true,
148+
characterData: true
149+
});
150+
151+
reRenderContent();

0 commit comments

Comments
 (0)