From 17a976235cefdf5bef808de7426bf0baade2a5ff Mon Sep 17 00:00:00 2001 From: Jason Paris Date: Sat, 22 Nov 2025 12:16:08 -0500 Subject: [PATCH 1/2] feat: add weekly notes support with ISO week numbers Add comprehensive weekly notes functionality to complement daily notes: - Create weekly.lua module with ISO week number calculation - Add keybindings: nw (this week), nwl (last week), nwn (next week) - Change workspace picker to nW to avoid keymap conflict - Add :MarkdownNotesWeeklyOpen command with offset support - Add template variables: week_number, week_year, week_id, full_date, year, month, day_name - Include weekly_spec.lua test file - Update README with weekly notes documentation and examples Weekly notes use format: W{week}-{year}-Weekly-Review.md ISO weeks start on Monday, Week 1 contains first Thursday of year Automatically applies Weekly.md template if available Fixes #16 --- README.md | 84 ++++++++++++++++++++++++++-- lua/markdown-notes/config.lua | 28 +++++++++- lua/markdown-notes/init.lua | 30 ++++++++++ lua/markdown-notes/weekly.lua | 69 +++++++++++++++++++++++ tests/markdown-notes/weekly_spec.lua | 20 +++++++ 5 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 lua/markdown-notes/weekly.lua create mode 100644 tests/markdown-notes/weekly_spec.lua diff --git a/README.md b/README.md index bdce8c2..4aa23b6 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ That's it! You're ready to start building your knowledge base. ### Core Features - **📅 Daily Notes** - Quick creation and navigation with automatic templating +- **📆 Weekly Notes** - ISO week-based notes for weekly reviews and planning - **📝 Template System** - Flexible templates with variable substitution (`{{date}}`, `{{time}}`, `{{title}}`, etc.) - **🔗 Wiki-style Links** - Create and follow `[[note-name]]` links between notes - **🔄 Smart Renaming** - Rename notes and automatically update all references with file preview @@ -116,6 +117,7 @@ require("markdown-notes").setup({ vault_path = "~/notes", -- Where your notes live templates_path = "~/notes/templates", -- Where your templates live dailies_path = "~/notes/daily", -- Where daily notes go + weekly_path = "~/notes/weekly", -- Where weekly notes go }) ``` @@ -128,6 +130,9 @@ All keybindings use `n` as the prefix for easy discovery: | `nd` | Daily note (today) | Create/open today's daily note | | `ny` | Daily note (yesterday) | Open yesterday's daily note | | `nt` | Daily note (tomorrow) | Open tomorrow's daily note | +| `nw` | Weekly note (this week) | Create/open this week's weekly note | +| `nwl` | Weekly note (last week) | Open last week's weekly note | +| `nwn` | Weekly note (next week) | Open next week's weekly note | | `nn` | New note | Create a new note | | `nc` | New note from template | Create note with template selection | | `nf` | Find notes | Search and open existing notes | @@ -137,7 +142,7 @@ All keybindings use `n` as the prefix for easy discovery: | `ng` | Search tags | Find notes by frontmatter tags | | `nb` | Show backlinks | Show notes linking to current note | | `nr` | Rename note | Rename note and update all references with preview | -| `nw` | Pick workspace | Switch between workspaces | +| `nW` | Pick workspace | Switch between workspaces (capital W) | | `gf` | Follow link | Follow link under cursor | > **💡 Tip:** All keybindings can be customized in your configuration. @@ -154,6 +159,25 @@ Daily notes are the heart of many note-taking workflows. Start your day by creat If you have a `Daily.md` template, it will be automatically applied. Otherwise, a basic note with frontmatter is created. +### Weekly Notes and Reviews + +Weekly notes help you plan and review your week at a higher level. They use ISO week numbers for consistency: + +``` +nw → Creates/opens this week's note (e.g., W03-2025-Weekly-Review.md) +nwl → Opens last week's note +nwn → Opens next week's note +``` + +Weekly notes are created with the format `W{week}-{year}-Weekly-Review.md` and automatically apply your `Weekly.md` template if available. The plugin uses ISO week numbers where: +- Weeks start on Monday +- Week 1 is the week containing the first Thursday of the year + +**Example workflow:** +1. Start each week with `nw` to create your weekly planning note +2. Review last week with `nwl` before planning the current week +3. Use template variables like `{{week_number}}`, `{{week_year}}`, and `{{week_id}}` in your Weekly template + ### Creating and Managing Notes #### Basic Note Creation @@ -226,6 +250,8 @@ tags: [meetings] | Command | Description | |---------|-------------| +| `:MarkdownNotesDailyOpen [offset]` | Open daily note (offset in days from today) | +| `:MarkdownNotesWeeklyOpen [offset]` | Open weekly note (offset in weeks from this week) | | `:MarkdownNotesRename [name]` | Rename current note and update references | | `:MarkdownNotesWorkspaceStatus` | Show current workspace | | `:MarkdownNotesWorkspacePick` | Switch workspace with fuzzy finder | @@ -254,12 +280,22 @@ require("markdown-notes").setup({ -- Custom template variables template_vars = { + -- Date/time variables date = function() return os.date("%Y-%m-%d") end, time = function() return os.date("%H:%M") end, datetime = function() return os.date("%Y-%m-%d %H:%M") end, title = function() return vim.fn.expand("%:t:r") end, yesterday = function() return os.date("%Y-%m-%d", os.time() - 86400) end, tomorrow = function() return os.date("%Y-%m-%d", os.time() + 86400) end, + -- Week variables + week_number = function() return os.date("%U") end, + week_year = function() return os.date("%Y") end, + week_id = function() return "W" .. os.date("%U") .. "-" .. os.date("%Y") end, + -- Full date format variables + full_date = function() return os.date("%A, %B %d, %Y") end, + year = function() return os.date("%Y") end, + month = function() return os.date("%B") end, + day_name = function() return os.date("%A") end, -- Add your own custom variables author = function() return "Your Name" end, project = function() return vim.fn.getcwd():match("([^/]+)$") end, @@ -268,8 +304,11 @@ require("markdown-notes").setup({ -- Customize keybindings mappings = { daily_note_today = "nd", - daily_note_yesterday = "ny", + daily_note_yesterday = "ny", daily_note_tomorrow = "nt", + weekly_note_this_week = "nw", + weekly_note_last_week = "nwl", + weekly_note_next_week = "nwn", new_note = "nn", new_note_from_template = "nc", find_notes = "nf", @@ -280,6 +319,7 @@ require("markdown-notes").setup({ show_backlinks = "nb", follow_link = "gf", rename_note = "nr", + pick_workspace = "nW", }, }) ``` @@ -321,7 +361,7 @@ require("markdown-notes").setup_workspace("research", { #### Workspace Workflow -- **Switch workspaces**: Use `nw` to pick from available workspaces +- **Switch workspaces**: Use `nW` (capital W) to pick from available workspaces - **Persistent context**: All commands use the active workspace until you switch - **Independent settings**: Each workspace has its own paths, templates, and variables @@ -355,6 +395,13 @@ Templates are markdown files with special `{{variable}}` syntax that gets substi | `{{title}}` | File name without extension | `meeting-notes` | | `{{yesterday}}` | Yesterday's date | `2025-01-14` | | `{{tomorrow}}` | Tomorrow's date | `2025-01-16` | +| `{{week_number}}` | ISO week number | `03` | +| `{{week_year}}` | Year for the week | `2025` | +| `{{week_id}}` | Week identifier | `W03-2025` | +| `{{full_date}}` | Full date format | `Wednesday, January 15, 2025` | +| `{{year}}` | Current year | `2025` | +| `{{month}}` | Current month name | `January` | +| `{{day_name}}` | Current day name | `Wednesday` | ### Creating Custom Variables @@ -392,6 +439,31 @@ tags: [daily] - [ ] ``` +**Weekly Note Template** (`templates/Weekly.md`): +```markdown +--- +title: Week {{week_number}} - {{week_year}} +date: {{date}} +week: {{week_id}} +tags: [weekly, review] +--- + +# Week {{week_number}}, {{week_year}} + +## 📊 Week Overview + +**Week of:** {{full_date}} + +## 🎯 Goals for This Week +- [ ] + +## 📝 Weekly Accomplishments + +## 🔄 Next Week's Focus + +## 📚 Notes and Learnings +``` + **Meeting Template** (`templates/meeting.md`): ```markdown --- @@ -403,15 +475,15 @@ attendees: [] # {{title}} -**Date:** {{datetime}} -**Attendees:** +**Date:** {{datetime}} +**Attendees:** ## 📋 Agenda ## 📝 Discussion Notes ## ✅ Action Items -- [ ] +- [ ] ## 🔗 Links ``` diff --git a/lua/markdown-notes/config.lua b/lua/markdown-notes/config.lua index 824f199..75b67e5 100644 --- a/lua/markdown-notes/config.lua +++ b/lua/markdown-notes/config.lua @@ -28,6 +28,29 @@ M.defaults = { tomorrow = function() return os.date("%Y-%m-%d", os.time() + 86400) end, + -- Week-related variables + week_number = function() + return os.date("%U") + end, + week_year = function() + return os.date("%Y") + end, + week_id = function() + return "W" .. os.date("%U") .. "-" .. os.date("%Y") + end, + -- Full date format variables + full_date = function() + return os.date("%A, %B %d, %Y") + end, + year = function() + return os.date("%Y") + end, + month = function() + return os.date("%B") + end, + day_name = function() + return os.date("%A") + end, }, -- Default workspace (optional) @@ -44,6 +67,9 @@ M.defaults = { daily_note_today = "nd", daily_note_yesterday = "ny", daily_note_tomorrow = "nt", + weekly_note_this_week = "nw", + weekly_note_last_week = "nwl", + weekly_note_next_week = "nwn", new_note = "nn", new_note_from_template = "nc", find_notes = "nf", @@ -54,7 +80,7 @@ M.defaults = { show_backlinks = "nb", follow_link = "gf", rename_note = "nr", - pick_workspace = "nw", + pick_workspace = "nW", }, } diff --git a/lua/markdown-notes/init.lua b/lua/markdown-notes/init.lua index 4cabf4c..6b51e3f 100644 --- a/lua/markdown-notes/init.lua +++ b/lua/markdown-notes/init.lua @@ -1,6 +1,7 @@ local config = require("markdown-notes.config") local templates = require("markdown-notes.templates") local daily = require("markdown-notes.daily") +local weekly = require("markdown-notes.weekly") local notes = require("markdown-notes.notes") local links = require("markdown-notes.links") local workspace = require("markdown-notes.workspace") @@ -58,6 +59,17 @@ function M.setup_keymaps() daily.open_daily_note(1) end, "Open tomorrow's note") + -- Weekly notes + map("n", "weekly_note_this_week", function() + weekly.open_weekly_note(0) + end, "Open this week's note") + map("n", "weekly_note_last_week", function() + weekly.open_weekly_note(-1) + end, "Open last week's note") + map("n", "weekly_note_next_week", function() + weekly.open_weekly_note(1) + end, "Open next week's note") + -- Note management map("n", "new_note", notes.create_new_note, "Create new note") map("n", "new_note_from_template", notes.create_from_template, "Create new note from template") @@ -85,6 +97,24 @@ function M.setup_keymaps() -- Workspaces map("n", "pick_workspace", workspace.pick_workspace, "Pick workspace") + -- Daily note commands + vim.api.nvim_create_user_command("MarkdownNotesDailyOpen", function(opts) + local offset = 0 + if opts.args and opts.args ~= "" then + offset = tonumber(opts.args) or 0 + end + daily.open_daily_note(offset) + end, { nargs = "?", desc = "Open daily note (offset in days from today)" }) + + -- Weekly note commands + vim.api.nvim_create_user_command("MarkdownNotesWeeklyOpen", function(opts) + local offset = 0 + if opts.args and opts.args ~= "" then + offset = tonumber(opts.args) or 0 + end + weekly.open_weekly_note(offset) + end, { nargs = "?", desc = "Open weekly note (offset in weeks from this week)" }) + -- Note management commands vim.api.nvim_create_user_command("MarkdownNotesRename", function(opts) if opts.args and opts.args ~= "" then diff --git a/lua/markdown-notes/weekly.lua b/lua/markdown-notes/weekly.lua new file mode 100644 index 0000000..a9ac58f --- /dev/null +++ b/lua/markdown-notes/weekly.lua @@ -0,0 +1,69 @@ +local config = require("markdown-notes.config") +local templates = require("markdown-notes.templates") + +local M = {} + +-- Calculate ISO week number and year for a given timestamp +-- ISO weeks start on Monday and the first week of the year is the one containing the first Thursday +local function get_iso_week(timestamp) + timestamp = timestamp or os.time() + local date = os.date("*t", timestamp) + + -- Get day of week (1=Monday, 7=Sunday) + local dow = (date.wday + 5) % 7 + 1 + + -- Find Thursday of the current week + local thursday = timestamp + (4 - dow) * 86400 + local thursday_date = os.date("*t", thursday) + + -- Get January 4th of the same year (always in week 1) + local jan4 = os.time({year = thursday_date.year, month = 1, day = 4, hour = 12}) + local jan4_date = os.date("*t", jan4) + local jan4_dow = (jan4_date.wday + 5) % 7 + 1 + + -- Find Monday of week 1 + local week1_monday = jan4 - (jan4_dow - 1) * 86400 + + -- Calculate week number + local week = math.floor((thursday - week1_monday) / (7 * 86400)) + 1 + + return week, thursday_date.year +end + +function M.open_weekly_note(offset) + offset = offset or 0 + local timestamp = os.time() + (offset * 7 * 86400) -- offset in weeks + local week, year = get_iso_week(timestamp) + + local options = config.get_current_config() + local filename = string.format("W%02d-%d-Weekly-Review.md", week, year) + local file_path = vim.fn.expand(options.weekly_path .. "/" .. filename) + + -- Create directory if it doesn't exist + local dir = vim.fn.fnamemodify(file_path, ":h") + if vim.fn.isdirectory(dir) == 0 then + vim.fn.mkdir(dir, "p") + end + + -- Create file with template if it doesn't exist + if vim.fn.filereadable(file_path) == 0 then + local template_path = vim.fn.expand(options.templates_path .. "/Weekly.md") + if vim.fn.filereadable(template_path) == 1 then + local template_content = vim.fn.readfile(template_path) + local custom_vars = { + week_number = string.format("%02d", week), + week_year = tostring(year), + week_id = string.format("W%02d-%d", week, year), + title = string.format("Week %d, %d", week, year), + date = os.date("%Y-%m-%d", timestamp), + datetime = os.date("%Y-%m-%d %H:%M", timestamp), + } + template_content = templates.substitute_template_vars(template_content, custom_vars) + vim.fn.writefile(template_content, file_path) + end + end + + vim.cmd("edit " .. file_path) +end + +return M diff --git a/tests/markdown-notes/weekly_spec.lua b/tests/markdown-notes/weekly_spec.lua new file mode 100644 index 0000000..56dd263 --- /dev/null +++ b/tests/markdown-notes/weekly_spec.lua @@ -0,0 +1,20 @@ +local weekly = require("markdown-notes.weekly") +local config = require("markdown-notes.config") + +describe("weekly", function() + before_each(function() + config.options = {} + config.workspaces = {} + config.setup({ + weekly_path = "/tmp/test-weekly-notes", + templates_path = "/tmp/test-templates", + }) + end) + + describe("open_weekly_note", function() + it("creates weekly note with ISO week format", function() + -- This is a basic test to verify the module loads and function exists + assert.is_function(weekly.open_weekly_note) + end) + end) +end) From dc79fe46cfb817301355a629ee9124a46f2aa038 Mon Sep 17 00:00:00 2001 From: Jason Paris Date: Sat, 22 Nov 2025 13:38:49 -0500 Subject: [PATCH 2/2] fix: use nww for weekly notes to avoid WhichKey timeout Changed weekly_note_this_week keybinding from nw to nww to prevent WhichKey waiting for additional keys when nwl and nwn are also bound. Also clarified workspace picker uses nW. --- README.md | 8 ++++---- doc/markdown-notes.txt | 4 ++-- lua/markdown-notes/config.lua | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4aa23b6..ea86504 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ All keybindings use `n` as the prefix for easy discovery: | `nd` | Daily note (today) | Create/open today's daily note | | `ny` | Daily note (yesterday) | Open yesterday's daily note | | `nt` | Daily note (tomorrow) | Open tomorrow's daily note | -| `nw` | Weekly note (this week) | Create/open this week's weekly note | +| `nww` | Weekly note (this week) | Create/open this week's weekly note | | `nwl` | Weekly note (last week) | Open last week's weekly note | | `nwn` | Weekly note (next week) | Open next week's weekly note | | `nn` | New note | Create a new note | @@ -164,7 +164,7 @@ If you have a `Daily.md` template, it will be automatically applied. Otherwise, Weekly notes help you plan and review your week at a higher level. They use ISO week numbers for consistency: ``` -nw → Creates/opens this week's note (e.g., W03-2025-Weekly-Review.md) +nww → Creates/opens this week's note (e.g., W03-2025-Weekly-Review.md) nwl → Opens last week's note nwn → Opens next week's note ``` @@ -174,7 +174,7 @@ Weekly notes are created with the format `W{week}-{year}-Weekly-Review.md` and a - Week 1 is the week containing the first Thursday of the year **Example workflow:** -1. Start each week with `nw` to create your weekly planning note +1. Start each week with `nww` to create your weekly planning note 2. Review last week with `nwl` before planning the current week 3. Use template variables like `{{week_number}}`, `{{week_year}}`, and `{{week_id}}` in your Weekly template @@ -306,7 +306,7 @@ require("markdown-notes").setup({ daily_note_today = "nd", daily_note_yesterday = "ny", daily_note_tomorrow = "nt", - weekly_note_this_week = "nw", + weekly_note_this_week = "nww", weekly_note_last_week = "nwl", weekly_note_next_week = "nwn", new_note = "nn", diff --git a/doc/markdown-notes.txt b/doc/markdown-notes.txt index 85c2274..29f42d6 100644 --- a/doc/markdown-notes.txt +++ b/doc/markdown-notes.txt @@ -225,7 +225,7 @@ Templates and Tags~ `ng` Find notes by frontmatter tags Workspaces~ -`nw` Switch between workspaces +`nW` Pick workspace Note: All keybindings can be customized in your configuration. @@ -339,7 +339,7 @@ Setting Up Workspaces~ Workspace Workflow~ *markdown-notes-workspace-workflow* -- Switch workspaces: Use `nw` to pick from available workspaces +- Switch workspaces: Use `nW` to pick from available workspaces - Persistent context: All commands use the active workspace until you switch - Independent settings: Each workspace has its own paths, templates, and variables diff --git a/lua/markdown-notes/config.lua b/lua/markdown-notes/config.lua index 75b67e5..23c7ae4 100644 --- a/lua/markdown-notes/config.lua +++ b/lua/markdown-notes/config.lua @@ -67,7 +67,7 @@ M.defaults = { daily_note_today = "nd", daily_note_yesterday = "ny", daily_note_tomorrow = "nt", - weekly_note_this_week = "nw", + weekly_note_this_week = "nww", weekly_note_last_week = "nwl", weekly_note_next_week = "nwn", new_note = "nn",