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,55 @@
|
||||
'use strict';
|
||||
// Checklist items (`- [ ] text`) across the notes, and ticking one from outside its page.
|
||||
// A task is identified by its page and line number in the page's body (what vault.read returns), plus its text,
|
||||
// so a file that changed since the list was made is never edited blindly.
|
||||
|
||||
const TASK = /^(\s*(?:>\s*)*[-*+]\s+)\[([ xX])\](\s+)(.*)$/;
|
||||
const DUE = /(?:📅|\bdue:|\[due::)\s*(\d{4}-\d{2}-\d{2})/;
|
||||
const TAG = /(?:^|\s)#([\p{L}\p{N}_/-]*[\p{L}_/-][\p{L}\p{N}_/-]*)/gu;
|
||||
|
||||
// Line indexes inside ``` / ~~~ fences are not tasks.
|
||||
function* lines(body) {
|
||||
let fence = null;
|
||||
const all = String(body || '').split('\n');
|
||||
for (let i = 0; i < all.length; i++) {
|
||||
const f = all[i].match(/^\s*(```|~~~)/);
|
||||
if (f) { fence = fence === f[1] ? null : fence || f[1]; continue; }
|
||||
if (!fence) yield [i, all[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// [{ id, title, icon, line, done, text, due, tags, depth }] for every task on every page (boards and canvases excluded:
|
||||
// a board's cards are its own thing).
|
||||
function findTasks(pages) {
|
||||
const out = [];
|
||||
for (const p of pages) {
|
||||
if (!p.exists || p.kind === 'canvas' || /\.canvas$/i.test(p.id)) continue;
|
||||
if (p.props && p.props['kanban-plugin']) continue;
|
||||
for (const [line, text] of lines(p.body)) {
|
||||
const m = text.match(TASK);
|
||||
if (!m) continue;
|
||||
const body = m[4].trim();
|
||||
if (!body) continue;
|
||||
const tags = new Set((p.tags || []).map(String));
|
||||
for (const t of body.matchAll(TAG)) tags.add(t[1]);
|
||||
const due = body.match(DUE);
|
||||
out.push({
|
||||
id: p.id, title: p.title, icon: p.icon || null, line,
|
||||
done: m[2] !== ' ', text: body, due: due ? due[1] : '', tags: [...tags],
|
||||
depth: Math.floor((m[1].match(/^\s*/)[0].replace(/\t/g, ' ').length) / 2)
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// The body with the task at `line` ticked or unticked; null if the line is no longer that task.
|
||||
function setTask(body, line, text, done) {
|
||||
const all = String(body || '').split('\n');
|
||||
const m = all[line] && all[line].match(TASK);
|
||||
if (!m || m[4].trim() !== String(text).trim()) return null;
|
||||
all[line] = m[1] + (done ? '[x]' : '[ ]') + m[3] + m[4];
|
||||
return all.join('\n');
|
||||
}
|
||||
|
||||
module.exports = { findTasks, setTask };
|
||||
Reference in new issue
Block a user