improvement: separate md loader & renderer
This commit is contained in:
parent
6d360e1026
commit
d0f284dc36
2 changed files with 122 additions and 88 deletions
|
@ -1,93 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUpdate } from "vue";
|
import { ref, onMounted, onBeforeUpdate } from "vue";
|
||||||
import { marked } from "marked";
|
|
||||||
import { useTocStore } from "../stores/toc";
|
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useRoute } from "vue-router";
|
import MarkdownRender from "./markdown/MarkdownRender.vue";
|
||||||
import { useConfigStore } from "@/stores/config";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
path: string;
|
path: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const htmlContent = ref("");
|
|
||||||
const loadedPage = ref("");
|
const loadedPage = ref("");
|
||||||
|
const markdown = ref("");
|
||||||
var specialQuote: Map<string, { class: string; text: string }> = new Map();
|
|
||||||
specialQuote.set("NOTE", { class: "info", text: "Information :" });
|
|
||||||
specialQuote.set("SUCCESS", { class: "success", text: "Success :" });
|
|
||||||
specialQuote.set("WARNING", { class: "warning", text: "Warning :" });
|
|
||||||
specialQuote.set("DANGER", { class: "danger", text: "Danger :" });
|
|
||||||
|
|
||||||
var renderer = new marked.Renderer();
|
|
||||||
|
|
||||||
const toc = useTocStore();
|
|
||||||
const config = useConfigStore();
|
|
||||||
const route = useRoute();
|
|
||||||
var tocNbr = 1;
|
|
||||||
|
|
||||||
renderer.heading = function (text, level, raw) {
|
|
||||||
var anchor = "#" + raw.toLowerCase().replace(/[^\w]+/g, "-");
|
|
||||||
if (level === 2) {
|
|
||||||
toc.addTocLine(route.path, {
|
|
||||||
anchor: anchor,
|
|
||||||
order: (props.order ?? 1) * 100 + tocNbr,
|
|
||||||
text: text,
|
|
||||||
});
|
|
||||||
tocNbr++;
|
|
||||||
}
|
|
||||||
return `<h${level} id="${anchor}">${text}</h${level}>\n`;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.blockquote = function (quote) {
|
|
||||||
var bqClass = "";
|
|
||||||
var newQuote = quote;
|
|
||||||
for (const [key, quoteData] of specialQuote) {
|
|
||||||
if (quote.includes(`[!${key}]`)) {
|
|
||||||
bqClass = `bg-${quoteData?.class}`;
|
|
||||||
newQuote = newQuote.replace(
|
|
||||||
`[!${key}]`,
|
|
||||||
`<strong>${quoteData?.text}</strong>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newQuote = newQuote.replace("\n", "<br />");
|
|
||||||
return `<blockquote class="${bqClass}">${newQuote}</blockquote>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const variable = {
|
|
||||||
name: "variable",
|
|
||||||
level: "inline", // Is this a block-level or inline-level tokenizer?
|
|
||||||
start(src: string) {
|
|
||||||
return src.match(/{{/)?.index;
|
|
||||||
}, // Hint to Marked.js to stop and check for a match
|
|
||||||
tokenizer(
|
|
||||||
src: string
|
|
||||||
): { type: string; raw: string; [index: string]: any } | undefined {
|
|
||||||
const rule = /\{\{([A-Za-z0-9_]+)\}\}/; // Regex for the complete token, anchor to string start
|
|
||||||
const match = rule.exec(src);
|
|
||||||
if (match) {
|
|
||||||
return {
|
|
||||||
// Token to generate
|
|
||||||
type: "variable", // Should match "name" above
|
|
||||||
raw: match[0], // Text to consume from the source
|
|
||||||
["varName"]: match[1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token: { type: string; raw: string; [index: string]: any }): string {
|
|
||||||
const varName = token["varName"] as string | null;
|
|
||||||
const value = `${config.getVar(varName ?? "")}`;
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
marked.setOptions({
|
|
||||||
renderer: renderer,
|
|
||||||
});
|
|
||||||
marked.use({ extensions: [variable] });
|
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
const markdownFileUrl = `/${props.path}.md`;
|
const markdownFileUrl = `/${props.path}.md`;
|
||||||
|
@ -99,21 +22,17 @@ function refresh() {
|
||||||
axios
|
axios
|
||||||
.get(markdownFileUrl)
|
.get(markdownFileUrl)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
tocNbr = 1;
|
markdown.value = response.data;
|
||||||
htmlContent.value = marked.parse(response.data);
|
|
||||||
})
|
})
|
||||||
.catch(
|
.catch(
|
||||||
() =>
|
() =>
|
||||||
(htmlContent.value = marked.parse(
|
(markdown.value =
|
||||||
"# 404 Not Found \n \n La page recherchée n'a pas pu être trouvée"
|
"# 404 Not Found \n \n La page recherchée n'a pas pu être trouvée")
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
|
||||||
refresh();
|
refresh();
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
|
@ -123,7 +42,11 @@ onBeforeUpdate(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<div v-html="htmlContent" class="markdown"></div>
|
<MarkdownRender
|
||||||
|
:markdown="markdown"
|
||||||
|
:order="order"
|
||||||
|
v-if="markdown !== ''"
|
||||||
|
></MarkdownRender>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
111
src/components/markdown/MarkdownRender.vue
Normal file
111
src/components/markdown/MarkdownRender.vue
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUpdate } from "vue";
|
||||||
|
import { marked } from "marked";
|
||||||
|
import { useTocStore } from "../../stores/toc";
|
||||||
|
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useConfigStore } from "@/stores/config";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
markdown: string;
|
||||||
|
order?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const htmlContent = ref("");
|
||||||
|
|
||||||
|
var specialQuote: Map<string, { class: string; text: string }> = new Map();
|
||||||
|
specialQuote.set("NOTE", { class: "info", text: "Information :" });
|
||||||
|
specialQuote.set("SUCCESS", { class: "success", text: "Success :" });
|
||||||
|
specialQuote.set("WARNING", { class: "warning", text: "Warning :" });
|
||||||
|
specialQuote.set("DANGER", { class: "danger", text: "Danger :" });
|
||||||
|
|
||||||
|
var renderer = new marked.Renderer();
|
||||||
|
|
||||||
|
const toc = useTocStore();
|
||||||
|
const config = useConfigStore();
|
||||||
|
const route = useRoute();
|
||||||
|
var tocNbr = 1;
|
||||||
|
|
||||||
|
renderer.heading = function (text, level, raw) {
|
||||||
|
var anchor = "#" + raw.toLowerCase().replace(/[^\w]+/g, "-");
|
||||||
|
if (level === 2) {
|
||||||
|
toc.addTocLine(route.path, {
|
||||||
|
anchor: anchor,
|
||||||
|
order: (props.order ?? 1) * 100 + tocNbr,
|
||||||
|
text: text,
|
||||||
|
});
|
||||||
|
tocNbr++;
|
||||||
|
}
|
||||||
|
return `<h${level} id="${anchor}">${text}</h${level}>\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.blockquote = function (quote) {
|
||||||
|
var bqClass = "";
|
||||||
|
var newQuote = quote;
|
||||||
|
for (const [key, quoteData] of specialQuote) {
|
||||||
|
if (quote.includes(`[!${key}]`)) {
|
||||||
|
bqClass = `bg-${quoteData?.class}`;
|
||||||
|
newQuote = newQuote.replace(
|
||||||
|
`[!${key}]`,
|
||||||
|
`<strong>${quoteData?.text}</strong>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newQuote = newQuote.replace("\n", "<br />");
|
||||||
|
return `<blockquote class="${bqClass}">${newQuote}</blockquote>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const variable = {
|
||||||
|
name: "variable",
|
||||||
|
level: "inline", // Is this a block-level or inline-level tokenizer?
|
||||||
|
start(src: string) {
|
||||||
|
return src.match(/{{/)?.index;
|
||||||
|
}, // Hint to Marked.js to stop and check for a match
|
||||||
|
tokenizer(
|
||||||
|
src: string
|
||||||
|
): { type: string; raw: string; [index: string]: any } | undefined {
|
||||||
|
const rule = /\{\{([A-Za-z0-9_]+)\}\}/; // Regex for the complete token, anchor to string start
|
||||||
|
const match = rule.exec(src);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
// Token to generate
|
||||||
|
type: "variable", // Should match "name" above
|
||||||
|
raw: match[0], // Text to consume from the source
|
||||||
|
["varName"]: match[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderer(token: { type: string; raw: string; [index: string]: any }): string {
|
||||||
|
const varName = token["varName"] as string | null;
|
||||||
|
const value = `${config.getVar(varName ?? "")}`;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
renderer: renderer,
|
||||||
|
});
|
||||||
|
marked.use({ extensions: [variable] });
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
tocNbr = 1;
|
||||||
|
if (htmlContent.value === "") {
|
||||||
|
htmlContent.value = marked.parse(props.markdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUpdate(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article>
|
||||||
|
<div v-html="htmlContent" class="markdown"></div>
|
||||||
|
<slot></slot>
|
||||||
|
</article>
|
||||||
|
</template>
|
Loading…
Reference in a new issue