feat(nvim): add automatic tagging for draft content
This commit is contained in:
parent
dba190a71e
commit
7624d54f51
2 changed files with 77 additions and 35 deletions
|
|
@ -13,6 +13,8 @@ calls the existing Bun scripts.
|
||||||
`~/Development/MyWeb/magnum-opus/main`.
|
`~/Development/MyWeb/magnum-opus/main`.
|
||||||
- `direnv` is installed and the website `.envrc` is allowed.
|
- `direnv` is installed and the website `.envrc` is allowed.
|
||||||
- The website development shell provides Bun.
|
- The website development shell provides Bun.
|
||||||
|
- `TYPESAFE_API_KEY` or `TYPESAFEAI_API_KEY` is available for tag classification.
|
||||||
|
- `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, or `TAG_GENERATOR_API_KEY` is available when a new tag must be created.
|
||||||
|
|
||||||
Set `RASYIDANAF_SITE_ROOT` before starting Neovim to use another checkout:
|
Set `RASYIDANAF_SITE_ROOT` before starting Neovim to use another checkout:
|
||||||
|
|
||||||
|
|
@ -26,9 +28,10 @@ The Notes group uses the local leader (`\`).
|
||||||
|
|
||||||
| Command | Mapping | Action |
|
| Command | Mapping | Action |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `:NotesNewLog` | `\nl` | Prompt for an optional title and tags, create a Log draft, and open it |
|
| `:NotesNewLog` | `\nl` | Prompt for an optional title, create an untagged Log draft, and open it |
|
||||||
| `:NotesNewNote` | `\nn` | Prompt for a required title and optional tags, create a Note draft, and open it |
|
| `:NotesNewNote` | `\nn` | Prompt for a required title, create an untagged Note draft, and open it |
|
||||||
| `:NotesPublish` | `\np` | Save and publish the current Entry after confirmation |
|
| `:NotesPublish` | `\np` | Save and publish the current Entry after confirmation |
|
||||||
|
| `:ContentAutoTag` | `\nt` | Save and automatically tag the current Draft Note or Blog Post |
|
||||||
| `:NotesValidate` | `\nv` | Validate public Notes metadata, routes, lifecycle, tags, and media |
|
| `:NotesValidate` | `\nv` | Validate public Notes metadata, routes, lifecycle, tags, and media |
|
||||||
| `:NotesBuild` | `\nb` | Run the complete production build |
|
| `:NotesBuild` | `\nb` | Run the complete production build |
|
||||||
|
|
||||||
|
|
@ -36,13 +39,19 @@ The Notes group uses the local leader (`\`).
|
||||||
does not commit, push, or deploy. Review the Jujutsu diff and push the website
|
does not commit, push, or deploy. Review the Jujutsu diff and push the website
|
||||||
repository separately.
|
repository separately.
|
||||||
|
|
||||||
|
`ContentAutoTag` accepts Drafts in `vault/public-notes/` and `vault/blog/`.
|
||||||
|
TypeSafe selects and validates tags. The command reuses tags collected from both
|
||||||
|
content areas. If no existing tag fits, an OpenAI-compatible generator proposes
|
||||||
|
new tags and TypeSafe validates them before the command updates frontmatter.
|
||||||
|
|
||||||
## Normal workflow
|
## Normal workflow
|
||||||
|
|
||||||
1. Press `\nl` for a Log or `\nn` for a titled Note.
|
1. Press `\nl` for a Log or `\nn` for a titled Note.
|
||||||
2. Write and save the Entry.
|
2. Write and save the Entry.
|
||||||
3. Press `\np` and confirm publication.
|
3. Press `\nt` to generate tags and review the notification.
|
||||||
4. Press `\nv`, then `\nb`.
|
4. Press `\np` and confirm publication.
|
||||||
5. Review, describe, and push the website change with Jujutsu.
|
5. Press `\nv`, then `\nb`.
|
||||||
|
6. Review, describe, and push the website change with Jujutsu.
|
||||||
|
|
||||||
The publication command reloads the current buffer after the repository script
|
The publication command reloads the current buffer after the repository script
|
||||||
writes `publishedAt` and processes referenced images.
|
writes `publishedAt` and processes referenced images.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ local M = {}
|
||||||
local default_root = vim.fn.expand("~/Development/MyWeb/magnum-opus/main")
|
local default_root = vim.fn.expand("~/Development/MyWeb/magnum-opus/main")
|
||||||
local root = vim.fs.normalize(vim.env.RASYIDANAF_SITE_ROOT or default_root)
|
local root = vim.fs.normalize(vim.env.RASYIDANAF_SITE_ROOT or default_root)
|
||||||
local notes_directory = vim.fs.normalize(root .. "/vault/public-notes")
|
local notes_directory = vim.fs.normalize(root .. "/vault/public-notes")
|
||||||
|
local blog_directory = vim.fs.normalize(root .. "/vault/blog")
|
||||||
|
|
||||||
local function notify(message, level)
|
local function notify(message, level)
|
||||||
vim.notify(message, level or vim.log.levels.INFO, { title = "Notes" })
|
vim.notify(message, level or vim.log.levels.INFO, { title = "Notes" })
|
||||||
|
|
@ -78,18 +79,6 @@ local function run(arguments, options)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function prompt_tags(callback)
|
|
||||||
vim.ui.input({
|
|
||||||
prompt = "Tags (comma-separated, optional): ",
|
|
||||||
}, function(tags)
|
|
||||||
if tags == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
callback(vim.trim(tags))
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function open_created_entry(output)
|
local function open_created_entry(output)
|
||||||
local relative_path = last_line(output)
|
local relative_path = last_line(output)
|
||||||
if not relative_path then
|
if not relative_path then
|
||||||
|
|
@ -106,17 +95,13 @@ local function open_created_entry(output)
|
||||||
notify("Draft created: " .. relative_path)
|
notify("Draft created: " .. relative_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_entry(kind, title, tags)
|
local function create_entry(kind, title)
|
||||||
local arguments = { "notes:new", "--", "--kind", kind }
|
local arguments = { "notes:new", "--", "--kind", kind }
|
||||||
|
|
||||||
if title and title ~= "" then
|
if title and title ~= "" then
|
||||||
vim.list_extend(arguments, { "--title", title })
|
vim.list_extend(arguments, { "--title", title })
|
||||||
end
|
end
|
||||||
|
|
||||||
if tags ~= "" then
|
|
||||||
vim.list_extend(arguments, { "--tags", tags })
|
|
||||||
end
|
|
||||||
|
|
||||||
run(arguments, {
|
run(arguments, {
|
||||||
progress = "Creating " .. kind .. " draft…",
|
progress = "Creating " .. kind .. " draft…",
|
||||||
on_success = open_created_entry,
|
on_success = open_created_entry,
|
||||||
|
|
@ -131,9 +116,7 @@ function M.new_log()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
prompt_tags(function(tags)
|
create_entry("log", vim.trim(title))
|
||||||
create_entry("log", vim.trim(title), tags)
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -151,18 +134,24 @@ function M.new_note()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
prompt_tags(function(tags)
|
create_entry("note", title)
|
||||||
create_entry("note", title, tags)
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function current_entry()
|
local function current_path()
|
||||||
local path = vim.fs.normalize(vim.api.nvim_buf_get_name(0))
|
local path = vim.fs.normalize(vim.api.nvim_buf_get_name(0))
|
||||||
if path == "" then
|
if path == "" then
|
||||||
notify("The current buffer has no file.", vim.log.levels.ERROR)
|
notify("The current buffer has no file.", vim.log.levels.ERROR)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
local function current_entry()
|
||||||
|
local path = current_path()
|
||||||
|
if not path then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
if not vim.startswith(path, notes_directory .. "/") or not path:match("%.md$") then
|
if not vim.startswith(path, notes_directory .. "/") or not path:match("%.md$") then
|
||||||
notify("Open a Markdown file inside vault/public-notes/ first.", vim.log.levels.ERROR)
|
notify("Open a Markdown file inside vault/public-notes/ first.", vim.log.levels.ERROR)
|
||||||
|
|
@ -172,18 +161,43 @@ local function current_entry()
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function current_taggable_content()
|
||||||
|
local path = current_path()
|
||||||
|
if not path then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_public_note = vim.startswith(path, notes_directory .. "/") and path:match("%.md$")
|
||||||
|
local is_blog_post = vim.startswith(path, blog_directory .. "/")
|
||||||
|
and (path:match("/index%.md$") or path:match("/index%.mdx$"))
|
||||||
|
if not is_public_note and not is_blog_post then
|
||||||
|
notify("Open a Draft in vault/public-notes/ or vault/blog/ first.", vim.log.levels.ERROR)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
local function save_current_buffer()
|
||||||
|
if not vim.bo.modified then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local saved, error_message = pcall(vim.cmd.write)
|
||||||
|
if not saved then
|
||||||
|
notify("Could not save the content: " .. tostring(error_message), vim.log.levels.ERROR)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function M.publish()
|
function M.publish()
|
||||||
local path = current_entry()
|
local path = current_entry()
|
||||||
if not path then
|
if not path then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.bo.modified then
|
if not save_current_buffer() then
|
||||||
local saved, error_message = pcall(vim.cmd.write)
|
return
|
||||||
if not saved then
|
|
||||||
notify("Could not save the Entry: " .. tostring(error_message), vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.ui.select({ "Publish", "Cancel" }, {
|
vim.ui.select({ "Publish", "Cancel" }, {
|
||||||
|
|
@ -203,6 +217,21 @@ function M.publish()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.auto_tag()
|
||||||
|
local path = current_taggable_content()
|
||||||
|
if not path or not save_current_buffer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
run({ "content:tag", path }, {
|
||||||
|
progress = "Generating tags…",
|
||||||
|
on_success = function(output)
|
||||||
|
vim.cmd.edit(vim.fn.fnameescape(path))
|
||||||
|
notify(last_line(output) or "Tags applied.")
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
function M.validate()
|
function M.validate()
|
||||||
run({ "notes:validate" }, {
|
run({ "notes:validate" }, {
|
||||||
progress = "Validating Notes…",
|
progress = "Validating Notes…",
|
||||||
|
|
@ -226,6 +255,9 @@ vim.api.nvim_create_user_command("NotesNewNote", M.new_note, {
|
||||||
vim.api.nvim_create_user_command("NotesPublish", M.publish, {
|
vim.api.nvim_create_user_command("NotesPublish", M.publish, {
|
||||||
desc = "Publish the current public Notes Entry",
|
desc = "Publish the current public Notes Entry",
|
||||||
})
|
})
|
||||||
|
vim.api.nvim_create_user_command("ContentAutoTag", M.auto_tag, {
|
||||||
|
desc = "Automatically tag the current Draft Note or Blog Post",
|
||||||
|
})
|
||||||
vim.api.nvim_create_user_command("NotesValidate", M.validate, {
|
vim.api.nvim_create_user_command("NotesValidate", M.validate, {
|
||||||
desc = "Validate public Notes",
|
desc = "Validate public Notes",
|
||||||
})
|
})
|
||||||
|
|
@ -236,6 +268,7 @@ vim.api.nvim_create_user_command("NotesBuild", M.build, {
|
||||||
vim.keymap.set("n", "<localleader>nl", M.new_log, { desc = "New log" })
|
vim.keymap.set("n", "<localleader>nl", M.new_log, { desc = "New log" })
|
||||||
vim.keymap.set("n", "<localleader>nn", M.new_note, { desc = "New note" })
|
vim.keymap.set("n", "<localleader>nn", M.new_note, { desc = "New note" })
|
||||||
vim.keymap.set("n", "<localleader>np", M.publish, { desc = "Publish current note" })
|
vim.keymap.set("n", "<localleader>np", M.publish, { desc = "Publish current note" })
|
||||||
|
vim.keymap.set("n", "<localleader>nt", M.auto_tag, { desc = "Auto-tag current draft" })
|
||||||
vim.keymap.set("n", "<localleader>nv", M.validate, { desc = "Validate notes" })
|
vim.keymap.set("n", "<localleader>nv", M.validate, { desc = "Validate notes" })
|
||||||
vim.keymap.set("n", "<localleader>nb", M.build, { desc = "Build website" })
|
vim.keymap.set("n", "<localleader>nb", M.build, { desc = "Build website" })
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue