diff --git a/template/app/components/PageTree.vue b/template/app/components/PageTree.vue index 33ae740..9d82a2c 100644 --- a/template/app/components/PageTree.vue +++ b/template/app/components/PageTree.vue @@ -7,7 +7,7 @@ const { data: pages } = await useAsyncData('nav', () => diff --git a/template/app/pages/[...slug].vue b/template/app/pages/[...slug].vue index 99d36e2..18016f4 100644 --- a/template/app/pages/[...slug].vue +++ b/template/app/pages/[...slug].vue @@ -48,6 +48,7 @@ if (!page.value) { width: 100%; max-width: 800px; padding: 0 1rem; + margin-bottom: 300px; } .page-tree-sidebar { diff --git a/template/content/index.md b/template/content/index.md index 08a564b..6b3ccb0 100644 --- a/template/content/index.md +++ b/template/content/index.md @@ -6,4 +6,6 @@ visible: true Hola que tal! Hauria de funcionar -Test +Test + +Razas diff --git a/template/convert.js b/template/convert.js index 64c0985..1462cff 100644 --- a/template/convert.js +++ b/template/convert.js @@ -19,7 +19,9 @@ * * VISIBILITY GATE: * Only notes whose frontmatter contains `visible: true` are converted. - * Any note missing the field, or with `visible: false`, is silently skipped. + * Any note missing the field, or with `visible: false`, is skipped. + * Wiki links that point to a hidden (or missing) note are rendered as dead + * links: instead of a normal link. * * Usage: * node obsidian-to-nuxt.js --vault ./my-vault --out ./content --base-path /notes @@ -108,6 +110,11 @@ function collectMarkdownFiles(dir) { return results; } +/** + * Build a lookup map of stem/title/slug → URL for every **visible** note. + * Hidden notes (visible !== true) are intentionally excluded so that wiki + * links pointing at them resolve to nothing and are rendered as dead links. + */ function buildNoteIndex(files) { const candidates = new Map(); @@ -118,27 +125,31 @@ function buildNoteIndex(files) { } for (const file of files) { - const stem = path.basename(file, ".md"); - const rel = path.relative(VAULT_DIR, file).replace(/\.md$/, ""); - const url = BASE_PATH + "/" + rel.split(path.sep).map(slugify).join("/"); - const depth = rel.split(path.sep).length; const content = fs.readFileSync(file, "utf8"); + // Skip hidden notes — links to them must become dead links. + const { frontmatter, body } = parseFrontmatter(content); + if (!isVisible(frontmatter)) continue; + + const stem = path.basename(file, ".md"); + const rel = path.relative(VAULT_DIR, file).replace(/\.md$/, ""); + const url = BASE_PATH + "/" + rel.split(path.sep).map(slugify).join("/"); + const depth = rel.split(path.sep).length; + // Key 1: exact lowercase filename stem e.g. "note2" addCandidate(stem.toLowerCase(), url, depth); // Key 2: slugified filename stem e.g. "note-two" from "Note Two.md" addCandidate(slugify(stem), url, depth); - // Key 3: frontmatter title - const fmMatch = content.match(/^---[\s\S]*?^title:\s*(.+)/m); - if (fmMatch) { - const title = fmMatch[1].trim().replace(/^["']|["']$/g, ""); + // Key 3: frontmatter title (already parsed above — no second regex needed) + if (frontmatter.title) { + const title = String(frontmatter.title); addCandidate(title.toLowerCase(), url, depth); addCandidate(slugify(title), url, depth); } // Key 4: first H1 heading - const h1Match = content.match(/^#\s+(.+)/m); + const h1Match = body.match(/^#\s+(.+)/m); if (h1Match) { const h1 = h1Match[1].trim(); addCandidate(h1.toLowerCase(), url, depth); @@ -151,6 +162,24 @@ function buildNoteIndex(files) { return result; } +/** + * Collect the lowercase stems (and slugified stems) of every note that exists + * in the vault but is NOT visible. Used to distinguish "hidden" dead links + * from "truly missing" dead links when rendering wiki links. + */ +function buildHiddenStems(files) { + const hidden = new Set(); + for (const file of files) { + const content = fs.readFileSync(file, "utf8"); + const { frontmatter } = parseFrontmatter(content); + if (isVisible(frontmatter)) continue; + const stem = path.basename(file, ".md"); + hidden.add(stem.toLowerCase()); + hidden.add(slugify(stem)); + } + return hidden; +} + // ─── Frontmatter ───────────────────────────────────────────────────────────── const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/; @@ -299,7 +328,12 @@ function convertEmbeds(body, noteIndex) { // ─── Wiki links [[...]] ────────────────────────────────────────────────────── -function convertWikiLinks(body, noteIndex) { +/** + * @param {string} body + * @param {Map} noteIndex - visible notes only (stem/title → url) + * @param {Set} hiddenStems - stems of notes that exist but are not visible + */ +function convertWikiLinks(body, noteIndex, hiddenStems) { return body.replace(/\[\[([^\]]+?)\]\]/g, function(_, ref) { const pipeIdx = ref.indexOf("|"); let target, alias; @@ -334,7 +368,12 @@ function convertWikiLinks(body, noteIndex) { const label = alias || (anchor ? stem + " \u203a " + anchor : stem); if (dead) { - return '' + label + ''; + // Distinguish between a note that is hidden vs one that doesn't exist at all. + const isHidden = hiddenStems.has(stem.toLowerCase()) || hiddenStems.has(slugify(stem)); + const extraAttr = isHidden + ? ' data-hidden="' + stem + '"' + : ' data-missing="' + stem + '"'; + return '' + label + ''; } return "[" + label + "](" + url + ")"; }); @@ -342,7 +381,7 @@ function convertWikiLinks(body, noteIndex) { // ─── Convert one file ──────────────────────────────────────────────────────── -function convertFile(content, noteIndex, filename) { +function convertFile(content, noteIndex, hiddenStems, filename) { let { frontmatter, body } = parseFrontmatter(content); const inlineTags = extractInlineTags(body); @@ -358,16 +397,14 @@ function convertFile(content, noteIndex, filename) { body = convertCallouts(body); body = convertBlockIds(body); body = convertEmbeds(body, noteIndex); - body = convertWikiLinks(body, noteIndex); + body = convertWikiLinks(body, noteIndex, hiddenStems); // Prepend a title if the body doesn't already start with an H1 heading const trimmed = body.trimStart(); const hasTitle = /^#[^#]/.test(trimmed); if (!hasTitle) { const title = frontmatter.title || path.basename(filename, ".md"); - if (title != "index") { - body = "# " + title + "\n\n" + trimmed; - } + body = "# " + title + "\n\n" + trimmed; } const fmString = Object.keys(frontmatter).length @@ -385,8 +422,9 @@ function main() { process.exit(1); } - const files = collectMarkdownFiles(VAULT_DIR); - const noteIndex = buildNoteIndex(files); + const files = collectMarkdownFiles(VAULT_DIR); + const noteIndex = buildNoteIndex(files); + const hiddenStems = buildHiddenStems(files); console.log("Found " + files.length + " markdown file(s) in " + VAULT_DIR); @@ -416,7 +454,7 @@ function main() { let result; try { - result = convertFile(content, noteIndex, path.basename(file)); + result = convertFile(content, noteIndex, hiddenStems, path.basename(file)); } catch (err) { console.warn(" \u26A0 Skipping " + rel + ": " + err.message); skipCount++; diff --git a/template/public/favicon.ico b/template/public/favicon.ico index 18993ad..f39f2db 100644 Binary files a/template/public/favicon.ico and b/template/public/favicon.ico differ