New Website in VueJS #6
1 changed files with 46 additions and 64 deletions
|
|
@ -1,94 +1,64 @@
|
|||
<template>
|
||||
<div class="min-h-screen bg-base-100 text-base-content">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-6 gap-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-5 gap-8">
|
||||
<!-- Sidebar con navigazione pagine wiki -->
|
||||
<aside class="lg:col-span-1">
|
||||
<nav
|
||||
class="sticky top-4 bg-base-200 rounded-lg p-4 border border-base-300"
|
||||
>
|
||||
<h2 class="text-lg font-bold mb-4 text-primary">Wiki</h2>
|
||||
|
||||
<aside class="lg:col-span-1 sticky top-4">
|
||||
<nav class="rounded-lg">
|
||||
<!-- Lista pagine dalla sidebar -->
|
||||
<div class="space-y-2 text-sm">
|
||||
<div v-for="(item, index) in sidebarItems" :key="index">
|
||||
<!-- Heading di sezione -->
|
||||
<h3
|
||||
v-if="item.type === 'heading'"
|
||||
class="text-xs font-bold text-secondary uppercase mt-4 mb-2"
|
||||
>
|
||||
<h3 v-if="item.type === 'heading'" class="text-xs font-bold text-secondary uppercase mt-4 mb-2">
|
||||
{{ item.text }}
|
||||
</h3>
|
||||
<!-- Link a pagina -->
|
||||
<a
|
||||
v-else-if="item.type === 'link'"
|
||||
:href="item.isExternal ? item.url : '#'"
|
||||
:target="item.isExternal ? '_blank' : null"
|
||||
:rel="item.isExternal ? 'noopener noreferrer' : null"
|
||||
<a v-else-if="item.type === 'link'" :href="item.isExternal ? item.url : '#'"
|
||||
:target="item.isExternal ? '_blank' : null" :rel="item.isExternal ? 'noopener noreferrer' : null"
|
||||
@click="onSidebarLinkClick($event, item)"
|
||||
class="block py-1.5 px-2 rounded hover:bg-base-300 transition truncate"
|
||||
:class="{
|
||||
'bg-base-300 text-primary':
|
||||
class="block py-1 px-2 rounded-lg hover:bg-base-300/20 transition truncate" :class="{
|
||||
'bg-base-300/20 text-primary font-bold':
|
||||
!item.isExternal && currentPage === item.slug,
|
||||
}"
|
||||
:title="item.text"
|
||||
>
|
||||
{{ item.text }}
|
||||
}" :title="item.text">
|
||||
{{ item.text }}<span v-if="item.isExternal" class="ml-1 text-base-content/60">↗</span>
|
||||
</a>
|
||||
<!-- TOC (Table of Contents) - Outline minimal terminal style -->
|
||||
<nav v-if="currentPage === item.slug && outline.length > 0" class="sticky top-4">
|
||||
<ul class="space-y-0 text-xs font-mono leading-snug border-l border-base-300 pl-3">
|
||||
<li v-for="heading in outline" :key="heading.id">
|
||||
<a :href="`#${heading.id}`" @click.prevent="scrollToHeading(heading.id)"
|
||||
class="block py-0.5 hover:text-base-content rounded-lg transition truncate"
|
||||
:style="{ paddingLeft: `${(heading.level - 1) * 0.5}rem` }" :class="{
|
||||
'text-primary bg-base-300/20': activeHeading === heading.id,
|
||||
'text-base-content/60': activeHeading !== heading.id,
|
||||
}" :title="heading.text">
|
||||
{{ heading.text }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Contenuto principale -->
|
||||
<main class="lg:col-span-5">
|
||||
<main class="lg:col-span-4">
|
||||
<div v-if="loading" class="text-center py-12">
|
||||
<span class="text-primary"
|
||||
>Loading<span class="blinking-cursor"></span
|
||||
></span>
|
||||
<span class="text-primary">Loading<span class="blinking-cursor"></span></span>
|
||||
</div>
|
||||
|
||||
<article
|
||||
v-else
|
||||
class="bg-base-200 rounded-lg p-8 border border-base-300"
|
||||
>
|
||||
<article v-else class="bg-base-200 rounded-lg p-8 border border-base-300">
|
||||
<h1 class="text-4xl font-bold text-primary mb-6">
|
||||
{{ pageTitle }}<span class="blinking-cursor"></span>
|
||||
</h1>
|
||||
<div
|
||||
class="markdown-content prose prose-invert max-w-none"
|
||||
v-html="pageContent"
|
||||
></div>
|
||||
<div class="markdown-content prose prose-invert max-w-none" v-html="pageContent"></div>
|
||||
|
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<!-- TOC (Table of Contents) - Outline minimal terminal style -->
|
||||
<aside class="lg:col-span-1">
|
||||
<nav v-if="outline.length > 0" class="sticky top-4">
|
||||
<div class="text-xs text-secondary mb-2 font-mono">// Contents</div>
|
||||
<ul
|
||||
class="space-y-0 text-xs font-mono leading-snug border-l border-base-300 pl-2"
|
||||
>
|
||||
<li v-for="heading in outline" :key="heading.id">
|
||||
<a
|
||||
:href="`#${heading.id}`"
|
||||
@click.prevent="scrollToHeading(heading.id)"
|
||||
class="block py-0.5 hover:text-primary transition truncate"
|
||||
:style="{ paddingLeft: `${(heading.level - 2) * 0.5}rem` }"
|
||||
:class="{
|
||||
'text-primary': activeHeading === heading.id,
|
||||
'text-secondary': activeHeading !== heading.id,
|
||||
}"
|
||||
:title="heading.text"
|
||||
>
|
||||
{{ heading.text }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-circle btn-secondary bottom-4 right-4 fixed font-bold text-3xl" :class="{ 'hidden': !showScrollTopButton }" @click="scrollToTop">↑</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -107,6 +77,12 @@ const currentPage = ref("home");
|
|||
const sidebarItems = ref([]);
|
||||
const outline = ref([]);
|
||||
const activeHeading = ref("");
|
||||
const showScrollTopButton = ref(false);
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
showScrollTopButton.value = false;
|
||||
}
|
||||
|
||||
function onSidebarLinkClick(event, item) {
|
||||
if (item.isExternal) return;
|
||||
|
|
@ -131,7 +107,7 @@ async function loadSidebar() {
|
|||
continue;
|
||||
|
||||
// Heading (###)
|
||||
if (line.startsWith("###")) {
|
||||
if (line.startsWith("###") || line.startsWith("- ###")) {
|
||||
items.push({
|
||||
type: "heading",
|
||||
text: line.replace(/^###\s*/, "").trim(),
|
||||
|
|
@ -227,7 +203,7 @@ async function loadPage(slug) {
|
|||
|
||||
renderer.heading = function (text, level) {
|
||||
const id = text.toLowerCase().replace(/[^\w]+/g, "-");
|
||||
return `<h${level} id="${id}">${text}</h${level}>`;
|
||||
return `<h${level} id="${id}" href="#${id}" >${text}</h${level}>`;
|
||||
};
|
||||
|
||||
renderer.image = function (href, title, text) {
|
||||
|
|
@ -258,6 +234,7 @@ async function loadPage(slug) {
|
|||
|
||||
// Render e outline coerenti, entrambi senza H1 iniziale
|
||||
pageContent.value = marked(contentWithoutMainTitle, { renderer });
|
||||
// TODO: togliere parti di codice per elaborare l'outline
|
||||
extractOutline(contentWithoutMainTitle);
|
||||
} catch (error) {
|
||||
console.error("Error loading page:", error);
|
||||
|
|
@ -275,7 +252,7 @@ function extractOutline(markdown) {
|
|||
const lines = markdown.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(#{2,4})\s+(.+)$/);
|
||||
const match = line.match(/^(#{1,5})\s+(.+)$/);
|
||||
if (match) {
|
||||
const level = match[1].length;
|
||||
const text = match[2];
|
||||
|
|
@ -321,6 +298,11 @@ onMounted(async () => {
|
|||
await loadPage(page);
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
|
||||
window.addEventListener("scroll", () => {
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
showScrollTopButton.value = scrollTop > 300;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue
An icon would look better :)