New Website in VueJS #6
14 changed files with 103 additions and 66 deletions
|
|
@ -20,6 +20,8 @@ The command creates `content/events/my_new_event.md` and fills front matter defa
|
||||||
- `date`: current local timestamp with timezone.
|
- `date`: current local timestamp with timezone.
|
||||||
- `author`: `ADMStaff`.
|
- `author`: `ADMStaff`.
|
||||||
|
|
||||||
|
Any kind of attachment (images, PDFs, etc.) can be placed in `static/img`, and referenced in markdown with `/img/filename.ext`.
|
||||||
|
|
||||||
Optional overrides:
|
Optional overrides:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,5 @@ Utilizziamo tecnologie open source come Linux, Proxmox, Docker, Ansible, Git/For
|
||||||
Cerchiamo studenti curiosi e motivati. Non importa il livello di esperienza: conta la voglia di imparare e mettersi in gioco. Passa a trovarci in laboratorio o scopri i nostri [eventi](/events).
|
Cerchiamo studenti curiosi e motivati. Non importa il livello di esperienza: conta la voglia di imparare e mettersi in gioco. Passa a trovarci in laboratorio o scopri i nostri [eventi](/events).
|
||||||
|
|
||||||
Se sei interessato scrivici su Telegram [t.me/admstaff_Chat](https://t.me/admstaff_Chat)
|
Se sei interessato scrivici su Telegram [t.me/admstaff_Chat](https://t.me/admstaff_Chat)
|
||||||
|
|
||||||
|

|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,5 @@ We use open-source technologies such as Linux, Proxmox, Docker, Ansible, Git/For
|
||||||
We’re looking for curious and motivated students. Your experience level doesn’t matter: what counts is the desire to learn and get involved. Drop by the lab or check out our [events](/events).
|
We’re looking for curious and motivated students. Your experience level doesn’t matter: what counts is the desire to learn and get involved. Drop by the lab or check out our [events](/events).
|
||||||
|
|
||||||
If you’re interested, write to us on Telegram: [t.me/admstaff_Chat](https://t.me/admstaff_Chat)
|
If you’re interested, write to us on Telegram: [t.me/admstaff_Chat](https://t.me/admstaff_Chat)
|
||||||
|
|
||||||
|

|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
<html lang="en" data-theme="admtheme">
|
<html lang="en" data-theme="admtheme">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
href="/assets/ADMstaff_logo-favicon.png"
|
||||||
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ADMStaff - University of Bologna</title>
|
<title>ADMStaff - University of Bologna</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,7 @@ export async function generateAllOGImages() {
|
||||||
// Leggi tutti i file markdown
|
// Leggi tutti i file markdown
|
||||||
const files = getAllMarkdownFiles(contentDir);
|
const files = getAllMarkdownFiles(contentDir);
|
||||||
|
|
||||||
console.log(
|
console.log(`\nGenerating OpenGraph images for ${files.length} files...\n`);
|
||||||
`\n🎨 Generating OpenGraph images for ${files.length} files...\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const content = fs.readFileSync(file, "utf-8");
|
const content = fs.readFileSync(file, "utf-8");
|
||||||
|
|
@ -127,7 +125,7 @@ export async function generateAllOGImages() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n✨ All OpenGraph images generated!\n`);
|
console.log(`\nAll OpenGraph images generated!\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
BIN
src/assets/ADMstaff_logo-favicon.png
(Stored with Git LFS)
Normal file
BIN
src/assets/ADMstaff_logo-favicon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
||||||
|
|
@ -10,13 +10,7 @@ import { useI18n } from "vue-i18n";
|
||||||
// Importa tutti i file markdown dalla directory content (più specifico)
|
// Importa tutti i file markdown dalla directory content (più specifico)
|
||||||
const contentFiles = import.meta.glob(
|
const contentFiles = import.meta.glob(
|
||||||
["/content/**/*.md", "!**/node_modules/**", "!**/public/**"],
|
["/content/**/*.md", "!**/node_modules/**", "!**/public/**"],
|
||||||
|
|
|||||||
{ as: "raw", eager: false },
|
{ query: "?raw", import: "default", eager: false },
|
||||||
);
|
|
||||||
|
|
||||||
// Debug: log dei file trovati
|
|
||||||
console.log(
|
|
||||||
"Content files loaded by import.meta.glob:",
|
|
||||||
Object.keys(contentFiles),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export function useContent(path) {
|
export function useContent(path) {
|
||||||
|
|
@ -73,9 +67,9 @@ export function useContentList(pattern) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
||||||
console.log("=== DEBUG useContentList ===");
|
// console.log("=== DEBUG useContentList ===");
|
||||||
console.log("Pattern received:", pattern);
|
// console.log("Pattern received:", pattern);
|
||||||
console.log("All available content files:", Object.keys(contentFiles));
|
// console.log("All available content files:", Object.keys(contentFiles));
|
||||||
|
|
||||||
let basePath = pattern;
|
let basePath = pattern;
|
||||||
if (basePath.startsWith("/content/")) {
|
if (basePath.startsWith("/content/")) {
|
||||||
|
|
@ -87,13 +81,13 @@ export function useContentList(pattern) {
|
||||||
const localizedDir =
|
const localizedDir =
|
||||||
locale.value === "en" ? `/content/en/${basePath}` : defaultDir;
|
locale.value === "en" ? `/content/en/${basePath}` : defaultDir;
|
||||||
|
|
||||||
console.log("Search path:", localizedDir);
|
// console.log("Search path:", localizedDir);
|
||||||
|
|
||||||
const filesInDir = (dir) =>
|
const filesInDir = (dir) =>
|
||||||
Object.entries(contentFiles).filter(([fullPath]) => {
|
Object.entries(contentFiles).filter(([fullPath]) => {
|
||||||
const pathDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
const pathDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
||||||
const matches = pathDir === dir && !fullPath.endsWith("_index.md");
|
const matches = pathDir === dir && !fullPath.endsWith("_index.md");
|
||||||
if (matches) console.log("✓ Matched file:", fullPath);
|
// if (matches) console.log("✓ Matched file:", fullPath);
|
||||||
return matches;
|
return matches;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -116,18 +110,18 @@ export function useContentList(pattern) {
|
||||||
matchingFiles = [...localizedEntries, ...fallbackOnlyEntries];
|
matchingFiles = [...localizedEntries, ...fallbackOnlyEntries];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Total matching files:", matchingFiles.length);
|
// console.log("Total matching files:", matchingFiles.length);
|
||||||
|
|
||||||
const contents = await Promise.all(
|
const contents = await Promise.all(
|
||||||
matchingFiles.map(async ([fullPath, loader]) => {
|
matchingFiles.map(async ([fullPath, loader]) => {
|
||||||
try {
|
try {
|
||||||
console.log("Loading file:", fullPath);
|
// console.log("Loading file:", fullPath);
|
||||||
const raw = await loader();
|
const raw = await loader();
|
||||||
const parsed = parseMarkdown(raw);
|
const parsed = parseMarkdown(raw);
|
||||||
console.log("Parsed:", {
|
// console.log("Parsed:", {
|
||||||
title: parsed.frontmatter.title,
|
// title: parsed.frontmatter.title,
|
||||||
date: parsed.frontmatter.date,
|
// date: parsed.frontmatter.date,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Estrai slug dal path
|
// Estrai slug dal path
|
||||||
const pathParts = fullPath.split("/");
|
const pathParts = fullPath.split("/");
|
||||||
|
|
@ -147,14 +141,14 @@ export function useContentList(pattern) {
|
||||||
|
|
||||||
// Filtra i null e ordina per data
|
// Filtra i null e ordina per data
|
||||||
const validContents = contents.filter((c) => c !== null);
|
const validContents = contents.filter((c) => c !== null);
|
||||||
console.log(
|
// console.log(
|
||||||
"Valid contents:",
|
// "Valid contents:",
|
||||||
validContents.length,
|
// validContents.length,
|
||||||
validContents.map((c) => ({
|
// validContents.map((c) => ({
|
||||||
title: c.frontmatter.title,
|
// title: c.frontmatter.title,
|
||||||
date: c.frontmatter.date,
|
// date: c.frontmatter.date,
|
||||||
})),
|
// })),
|
||||||
);
|
// );
|
||||||
|
|
||||||
items.value = validContents.sort((a, b) => {
|
items.value = validContents.sort((a, b) => {
|
||||||
// Ordina dal più recente al meno recente
|
// Ordina dal più recente al meno recente
|
||||||
|
|
@ -169,7 +163,7 @@ export function useContentList(pattern) {
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Final items:", items.value.length);
|
// console.log("Final items:", items.value.length);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error loading content list:", e);
|
console.error("Error loading content list:", e);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ function parseYAMLFrontmatter(yamlContent) {
|
||||||
else if (/^-?\d+\.?\d*$/.test(value)) value = Number(value);
|
else if (/^-?\d+\.?\d*$/.test(value)) value = Number(value);
|
||||||
|
|
||||||
if (key === "cover") {
|
if (key === "cover") {
|
||||||
console.log("Cover value parsed:", value, "type:", typeof value);
|
// console.log("Cover value parsed:", value, "type:", typeof value);
|
||||||
}
|
}
|
||||||
|
|
||||||
frontmatter[key] = value;
|
frontmatter[key] = value;
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,11 @@
|
||||||
<!-- About Section con cursore -->
|
<!-- About Section con cursore -->
|
||||||
<div class="about relative flex flex-row items-center mx-[5%] my-12 gap-5">
|
<div class="about relative flex flex-row items-center mx-[5%] my-12 gap-5">
|
||||||
<img
|
<img
|
||||||
src="/static/img/ADMstaff_logo-modern-trasp.png"
|
src="@/assets/ADMstaff_logo-modern-trasp.png"
|
||||||
alt="ADMStaff Logo"
|
alt="ADMStaff Logo"
|
||||||
class=" w-1/3" />
|
class="w-1/3"
|
||||||
<h4
|
/>
|
||||||
id="about-text"
|
<h4 id="about-text" class="leading-relaxed md:text-xl">
|
||||||
class="leading-relaxed md:text-xl"
|
|
||||||
>
|
|
||||||
ADMstaff nasce come gruppo che fornisce servizi da studenti per
|
ADMstaff nasce come gruppo che fornisce servizi da studenti per
|
||||||
studenti.<br /><br />
|
studenti.<br /><br />
|
||||||
Il nostro obiettivo è sperimentare e fare pratica su ciò che ci
|
Il nostro obiettivo è sperimentare e fare pratica su ciò che ci
|
||||||
|
|
@ -142,12 +140,4 @@ function truncate(html, length) {
|
||||||
// Restituisci HTML semplice
|
// Restituisci HTML semplice
|
||||||
return `<p>${finalText}...</p>`;
|
return `<p>${finalText}...</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Posiziona il cursore dopo il caricamento
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
updateCursorPosition();
|
|
||||||
window.addEventListener("resize", updateCursorPosition);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -9,29 +9,56 @@
|
||||||
<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 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 }}
|
{{ item.text }}
|
||||||
</h3>
|
</h3>
|
||||||
<!-- Link a pagina -->
|
<!-- Link a pagina -->
|
||||||
<a v-else-if="item.type === 'link'" :href="item.isExternal ? item.url : '#'"
|
<a
|
||||||
:target="item.isExternal ? '_blank' : null" :rel="item.isExternal ? 'noopener noreferrer' : null"
|
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)"
|
@click="onSidebarLinkClick($event, item)"
|
||||||
class="block py-1 px-2 rounded-lg hover:bg-base-300/20 transition truncate" :class="{
|
class="block py-1 px-2 rounded-lg hover:bg-base-300/20 transition truncate"
|
||||||
|
:class="{
|
||||||
'bg-base-300/20 text-primary font-bold':
|
'bg-base-300/20 text-primary font-bold':
|
||||||
!item.isExternal && currentPage === item.slug,
|
!item.isExternal && currentPage === item.slug,
|
||||||
}" :title="item.text">
|
}"
|
||||||
{{ item.text }}<span v-if="item.isExternal" class="ml-1 text-base-content/60">↗</span>
|
:title="item.text"
|
||||||
|
>
|
||||||
|
{{ item.text
|
||||||
|
}}<span
|
||||||
|
v-if="item.isExternal"
|
||||||
|
class="ml-1 text-base-content/60"
|
||||||
|
>↗</span
|
||||||
|
>
|
||||||
</a>
|
</a>
|
||||||
<!-- TOC (Table of Contents) - Outline minimal terminal style -->
|
<!-- TOC (Table of Contents) - Outline minimal terminal style -->
|
||||||
<nav v-if="currentPage === item.slug && outline.length > 0" class="sticky top-4">
|
<nav
|
||||||
<ul class="space-y-0 text-xs font-mono leading-snug border-l border-base-300 pl-3">
|
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">
|
<li v-for="heading in outline" :key="heading.id">
|
||||||
<a :href="`#${heading.id}`" @click.prevent="scrollToHeading(heading.id)"
|
<a
|
||||||
|
:href="`#${heading.id}`"
|
||||||
|
@click.prevent="scrollToHeading(heading.id)"
|
||||||
class="block py-0.5 hover:text-base-content rounded-lg transition truncate"
|
class="block py-0.5 hover:text-base-content rounded-lg transition truncate"
|
||||||
:style="{ paddingLeft: `${(heading.level - 1) * 0.5}rem` }" :class="{
|
:style="{
|
||||||
'text-primary bg-base-300/20': activeHeading === heading.id,
|
paddingLeft: `${(heading.level - 1) * 0.5}rem`,
|
||||||
|
}"
|
||||||
|
:class="{
|
||||||
|
'text-primary bg-base-300/20':
|
||||||
|
samu
commented
An icon would look better :) An icon would look better :)
|
|||||||
|
activeHeading === heading.id,
|
||||||
'text-base-content/60': activeHeading !== heading.id,
|
'text-base-content/60': activeHeading !== heading.id,
|
||||||
}" :title="heading.text">
|
}"
|
||||||
|
:title="heading.text"
|
||||||
|
>
|
||||||
{{ heading.text }}
|
{{ heading.text }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -39,26 +66,39 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Contenuto principale -->
|
<!-- Contenuto principale -->
|
||||||
<main class="lg:col-span-4">
|
<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">Loading<span class="blinking-cursor"></span></span>
|
<span class="text-primary"
|
||||||
|
>Loading<span class="blinking-cursor"></span
|
||||||
|
></span>
|
||||||
</div>
|
</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">
|
<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 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>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</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>
|
<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>
|
||||||
|
|
||||||
|
|
@ -302,7 +342,7 @@ onMounted(async () => {
|
||||||
window.addEventListener("scroll", () => {
|
window.addEventListener("scroll", () => {
|
||||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||||
showScrollTopButton.value = scrollTop > 300;
|
showScrollTopButton.value = scrollTop > 300;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
BIN
static/img/team2024.jpg
(Stored with Git LFS)
Normal file
BIN
static/img/team2024.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
Reference in a new issue
We don't need the last two matching strings, only the first one