djksdkj
This commit is contained in:
@@ -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: <a class="dead-link" data-missing="…"> 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 '<a href="' + url + '" class="dead-link" data-missing="' + stem + '">' + label + '</a>';
|
||||
// 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 '<a href="' + url + '" class="dead-link"' + extraAttr + '>' + label + '</a>';
|
||||
}
|
||||
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++;
|
||||
|
||||
Reference in New Issue
Block a user