zerowidth positive lookahead

An Obsidian project template for GitHub issues

How to wire up a CLI utility and plugins in Obsidian to automate project document creation from a GitHub issue URL on the clipboard.

I use per-project documents to track project-specific notes and work logs in Obisidian. Often these projects are related to a specific GitHub issue, so I connected a CLI utility with a few Obsidian plugins to make these issue-related projects easy to create. The basic flow:

  • Copy the issue URL to the clipboard
  • ⌘+P to open the Obsidian command palette
  • Type in git issue or enough to match a “QuickAdd: Create Project for GitHub Issue” command
  • Select the project folder it goes in

Doing those steps for an issue, let’s say cli/cli#7482: [docs] Document API caching behavior, results in a new project document: Issue - 2023-12 - (docs) Document API caching behavior.md:

---
status: in progress
issue: https://github.com/cli/cli/issues/7482
---

## References

- [cli/cli#7482: [docs] Document API caching behavior](https://github.com/cli/cli/issues/7482)

## Log

### [[2023-12-30 Sat]]

(cursor here)

## Next

- [ ] 

This saves several steps:

  • Creating a new project document in the right location
  • Naming the project after the current month and including the GitHub issue’s title, in a markdown and path safe format
  • Linking the issue from both the metadata and the “References” section

The project template

This is the project template, stored in my Obsidian vault at Templates/Issue Project.md. I’ll explain each piece of this as we go.

---
status: in progress
issue: <% await tp.system.clipboard() %>
---
<%- await tp.file.rename("Issue - " + tp.date.now("YYYY-MM") + " - " + (await tp.user.markdownTitle({input: (await tp.system.clipboard())}))) -%>

## References

- <% tp.user.markdownLink({input: (await tp.system.clipboard())}) %>

## Log

### [[<% tp.date.now("YYYY-MM-DD ddd") %>]]

<% tp.file.cursor() %>

## Next

Configuring the template

Copy the above template into an appropriate location, e.g. Templates/Issue Project.md.

To support this workflow, I wrote a CLI extension to the GitHub CLI called gh-md. It uses the GitHub API to generate markdown links, issue references, and issue titles from GitHub issue URLs.

For example:

$ gh md link https://github.com/cli/cli/pull/123
[cli/cli#123: Tweak flags language](https://github.com/cli/cli/pull/123)

$ gh md title cli/cli#123
Tweak flags language

If you don’t already have the GitHub CLI, you can install it withhomebrew: brew install gh. gh auth login to authenticate, if you’re planning to use this with private repositories.

To install the gh-md extension, run gh extension install zerowidth/gh-md.

Templater

Now that we have markdown link generation available at the command line, we’ll wire it into an Obsidian plugin called Templater. Templater lets us insert and execute code from within an Obsidian document when it’s created.

We’ll need to define two user-defined functions in the templater configuration to use gh-md:

  • markdownLink is defined as PATH=$PATH:/opt/homebrew/bin /opt/homebrew/bin/gh md link -n "$input"
  • markdownTitle is PATH=$PATH:/opt/homebrew/bin /opt/homebrew/bin/gh md title -n --sanitize "$input"

Assuming you’ve installed the GitHub CLI with homebrew, the PATH= bit at the front ensures the homebrew path is present to allow gh-md calls to correctly authenticate against the API.

The only other setting we need in Templater is Trigger Templater on new file creation, to make sure it runs automatically.

QuickAdd

The QuickAdd plugin, though confusing to configure, gives us the ability to create top-level Obsidian commands for the command palette that can create documents (and more). We’ll use this to define a Create Project from GitHub Issue command:

  • Enter “Create Project from GitHub Issue” in the Name text field and click Add Choice
  • Click the gear icon next to the new entry to configure it
  • Set the template path to Templates/Issue Project.md
  • Set the file name format to Issue - {{DATE:YYYY-MM}} - loading. This adds the current year and month to the document title but otherwise names it as a placeholder value.
  • Enable Create in folder, and select your projects folder
  • Optionally enable Include subfolders if you want more granularity there
  • Enable Set default behavior if file already exists and set it to Increment the filename. This will prevent accidental overwrites in case something goes wrong.
  • Enable Open to open the created file.

Go back to the QuickAdd settings and toggle the lightning bolt icon for the choice you added. This makes it available as a top-level command in the Obsidian command palette.

Try it out

Copy a GitHub issue URL to your clipboard, find the new Create Project from GitHub Issue command in the command palette, and run it. If all goes well, you’ll see a new document in your vault, correctly named after the issue, with the cursor in the current day’s log entry.

Explaining the template

First, the document properties:

---
status: in progress
issue: <% await tp.system.clipboard() %>
---

This inserts the contents of the clipboard (the issue URL) as an issue property in the document.

<%- await tp.file.rename("Issue - " + tp.date.now("YYYY-MM") + " - " +
  (await tp.user.markdownTitle({input: (await tp.system.clipboard())}))) -%>

This calls the markdownTitle user-defined function with the clipboard contents to fetch a path-safe title from the issue URL, then renames the current file (the newly generated project file) to include the title.

## References

- <% tp.user.markdownLink({input: (await tp.system.clipboard())}) %>

Adds a full markdown link to the issue in the References section, using the markdownLink user-defined function.

## Log

### [[<% tp.date.now("YYYY-MM-DD ddd") %>]]

<% tp.file.cursor() %>

## Next

Creates the rest of the project document, including an empty log entry for today’s date. It also focuses the cursor in that empty log entry.

And that’s it! Now it’s just a few keystrokes to get from an issue URL to a full project document, ready to track your work and notes.

More uses for the user functions

I open and ship PRs often enough that I have a QuickAdd “Add tasks for PR from clipboard” choice. It’s configured to “capture to active file” with an inline template:

- [x] open <% tp.user.markdownLink({input: (await tp.system.clipboard())}) %>
- [ ] ship <% tp.user.markdownLink({input: (await tp.system.clipboard())}) %>

When I open a PR, I’ve now got two TODO items with the first one completed.

Similarly, when I’m reviewing PRs or want to capture the intent, “Add task to review PR from clipboard”:

- [ ] review <% tp.user.markdownLink({input: (await tp.system.clipboard())}) %>

Bonus progress bar

If you want to see a progress bar at the top of the document showing task completion, try this snippet:

Progress: `$= const value = Math.round(((dv.current().file.tasks.where(t => t.completed).length) / (dv.current().file.tasks).length || 0) * 100); "<progress value='" + value + "' max='100'></progress>" + " " + value + "%"`