feat: ajout ToC

This commit is contained in:
Kazhnuz 2023-02-07 23:30:55 +01:00
parent 132bf5f060
commit eaa1da8752
11 changed files with 145 additions and 18 deletions

View file

@ -2,6 +2,7 @@
import { RouterView } from "vue-router"; import { RouterView } from "vue-router";
import TopBar from "./components/layout/TopBar.vue"; import TopBar from "./components/layout/TopBar.vue";
import SideBar from "./components/layout/SideBar.vue"; import SideBar from "./components/layout/SideBar.vue";
import TableOfContent from "./components/layout/TableOfContent.vue";
import { useConfigStore } from "./stores/config"; import { useConfigStore } from "./stores/config";
import { onMounted } from "vue"; import { onMounted } from "vue";
import axios from "axios"; import axios from "axios";
@ -17,6 +18,9 @@ onMounted(() => {
<TopBar id="topbar" /> <TopBar id="topbar" />
<div id="wrapper"> <div id="wrapper">
<SideBar /> <SideBar />
<div id="page"><RouterView /></div> <div id="page">
<RouterView />
</div>
<TableOfContent />
</div> </div>
</template> </template>

View file

@ -1,20 +1,56 @@
<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 { marked } from "marked";
import { useTocStore } from "../stores/toc";
import axios from "axios"; import axios from "axios";
import { useRoute } from "vue-router";
const props = defineProps<{ const props = defineProps<{
path: string; path: string;
order?: number;
}>(); }>();
const htmlContent = ref(""); const htmlContent = ref("");
const loadedPage = ref("");
var renderer = new marked.Renderer();
const toc = useTocStore();
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`;
};
marked.setOptions({
renderer: renderer,
});
function refresh() { function refresh() {
const markdownFileUrl = `/${props.path}.md`; const markdownFileUrl = `/${props.path}.md`;
if (loadedPage.value === markdownFileUrl) {
return;
}
loadedPage.value = markdownFileUrl;
console.log(`Chargement de l'URL ${markdownFileUrl}`); console.log(`Chargement de l'URL ${markdownFileUrl}`);
axios axios
.get(markdownFileUrl) .get(markdownFileUrl)
.then((response) => (htmlContent.value = marked.parse(response.data))) .then((response) => {
tocNbr = 1;
htmlContent.value = marked.parse(response.data);
console.log(toc);
})
.catch( .catch(
() => () =>
(htmlContent.value = marked.parse( (htmlContent.value = marked.parse(

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
import { useTocStore } from "../../stores/toc";
import { computed } from "vue";
const toc = useTocStore();
const tocList = computed(() => {
return toc.getToc();
});
</script>
<template>
<div class="card" id="toc" v-if="tocList.length > 1">
<div class="card-header bg-primary">Sommaire</div>
<ul class="menu fg-dark">
<li v-for="(tocLine, index) in tocList" :key="index">
<router-link :to="`#${tocLine.anchor}`"
><span v-html="tocLine.text"></span
></router-link>
</li>
</ul>
</div>
</template>

View file

@ -1,10 +1,10 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHashHistory } from "vue-router";
import HomeView from "../views/HomeView.vue"; import HomeView from "../views/HomeView.vue";
import RuleView from "../views/RuleView.vue"; import RuleView from "../views/RuleView.vue";
import JdrView from "../views/JdrView.vue"; import JdrView from "../views/JdrView.vue";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: "/", path: "/",
@ -28,6 +28,15 @@ const router = createRouter({
component: () => import("../views/AboutView.vue"), component: () => import("../views/AboutView.vue"),
}, },
], ],
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: "smooth",
top: 64,
};
}
},
}); });
export default router; export default router;

27
src/stores/toc.ts Normal file
View file

@ -0,0 +1,27 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import type TocLine from "@/types/TocLine";
export const useTocStore = defineStore("toc", () => {
const currentPage = ref("");
const tocLines = ref([] as TocLine[]);
function addTocLine(page: string, line: TocLine) {
if (page !== currentPage.value) {
tocLines.value = [];
currentPage.value = page;
}
tocLines.value.push(line);
}
function getToc(): TocLine[] {
return tocLines.value.sort((a, b) => a.order - b.order);
}
function resetToc() {
tocLines.value = [];
currentPage.value = "";
}
return { currentPage, tocLines, addTocLine, getToc, resetToc };
});

View file

@ -31,6 +31,18 @@
#page { #page {
flex-grow:1; flex-grow:1;
padding-left:18rem; padding-left:18rem;
padding-right: 18rem;
}
#toc {
position: fixed;
top: 4rem;
right: 1rem;
width: 17rem;
.menu {
padding-left:0.5rem;
padding-right:0.5rem;
}
} }
#content { #content {

5
src/types/TocLine.ts Normal file
View file

@ -0,0 +1,5 @@
export default interface TocLine {
order: number;
text: string;
anchor: string;
}

View file

@ -1,15 +1,18 @@
<template> <script setup lang="ts">
<div class="about"> import { useConfigStore } from "@/stores/config";
<h1>This is an about page</h1> import { onMounted } from "vue";
</div> import { useTocStore } from "@/stores/toc";
</template> import MarkdownFile from "../components/MarkdownFile.vue";
<style> const store = useConfigStore();
@media (min-width: 1024px) { const toc = useTocStore();
.about {
min-height: 100vh; onMounted(() => {
display: flex; store.resetJdr();
align-items: center; toc.resetToc();
} });
} </script>
</style>
<template>
<MarkdownFile path="pages/about"></MarkdownFile>
</template>

View file

@ -1,12 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConfigStore } from "@/stores/config"; import { useConfigStore } from "@/stores/config";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useTocStore } from "@/stores/toc";
import MarkdownFile from "../components/MarkdownFile.vue"; import MarkdownFile from "../components/MarkdownFile.vue";
const store = useConfigStore(); const store = useConfigStore();
const toc = useTocStore();
onMounted(() => { onMounted(() => {
store.resetJdr(); store.resetJdr();
toc.resetToc();
}); });
</script> </script>

View file

@ -1,13 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConfigStore } from "@/stores/config"; import { useConfigStore } from "@/stores/config";
import { useTocStore } from "@/stores/toc";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import MarkdownFile from "../components/MarkdownFile.vue"; import MarkdownFile from "../components/MarkdownFile.vue";
const store = useConfigStore(); const store = useConfigStore();
const route = useRoute(); const route = useRoute();
const toc = useTocStore();
onMounted(() => { onMounted(() => {
toc.resetToc();
store.loadJdr(`${route.params.jdr}`); store.loadJdr(`${route.params.jdr}`);
}); });
</script> </script>

View file

@ -1,13 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConfigStore } from "@/stores/config"; import { useConfigStore } from "@/stores/config";
import { useTocStore } from "@/stores/toc";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import MarkdownFile from "../components/MarkdownFile.vue"; import MarkdownFile from "../components/MarkdownFile.vue";
const store = useConfigStore(); const store = useConfigStore();
const toc = useTocStore();
const route = useRoute(); const route = useRoute();
onMounted(() => { onMounted(() => {
toc.resetToc();
store.loadJdr(`${route.params.jdr}`); store.loadJdr(`${route.params.jdr}`);
}); });
</script> </script>