feat(electron): main process, vault, search index, links, import, capture
Vault on plain .md files with FTS5 index, link rewriting, canvas files, history/trash/backup, link previews, image fetch, tasks and unlinked mentions, Notion/Obsidian importer, quick-capture window and shortcut. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0b5f8bfa8b
commit
313c41f867
16 files changed
+2706
-15
No files matched your search
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
// Minimal YAML front matter: flat `key: value`, inline `[a, b]` lists and `- item` lists.
|
||||
// Unknown keys are kept and written back, so notes from Obsidian survive a round trip.
|
||||
|
||||
const FM = /^?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
||||
|
||||
function unquote(v) {
|
||||
v = v.trim();
|
||||
if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) {
|
||||
try { return JSON.parse(v); } catch { return v.slice(1, -1); }
|
||||
}
|
||||
if (v.length >= 2 && v.startsWith("'") && v.endsWith("'")) return v.slice(1, -1).replace(/''/g, "'");
|
||||
return v;
|
||||
}
|
||||
|
||||
function parseFrontMatter(raw) {
|
||||
const m = raw.match(FM);
|
||||
if (!m) return { meta: {}, body: raw };
|
||||
const meta = {};
|
||||
let last = null;
|
||||
for (const line of m[1].split(/\r?\n/)) {
|
||||
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
||||
if (kv) {
|
||||
last = kv[1];
|
||||
const v = kv[2].trim();
|
||||
if (v.startsWith('[') && v.endsWith(']')) {
|
||||
meta[last] = v.slice(1, -1).split(',').map(unquote).filter(Boolean);
|
||||
} else {
|
||||
meta[last] = v === '' ? [] : unquote(v);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const item = line.match(/^\s*-\s+(.*)$/);
|
||||
if (item && last) {
|
||||
if (!Array.isArray(meta[last])) meta[last] = [];
|
||||
meta[last].push(unquote(item[1]));
|
||||
}
|
||||
}
|
||||
// An empty `key:` with no list items below it is just an empty string.
|
||||
for (const k of Object.keys(meta)) if (Array.isArray(meta[k]) && !meta[k].length && k !== 'tags') meta[k] = '';
|
||||
return { meta, body: raw.slice(m[0].length).replace(/^\r?\n/, '') };
|
||||
}
|
||||
|
||||
function quote(s) {
|
||||
s = String(s);
|
||||
return /^[\p{L}\p{N}_ .\/@#+-]+$/u.test(s) && s.trim() === s && !/^[-#@]/.test(s) ? s : JSON.stringify(s);
|
||||
}
|
||||
|
||||
const ORDER = ['icon', 'cover', 'tags', 'created'];
|
||||
|
||||
function serializeFrontMatter(meta = {}) {
|
||||
const keys = Object.keys(meta).filter((k) => {
|
||||
const v = meta[k];
|
||||
return v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && !v.length);
|
||||
});
|
||||
if (!keys.length) return '';
|
||||
keys.sort((a, b) => {
|
||||
const ia = ORDER.indexOf(a), ib = ORDER.indexOf(b);
|
||||
return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib);
|
||||
});
|
||||
const lines = keys.map((k) => {
|
||||
const v = meta[k];
|
||||
return Array.isArray(v) ? `${k}: [${v.map(quote).join(', ')}]` : `${k}: ${quote(v)}`;
|
||||
});
|
||||
return `---\n${lines.join('\n')}\n---\n\n`;
|
||||
}
|
||||
|
||||
module.exports = { parseFrontMatter, serializeFrontMatter };
|
||||
Reference in new issue
Block a user