Bagaimana itu bekerja?
Ini bekerja dengan membaca sepotong string demi sepotong, yang mungkin bukan solusi terbaik untuk string yang sangat panjang.
Setiap kali parser mendeteksi bongkahan kritis sedang dibaca, yaitu '*'
atau tag penurunan harga lainnya, ia mulai menguraikan potongan elemen ini hingga parser menemukan tag penutupnya.
Ini berfungsi pada string multi-line, lihat kode misalnya.
Peringatan
Anda belum menentukan, atau saya bisa salah memahami kebutuhan Anda, jika ada kebutuhan untuk mengurai tag yang tebal dan miring , solusi saya saat ini mungkin tidak berfungsi dalam kasus ini.
Jika Anda perlu, untuk bekerja dengan kondisi di atas, cukup komentari di sini dan saya akan mengubah kode.
Pembaruan pertama: tweak bagaimana perlakuan markdown diperlakukan
Tag tidak lagi hardcoded, sebaliknya mereka adalah peta di mana Anda dapat dengan mudah memperluas agar sesuai dengan kebutuhan Anda.
Memperbaiki bug yang Anda sebutkan di komentar, terima kasih telah menunjukkan masalah ini = hal
Pembaruan kedua: tag markdown multi-panjang
Cara termudah untuk mencapai ini: mengganti multi-panjang karakter dengan unicode yang jarang digunakan
Meskipun metode parseMarkdown
ini belum mendukung tag multi-panjang, kami dapat dengan mudah mengganti tag multi-panjang dengan sederhana string.replace
ketika mengirim rawMarkdown
prop kami .
Untuk melihat contohnya dalam praktik ini, lihat ReactDOM.render
, yang terletak di akhir kode.
Bahkan jika aplikasi Anda mendukung banyak bahasa, ada karakter unicode yang tidak valid yang masih dideteksi oleh JavaScript, mis .: "\uFFFF"
bukan unicode yang valid, jika saya ingat dengan benar, tetapi JS masih dapat membandingkannya ("\uFFFF" === "\uFFFF" = true
)
Pada awalnya mungkin terlihat hack-y tetapi, tergantung pada kasus penggunaan Anda, saya tidak melihat masalah besar dengan menggunakan rute ini.
Cara lain untuk mencapai ini
Nah, kita bisa dengan mudah melacak potongan terakhir N
(di mana N
sesuai dengan panjang tag multi-panjang terpanjang).
Akan ada beberapa penyesuaian yang harus dilakukan dengan cara loop di dalam metode
parseMarkdown
berperilaku, yaitu memeriksa apakah potongan saat ini merupakan bagian dari tag multi-panjang, jika itu digunakan sebagai tag; jika tidak, dalam kasus seperti ``k
, kita harus menandainya sebagai notMultiLength
atau sesuatu yang serupa dan mendorong potongan itu sebagai konten.
Kode
// Instead of creating hardcoded variables, we can make the code more extendable
// by storing all the possible tags we'll work with in a Map. Thus, creating
// more tags will not require additional logic in our code.
const tags = new Map(Object.entries({
"*": "strong", // bold
"!": "button", // action
"_": "em", // emphasis
"\uFFFF": "pre", // Just use a very unlikely to happen unicode character,
// We'll replace our multi-length symbols with that one.
}));
// Might be useful if we need to discover the symbol of a tag
const tagSymbols = new Map();
tags.forEach((v, k) => { tagSymbols.set(v, k ); })
const rawMarkdown = `
This must be *bold*,
This also must be *bo_ld*,
this _entire block must be
emphasized even if it's comprised of multiple lines_,
This is an !action! it should be a button,
\`\`\`
beep, boop, this is code
\`\`\`
This is an asterisk\\*
`;
class App extends React.Component {
parseMarkdown(source) {
let currentTag = "";
let currentContent = "";
const parsedMarkdown = [];
// We create this variable to track possible escape characters, eg. "\"
let before = "";
const pushContent = (
content,
tagValue,
props,
) => {
let children = undefined;
// There's the need to parse for empty lines
if (content.indexOf("\n\n") >= 0) {
let before = "";
const contentJSX = [];
let chunk = "";
for (let i = 0; i < content.length; i++) {
if (i !== 0) before = content[i - 1];
chunk += content[i];
if (before === "\n" && content[i] === "\n") {
contentJSX.push(chunk);
contentJSX.push(<br />);
chunk = "";
}
if (chunk !== "" && i === content.length - 1) {
contentJSX.push(chunk);
}
}
children = contentJSX;
} else {
children = [content];
}
parsedMarkdown.push(React.createElement(tagValue, props, children))
};
for (let i = 0; i < source.length; i++) {
const chunk = source[i];
if (i !== 0) {
before = source[i - 1];
}
// Does our current chunk needs to be treated as a escaped char?
const escaped = before === "\\";
// Detect if we need to start/finish parsing our tags
// We are not parsing anything, however, that could change at current
// chunk
if (currentTag === "" && escaped === false) {
// If our tags array has the chunk, this means a markdown tag has
// just been found. We'll change our current state to reflect this.
if (tags.has(chunk)) {
currentTag = tags.get(chunk);
// We have simple content to push
if (currentContent !== "") {
pushContent(currentContent, "span");
}
currentContent = "";
}
} else if (currentTag !== "" && escaped === false) {
// We'll look if we can finish parsing our tag
if (tags.has(chunk)) {
const symbolValue = tags.get(chunk);
// Just because the current chunk is a symbol it doesn't mean we
// can already finish our currentTag.
//
// We'll need to see if the symbol's value corresponds to the
// value of our currentTag. In case it does, we'll finish parsing it.
if (symbolValue === currentTag) {
pushContent(
currentContent,
currentTag,
undefined, // you could pass props here
);
currentTag = "";
currentContent = "";
}
}
}
// Increment our currentContent
//
// Ideally, we don't want our rendered markdown to contain any '\'
// or undesired '*' or '_' or '!'.
//
// Users can still escape '*', '_', '!' by prefixing them with '\'
if (tags.has(chunk) === false || escaped) {
if (chunk !== "\\" || escaped) {
currentContent += chunk;
}
}
// In case an erroneous, i.e. unfinished tag, is present and the we've
// reached the end of our source (rawMarkdown), we want to make sure
// all our currentContent is pushed as a simple string
if (currentContent !== "" && i === source.length - 1) {
pushContent(
currentContent,
"span",
undefined,
);
}
}
return parsedMarkdown;
}
render() {
return (
<div className="App">
<div>{this.parseMarkdown(this.props.rawMarkdown)}</div>
</div>
);
}
}
ReactDOM.render(<App rawMarkdown={rawMarkdown.replace(/```/g, "\uFFFF")} />, document.getElementById('app'));
Tautan ke kode (TypeScript) https://codepen.io/ludanin/pen/GRgNWPv
Tautan ke kode (vanilla / babel) https://codepen.io/ludanin/pen/eYmBvXw
font _italic *and bold* then only italic_ and normal
? Apa yang akan menjadi hasil yang diharapkan? Atau akankah itu tidak pernah disarangkan?