386 lines
9.3 KiB
Lua
386 lines
9.3 KiB
Lua
-- === Zettelkasten helpers (custom.zk) ===
|
|
-- Root + folder layout (with spaces, as requested)
|
|
local zk_root = vim.fn.expand "~/Documents/Dane's Vault/Zettelkasten"
|
|
local fleeting_dir = zk_root .. "/Fleeting Notes"
|
|
local hub_dir = zk_root .. "/Hub Notes"
|
|
local permanent_dir = zk_root .. "/Permanent Notes"
|
|
local daily_dir = fleeting_dir .. "/Daily" -- assumption: dailies live under Fleeting Notes/Daily
|
|
|
|
-- ---------- utilities ----------
|
|
local function slugify(s)
|
|
s = (s or ""):lower()
|
|
s = s
|
|
:gsub("[^%w%s%-]", "") -- keep letters, digits, space, dash
|
|
:gsub("%s+", "-") -- spaces -> dash
|
|
:gsub("%-+", "-") -- collapse dashes
|
|
:gsub("^%-", "")
|
|
:gsub("%-$", "")
|
|
return s
|
|
end
|
|
|
|
local function iso_utc()
|
|
return os.date "!%Y-%m-%dT%H:%M:%SZ"
|
|
end
|
|
local function ensure_dir(path)
|
|
vim.fn.mkdir(path, "p")
|
|
end
|
|
local function starts_with(s, prefix)
|
|
return s:sub(1, #prefix) == prefix
|
|
end
|
|
|
|
-- ---------- front-matter: update `modified:` on save ----------
|
|
local function update_modified_in_buffer()
|
|
local api, buf = vim.api, vim.api.nvim_get_current_buf()
|
|
local path = vim.fn.expand "%:p"
|
|
|
|
-- Only touch Markdown in your ZK root
|
|
if vim.bo[buf].filetype ~= "markdown" and not path:lower():match "%.md$" then
|
|
return
|
|
end
|
|
if not starts_with(path, zk_root) then
|
|
return
|
|
end
|
|
|
|
local lines = api.nvim_buf_get_lines(buf, 0, -1, false)
|
|
if #lines < 1 or lines[1] ~= "---" then
|
|
return
|
|
end
|
|
|
|
-- find closing '---'
|
|
local fm_end
|
|
for i = 2, math.min(#lines, 200) do
|
|
if lines[i] == "---" then
|
|
fm_end = i
|
|
break
|
|
end
|
|
end
|
|
if not fm_end then
|
|
return
|
|
end
|
|
|
|
-- find or insert modified:
|
|
local modified_idx
|
|
for i = 2, fm_end - 1 do
|
|
if lines[i]:match "^modified:%s" then
|
|
modified_idx = i
|
|
break
|
|
end
|
|
end
|
|
|
|
local now = "modified: " .. iso_utc()
|
|
if modified_idx then
|
|
if lines[modified_idx] ~= now then
|
|
lines[modified_idx] = now
|
|
api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
end
|
|
else
|
|
table.insert(lines, fm_end, now)
|
|
api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_create_autocmd("BufWritePre", {
|
|
pattern = "*.md",
|
|
callback = update_modified_in_buffer,
|
|
desc = "ZK: auto-update YAML modified: on save",
|
|
})
|
|
|
|
-- ---------- create file helper ----------
|
|
local function write_if_missing(path, lines)
|
|
if vim.fn.filereadable(path) == 0 then
|
|
local fh = io.open(path, "w")
|
|
if not fh then
|
|
vim.notify("Failed to create file at " .. path, vim.log.levels.ERROR)
|
|
return false
|
|
end
|
|
fh:write(table.concat(lines, "\n"))
|
|
fh:close()
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- ---------- :ZkNewPermanent ----------
|
|
local function new_permanent_note(opts)
|
|
local title = table.concat(opts.fargs or {}, " ")
|
|
if title == "" then
|
|
title = vim.fn.input "Permanent note title: "
|
|
end
|
|
if not title or title == "" then
|
|
vim.notify("Aborted: empty title.", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
local id = os.date "%Y%m%d%H%M%S"
|
|
local slug = slugify(title)
|
|
local filename = string.format("%s-%s.md", id, slug)
|
|
|
|
ensure_dir(permanent_dir)
|
|
local path = permanent_dir .. "/" .. filename
|
|
|
|
local created = iso_utc()
|
|
local ok = write_if_missing(path, {
|
|
"---",
|
|
"id: " .. id,
|
|
"title: " .. title,
|
|
"type: permanent",
|
|
"created: " .. created,
|
|
"modified: " .. created,
|
|
"tags: []",
|
|
"---",
|
|
"",
|
|
"# " .. title,
|
|
"",
|
|
})
|
|
if ok then
|
|
vim.cmd.edit(path)
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_create_user_command(
|
|
"ZkNewPermanent",
|
|
new_permanent_note,
|
|
{ nargs = "*", desc = "Create a new permanent ZK note and open it" }
|
|
)
|
|
|
|
-- ---------- :ZkNewDaily ----------
|
|
-- Opens today's daily note if it exists; otherwise creates it (under Fleeting Notes/Daily).
|
|
local function new_daily_note()
|
|
local today = os.date "%Y-%m-%d"
|
|
local title = "Daily — " .. today
|
|
local fname = today .. ".md"
|
|
|
|
ensure_dir(daily_dir)
|
|
local path = daily_dir .. "/" .. fname
|
|
|
|
local created = iso_utc()
|
|
local ok = write_if_missing(path, {
|
|
"---",
|
|
"id: " .. today, -- simple id for dailies
|
|
"title: " .. title,
|
|
"type: daily",
|
|
"created: " .. created,
|
|
"modified: " .. created,
|
|
"tags: [daily]",
|
|
"---",
|
|
"",
|
|
"# " .. title,
|
|
"",
|
|
"## Quick capture",
|
|
"",
|
|
"- ",
|
|
"",
|
|
"## Tasks",
|
|
"",
|
|
"- [ ] ",
|
|
"",
|
|
"## Notes",
|
|
"",
|
|
})
|
|
if ok then
|
|
vim.cmd.edit(path)
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_create_user_command("ZkNewDaily", new_daily_note, { desc = "Open or create today's ZK daily note" })
|
|
|
|
-- ---------- :ZkNewHub ----------
|
|
-- Hub/Structure note (MOC): great as an index/overview for a topic or project.
|
|
-- File naming: HUB-YYYYMMDDHHMMSS-<slug>.md (easy to spot + sortable)
|
|
local function new_hub_note(opts)
|
|
local title = table.concat(opts.fargs or {}, " ")
|
|
if title == "" then
|
|
title = vim.fn.input "Hub note title: "
|
|
end
|
|
if not title or title == "" then
|
|
vim.notify("Aborted: empty title.", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
local id = "HUB-" .. os.date "%Y%m%d%H%M%S"
|
|
local slug = slugify(title)
|
|
local filename = string.format("%s-%s.md", id, slug)
|
|
|
|
ensure_dir(hub_dir)
|
|
local path = hub_dir .. "/" .. filename
|
|
|
|
local created = iso_utc()
|
|
-- A practical hub template: overview, TOC, backlinks/related, and open questions.
|
|
local ok = write_if_missing(path, {
|
|
"---",
|
|
"id: " .. id,
|
|
"title: " .. title,
|
|
"type: hub",
|
|
"created: " .. created,
|
|
"modified: " .. created,
|
|
"tags: [hub]",
|
|
"aliases: []",
|
|
"---",
|
|
"",
|
|
"# " .. title,
|
|
"",
|
|
"> Brief purpose/definition of this hub.",
|
|
"",
|
|
"## Overview",
|
|
"",
|
|
"- ",
|
|
"",
|
|
"## Contents (map of notes)",
|
|
"",
|
|
"- [[ ]] ",
|
|
"- [[ ]] ",
|
|
"- [[ ]] ",
|
|
"",
|
|
"## Permanent seeds (key ideas)",
|
|
"",
|
|
"- [[ ]] ",
|
|
"",
|
|
"## Related hubs",
|
|
"",
|
|
"- [[ ]] ",
|
|
"",
|
|
"## Sources / Literature",
|
|
"",
|
|
"- ",
|
|
"",
|
|
"## Open questions / Next actions",
|
|
"",
|
|
"- [ ] ",
|
|
"",
|
|
})
|
|
if ok then
|
|
vim.cmd.edit(path)
|
|
end
|
|
end
|
|
-- ---------- :ZkNewLit ----------
|
|
-- Lightweight Literature (three-pass) note creator.
|
|
-- Files live under: Permanent Notes/Literature
|
|
-- Naming: LIT-YYYYMMDDHHMMSS-<slug>.md
|
|
local literature_dir = permanent_dir .. "/Literature Notes"
|
|
|
|
local function new_literature_note(opts)
|
|
local title = table.concat(opts.fargs or {}, " ")
|
|
if title == "" then
|
|
title = vim.fn.input "Literature note title (paper title or handle): "
|
|
end
|
|
if not title or title == "" then
|
|
vim.notify("Aborted: empty title.", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
local id = "LIT-" .. os.date "%Y%m%d%H%M%S"
|
|
local slug = slugify(title)
|
|
local filename = string.format("%s-%s.md", id, slug)
|
|
|
|
ensure_dir(literature_dir)
|
|
local path = literature_dir .. "/" .. filename
|
|
|
|
local created = iso_utc()
|
|
|
|
local ok = write_if_missing(path, {
|
|
"---",
|
|
"id: " .. id,
|
|
"title: " .. title,
|
|
"type: literature",
|
|
"created: " .. created,
|
|
"modified: " .. created,
|
|
"citekey: ", -- just paste your Zotero citekey here
|
|
"---",
|
|
"",
|
|
"# " .. title,
|
|
"",
|
|
"## First Pass",
|
|
"**Category:** ",
|
|
"",
|
|
"**Context:** ",
|
|
"",
|
|
"**Correctness:** ",
|
|
"",
|
|
"**Contributions:** ",
|
|
"",
|
|
"**Clarity:** ",
|
|
"",
|
|
"## Second Pass",
|
|
"**What is the main thrust?**",
|
|
"",
|
|
"**What is the supporting evidence?**",
|
|
"",
|
|
"**What are the key findings?**",
|
|
"",
|
|
"## Third Pass",
|
|
"**Recreation Notes:**",
|
|
"",
|
|
"**Hidden Findings:**",
|
|
"",
|
|
"**Weak Points? Strong Points?**",
|
|
"",
|
|
})
|
|
if ok then
|
|
vim.cmd.edit(path)
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_create_user_command(
|
|
"ZkNewLit",
|
|
new_literature_note,
|
|
{ nargs = "*", desc = "Create a new literature ZK note (three-pass template) and open it" }
|
|
)
|
|
|
|
-- Keymap (optional): <leader>zl
|
|
|
|
vim.api.nvim_create_user_command(
|
|
"ZkNewHub",
|
|
new_hub_note,
|
|
{ nargs = "*", desc = "Create a new hub (structure) note and open it" }
|
|
)
|
|
local thesis_dir = permanent_dir .. "/Thesis"
|
|
|
|
local function new_thesis_note(opts)
|
|
local title = table.concat(opts.fargs or {}, " ")
|
|
if title == "" then
|
|
title = vim.fn.input "Thesis note title: "
|
|
end
|
|
if not title or title == "" then
|
|
vim.notify("Aborted: empty title.", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
local id = "DR-" .. os.date "%Y%m%d%H%M%S"
|
|
local slug = slugify(title)
|
|
local filename = string.format("%s-%s.md", id, slug)
|
|
|
|
ensure_dir(thesis_dir)
|
|
local path = thesis_dir .. "/" .. filename
|
|
|
|
local created = iso_utc()
|
|
|
|
local ok = write_if_missing(path, {
|
|
"---",
|
|
"id: " .. id,
|
|
"title: " .. title,
|
|
"type: thesis",
|
|
"created: " .. created,
|
|
"modified: " .. created,
|
|
"tags: []",
|
|
"---",
|
|
"",
|
|
})
|
|
if ok then
|
|
vim.cmd.edit(path)
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_create_user_command(
|
|
"ZkNewThesis",
|
|
new_thesis_note,
|
|
{ nargs = "*", desc = "Create a new thesis ZK note and open it" }
|
|
)
|
|
-- ---------- keymaps ----------
|
|
-- <leader>zn : new permanent
|
|
-- <leader>zd : today's daily
|
|
-- <leader>zh : new hub
|
|
vim.keymap.set("n", "<leader>zn", ":ZkNewPermanent ", { desc = "ZK: New permanent note" })
|
|
vim.keymap.set("n", "<leader>zd", ":ZkNewDaily<CR>", { desc = "ZK: Open/create today's daily" })
|
|
vim.keymap.set("n", "<leader>zh", ":ZkNewHub ", { desc = "ZK: New hub (structure) note" })
|
|
vim.keymap.set("n", "<leader>zl", ":ZkNewLit ", { desc = "ZK: New literature note" })
|
|
vim.keymap.set("n", "<leader>zt", ":ZkNewThesis ", { desc = "ZK: New thesis note" })
|