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