-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com>
//
// SPDX-License-Identifier: Apache-2.0
import { extname } from "../deps/deno.land/std/path/mod.ts";
import * as yamlFrontmatter from "../deps/deno.land/std/front_matter/yaml.ts";
import type {
DirectoryReader,
FileReader,
} from "../filesystem_reader/interface.ts";
import type { DocumentMetadata, MetadataParser, Skip } from "./interface.ts";
function escapeNodeName(nodeName: string): string {
return encodeURIComponent(nodeName.toLowerCase());
}
export interface VaultParserOptions {
/**
* Whether to read YAML frontmatter of notes.
* When enabled,
*
* - Use `name` property for document name if defined.
* - Use `title` property for document title if defined.
*
* This flag is off by-default for performance reasons.
*/
readFrontMatter?: boolean;
}
/**
* A parser for Obsidian Vault.
*
* By default, this parser uses file and directory name as document title
* and lowercased escaped one as document name.
*/
export class VaultParser implements MetadataParser {
#readFrontMatter: boolean;
constructor({ readFrontMatter = false }: VaultParserOptions = {}) {
this.#readFrontMatter = readFrontMatter;
}
async parse(
node: FileReader | DirectoryReader,
): Promise<DocumentMetadata | Skip> {
if (node.type === "directory") {
return {
name: escapeNodeName(node.name),
title: node.name,
};
}
const ext = extname(node.name);
const basename = ext ? node.name.slice(0, -ext.length) : node.name;
switch (ext) {
case ".md": {
const fromFileName: DocumentMetadata = {
name: escapeNodeName(basename),
title: basename,
};
if (this.#readFrontMatter) {
const parsed = await this.#parseFrontMatter(node);
return {
name: parsed.name || fromFileName.name,
title: parsed.title || fromFileName.title,
};
}
return fromFileName;
}
case ".canvas": {
return {
name: escapeNodeName(basename),
title: basename,
};
}
// Not an Obsidian document.
default: {
return {
skip: true,
};
}
}
}
async #parseFrontMatter(
file: FileReader,
): Promise<Partial<DocumentMetadata>> {
const markdown = new TextDecoder().decode(await file.read());
// Obsidian currently supports YAML frontmatter only.
const frontmatter = yamlFrontmatter.extract(markdown);
const name = ("name" in frontmatter.attrs &&
typeof frontmatter.attrs.name === "string" && frontmatter.attrs.name) ||
undefined;
const title = ("title" in frontmatter.attrs &&
typeof frontmatter.attrs.title === "string" &&
frontmatter.attrs.title) || undefined;
return { name, title };
}
}