TOAST UI Editor(μ΄ν 'μλν°'λΌκ³ λͺ μ)λ CommonMark μ€νμ μ€μνλ©°, μΆκ°λ‘ GFM μ€νλ μ§μνλ€. νμ§λ§ λ§μ½ CommonMarkλ GFMμμ μ§μνμ§ μλ νΉμ λ¬Έλ²μ μ¬μ©νκ³ μΆλ€λ©΄ μ΄λ¨κΉ? μλ₯Ό λ€μ΄ λ§ν¬λ€μ΄μμ LaTeX λ¬Έλ²μ μ¬μ©νκ±°λ μ°¨νΈ κ°μ μμλ₯Ό λ λλ§νκ³ μΆμ μ μλ€. μλν°μμλ μ΄λ¬ν μ¬μ©μ±μ μν΄ μ¬μ©μλ§μ 컀μ€ν λΈλ‘ λ Έλλ₯Ό μ μν μ μλ μ΅μ μ μ 곡νλ€.
μλν°λ λ§ν¬λ€μ΄ AST(Abstract Syntax Tree)λ₯Ό HTML λ¬Έμμ΄λ‘ λ³νν λ 컀μ€ν°λ§μ΄μ§ν μ μλ customHTMLRenderer
μ΅μ
μ μ 곡νλ€. customHTMLRenderer
μ΅μ
μ μ¬μ©νλ©΄ table
, heading
μ²λΌ CommonMarkλ GFMμμ μ§μνλ λ
Έλμ λ λλ§ κ²°κ³Όλ₯Ό 컀μ€ν°λ§μ΄μ§ν μ μλ€. 컀μ€ν
λΈλ‘ λ
Έλ μμ μ΄ customHTMLRenderer
μ΅μ
μ μ¬μ©νμ¬ μ μν μ μλ€.
λ€μ μ½λλ LaTex λ¬Έλ²μ μ§μνλ λΌμ΄λΈλ¬λ¦¬μΈ KaTeXλ₯Ό μ¬μ©νμ¬ μμμ λ λλ§νλ 컀μ€ν λΈλ‘ λ Έλλ₯Ό μ μν κ²μ΄λ€.
const editor = new Editor({
el: document.querySelector('#editor'),
customHTMLRenderer: {
latex(node) {
const generator = new latexjs.HtmlGenerator({ hyphenate: false });
const { body } = latexjs.parse(node.literal, { generator }).htmlDocument();
return [
{ type: 'openTag', tagName: 'div', outerNewLine: true },
{ type: 'html', content: body.innerHTML },
{ type: 'closeTag', tagName: 'div', outerNewLine: true }
];
},
}
});
customHTMLRenderer
μ΅μ
μ latex
ν¨μ νλ‘νΌν°λ₯Ό μμ±νμκ³ μ΄ ν¨μμμλ λ λλ§ λ HTMLμ ν ν° ννλ‘ λ°ννλ€. λ§ν¬λ€μ΄ λ
Έλλ₯Ό 컀μ€ν°λ§μ΄μ§μ ν λμ κ±°μ λμΌν ννλ‘ μ΅μ
μ μ§μ νκΈ° λλ¬Έμ μ½κ² μ¬μ©ν μ μλ€. μμ μ½λλ λ§ν¬λ€μ΄ μλν°μμ λ€μμ²λΌ λ λλ§λλ€.
μμ μ΄λ―Έμ§μμ λ³Ό μ μλ―μ΄ λ§ν¬λ€μ΄ μλν°μμ 컀μ€ν
λΈλ‘ λ
Έλλ₯Ό μ¬μ©νκΈ° μν΄μλ $$
κΈ°νΈλ‘ κ°μΈμ§ λΈλ‘ λ΄μ ν
μ€νΈλ₯Ό μ
λ ₯ν΄μΌ νλ€. $$
κΈ°νΈλ‘ κ°μΈμ§ λΈλ‘μ μλν°μμ 컀μ€ν
λΈλ‘ λ
Έλλ‘ νμ±λλ€. λν μ΄λ ν 컀μ€ν
λΈλ‘ λ
ΈλμΈμ§ λνλ΄κΈ° μν΄ $$
κΈ°νΈ λ€μμ λ°λμ customHTMLRenderer
μ΅μ
μμ μ μν λ
Έλ μ΄λ¦μ μμ±ν΄μΌ νλ€.
// $$ κΈ°νΈ λ€μ μ΅μ
μμ μ μν λ
Έλ μ΄λ¦μ λ°λμ λͺ
μν΄μΌ νλ€.
$$latex
\documentclass{article}
\begin{document}
$
f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \, d\xi
$
\end{document}
$$
μ μλ 컀μ€ν λΈλ‘ λ Έλλ μμ§μ μλν°μμ μλ μ΄λ―Έμ§μ²λΌ λμνλ€.
μμ§μ μλν°μμ 컀μ€ν λΈλ‘ λ Έλλ λ§ν¬λ€μ΄ ν리뷰μ λμΌν λͺ¨μ΅μΌλ‘ λ λλ§λλ©°, λ Έλλ₯Ό ν΄λ¦νμ¬ μ ννμ λ λμ€λ νΈμ§ λ²νΌμ ν΅ν΄ λ΄μ©μ λ³κ²½ν μ μλ€. 컀μ€ν λΈλ‘ λ Έλλ κ²°κ΅ νΉμ ν μ€νΈλ₯Ό κΈ°μ€μΌλ‘ νμ±λλ κ²μ΄κΈ° λλ¬Έμ μμ§μ μλν°μμμ νΈμ§λ ν μ€νΈλ₯Ό κΈ°μ€μΌλ‘ νλ€. μ΄λ μΌλ°μ μΈ μμ§μ μλν°μλ λ€λ₯Έ λμμ΄μ§λ§ TOAST UI Editorλ λ§ν¬λ€μ΄μ κΈ°λ°μΌλ‘ μμ§μ μλν°λ₯Ό μ§μνκΈ° λλ¬Έμ μ΄λ¬ν λμμ΄ λ μ΄μμ μ΄λ€.
CommonMarkμμλ <
κ³Ό >
λ¬Έμλ₯Ό μ¬μ©νμ¬ κΈ°λ³Έμ μΌλ‘ μ§μνμ§ μλ λ
Έλλ₯Ό HTML λ¬Έμμ΄ ννλ‘ μμ±ν μ μλ€.
(CommonMark Raw HTML Spec μ°Έμ‘°)
μλν°μ λ§ν¬λ€μ΄ μλν°μμλ μ΄λ¬ν μ€νμ μ€μνκΈ° λλ¬Έμ HTML λ¬Έμμ΄μ λ§ν¬λ€μ΄ ν리뷰μμ μ¬λ°λ₯΄κ² λ λλ§ λλ€.
νμ§λ§ μνκΉκ²λ μμ§μ
μλν°μμλ HTML λ
Έλλ₯Ό μ λλ‘ λ λλ§ν μ μλ€. μλν°λ λ΄λΆμ μΌλ‘ μμ§μ
μλν°μμ κΈ°λ³ΈμΌλ‘ μ§μνλ λ
Έλλ₯Ό μΆμνλ λͺ¨λΈ κ°μ²΄λ‘ κ΄λ¦¬νκ³ μλ€. μμ§μ
μλν°μμ μ§μνλ λ
Έλλ CommonMarkμ GFM μμ μ§μνλ λ
Έλ(heading
, list
, strike
λ±)μ 컀μ€ν
λΈλ‘ λ
Έλλ₯Ό μλ―Ένλ€.
μ μμ μ΄λ―Έμ§μ iframe
λ
Έλλ μμ§μ
μλν°μμ κΈ°λ³Έμ μΌλ‘ μ§μνλ λ
Έλκ° μλλ€. κ·Έλ κΈ° λλ¬Έμ iframe
λ
Έλλ₯Ό μμ§μ
μλν°μμλ μ¬μ©νκ³ μΆλ€λ©΄ customHTMLRenderer
μ΅μ
μ μ¬μ©νμ¬ μΆκ° μ€μ μ ν΄μΌ νλ€.
const editor = new Editor({
el: document.querySelector('#editor'),
customHTMLRenderer: {
htmlBlock: {
iframe(node) {
return [
{ type: 'openTag', tagName: 'iframe', outerNewLine: true, attributes: node.attrs },
{ type: 'html', content: node.childrenHTML },
{ type: 'closeTag', tagName: 'iframe', outerNewLine: true },
];
},
}
},
});
HTML λ
Έλλ customHTMLRenderer.htmlBlock
νλ‘νΌν°μ μ μνλ€. μμμ μ€λͺ
ν 컀μ€ν
λΈλ‘ λ
Έλμ ꡬλΆνκΈ° μν΄ htmlBlock
νλ‘νΌν° λ΄μμ μΆκ°ν HTML λ
Έλμ 컨λ²ν
ν¨μλ₯Ό μ μνλ€. μμ μ½λλ₯Ό μ€ννλ©΄ μλ μ΄λ―Έμ§μ²λΌ μμ§μ
μμλ iframe
λ
Έλκ° μ¬λ°λ₯΄κ² λ λλ§λλ€.
λ§μ½ μΈλΌμΈ HTML λ
Έλλ₯Ό μ¬μ©νκ³ μΆλ€λ©΄, customHTMLRenderer.htmlInline
νλ‘νΌν°μ μ μνλ€.
const editor = new Editor({
el: document.querySelector('#editor'),
customHTMLRenderer: {
htmlBlock: {
iframe(node) {
return [
{ type: 'openTag', tagName: 'iframe', outerNewLine: true, attributes: node.attrs },
{ type: 'html', content: node.childrenHTML },
{ type: 'closeTag', tagName: 'iframe', outerNewLine: true },
];
},
},
htmlInline: {
big(node, { entering }) {
return entering
? { type: 'openTag', tagName: 'big', attributes: node.attrs }
: { type: 'closeTag', tagName: 'big' };
},
},
},
});