diff --git a/cmd/assets/guide/domain-task-bookmarks.md b/cmd/assets/guide/domain-task-bookmarks.md index c4a7029..18d6e75 100644 --- a/cmd/assets/guide/domain-task-bookmarks.md +++ b/cmd/assets/guide/domain-task-bookmarks.md @@ -1,14 +1,19 @@ -Sometimes you'll add URLs to a task's summary or its context. +Sometimes you'll add URIs to a task's summary or its context. -Such URLs (eg. https://github.com/dhth/omm, https://tools.dhruvs.space, -https://c.xkcd.com/random/comic) could be placed anywhere in the -summary/context. +Such URIs could be placed anywhere in the summary/context. For example: -omm lets you open these URLs via a single keypress. You can either press `b` to -open up a list of all URLs, and then open one of them by pressing `⏎` or open -all of them by pressing `B`. +- https://c.xkcd.com/random/comic +- spotify:track:4fVBFyglBhMf0erfF7pBJp +- obsidian://open?vault=VAULT&file=FILE +- mailto:example@example.com +- slack://channel?team=TEAM_ID&id=ID -Note: If a task has a single URL added to it, pressing `b` will skip showing the -list, and open the URL directly. +omm lets you open these URIs via a single keypress. You can either press `b` to +open up a list of all URIs, and then open one of them by pressing `⏎` or open +all of them by pressing `B` (some of them will fail to be opened on your machine +since they point to non-existent resources, but you get the point). + +Note: If a task has a single URI added to it, pressing `b` will skip showing the +list, and open the URI directly. Try both approaches now. diff --git a/internal/types/colors.go b/internal/types/colors.go new file mode 100644 index 0000000..f69a4a8 --- /dev/null +++ b/internal/types/colors.go @@ -0,0 +1,20 @@ +package types + +var ( + colors = []string{ + "#d3869b", + "#b5e48c", + "#90e0ef", + "#ca7df9", + "#ada7ff", + "#bbd0ff", + "#48cae4", + "#8187dc", + "#ffb4a2", + "#b8bb26", + "#ffc6ff", + "#4895ef", + "#83a598", + "#fabd2f", + } +) diff --git a/internal/types/types.go b/internal/types/types.go index 7e6f67e..978e655 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -26,23 +26,7 @@ const ( var ( createdAtColor = "#928374" hasContextColor = "#928374" - taskColors = []string{ - "#d3869b", - "#b5e48c", - "#90e0ef", - "#ca7df9", - "#ada7ff", - "#bbd0ff", - "#48cae4", - "#8187dc", - "#ffb4a2", - "#b8bb26", - "#ffc6ff", - "#4895ef", - "#83a598", - "#fabd2f", - } - createdAtStyle = lipgloss.NewStyle(). + createdAtStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color(createdAtColor)) hasContextStyle = lipgloss.NewStyle(). @@ -155,7 +139,7 @@ func GetDynamicStyle(str string) lipgloss.Style { h.Write([]byte(str)) hash := h.Sum32() - color := taskColors[int(hash)%len(taskColors)] + color := colors[int(hash)%len(colors)] return lipgloss.NewStyle(). Foreground(lipgloss.Color(color)) } diff --git a/internal/ui/assets/help.md b/internal/ui/assets/help.md index 305bb6c..4941084 100644 --- a/internal/ui/assets/help.md +++ b/internal/ui/assets/help.md @@ -84,5 +84,5 @@ B open all bookmarks added to current task ### Task Bookmarks List ```text -⏎ open URL in browser +⏎ open URI in browser ``` diff --git a/internal/ui/cmds.go b/internal/ui/cmds.go index a06785f..9707d94 100644 --- a/internal/ui/cmds.go +++ b/internal/ui/cmds.go @@ -92,7 +92,7 @@ func openTextEditor(fPath string, editorCmd []string, taskIndex int, taskId uint }) } -func openURL(url string) tea.Cmd { +func openURI(uri string) tea.Cmd { var cmd string var args []string switch runtime.GOOS { @@ -104,16 +104,16 @@ func openURL(url string) tea.Cmd { default: cmd = "xdg-open" } - c := exec.Command(cmd, append(args, url)...) + c := exec.Command(cmd, append(args, uri)...) return tea.ExecProcess(c, func(err error) tea.Msg { - return urlOpenedMsg{url, err} + return uriOpenedMsg{uri, err} }) } -func openURLsDarwin(urls []string) tea.Cmd { - c := exec.Command("open", urls...) +func openURIsDarwin(uris []string) tea.Cmd { + c := exec.Command("open", uris...) return tea.ExecProcess(c, func(err error) tea.Msg { - return urlsOpenedDarwinMsg{urls, err} + return urisOpenedDarwinMsg{uris, err} }) } diff --git a/internal/ui/initial.go b/internal/ui/initial.go index c0dc55d..17422ad 100644 --- a/internal/ui/initial.go +++ b/internal/ui/initial.go @@ -9,7 +9,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/dhth/omm/internal/types" "github.com/dhth/omm/internal/utils" - "mvdan.cc/xurls/v2" ) func InitialModel(db *sql.DB, config Config) model { @@ -135,7 +134,7 @@ func InitialModel(db *sql.DB, config Config) model { atlSelStyle: atlSelItemStyle, contextVPTaskId: 0, rtos: runtime.GOOS, - urlRegex: xurls.Strict(), + uriRegex: utils.GetURIRegex(), taskDetailsMdRenderer: tr, } diff --git a/internal/ui/model.go b/internal/ui/model.go index 58c75e2..c8ddd17 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -89,7 +89,7 @@ type model struct { terminalHeight int contextVPTaskId uint64 rtos string - urlRegex *regexp.Regexp + uriRegex *regexp.Regexp shortenedListHt int contextMdRenderer *glamour.TermRenderer taskDetailsMdRenderer *glamour.TermRenderer diff --git a/internal/ui/msgs.go b/internal/ui/msgs.go index a568734..f98a96a 100644 --- a/internal/ui/msgs.go +++ b/internal/ui/msgs.go @@ -66,12 +66,12 @@ type textEditorClosed struct { err error } -type urlOpenedMsg struct { +type uriOpenedMsg struct { url string err error } -type urlsOpenedDarwinMsg struct { +type urisOpenedDarwinMsg struct { urls []string err error } diff --git a/internal/ui/update.go b/internal/ui/update.go index fcff5e9..c63e122 100644 --- a/internal/ui/update.go +++ b/internal/ui/update.go @@ -873,8 +873,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.archivedTaskList.Select(listIndex) case contextBookmarksView: - url := m.taskBMList.SelectedItem().FilterValue() - cmds = append(cmds, openURL(url)) + uri := m.taskBMList.SelectedItem().FilterValue() + cmds = append(cmds, openURI(uri)) case prefixSelectionView: prefix := m.prefixSearchList.SelectedItem().FilterValue() @@ -1155,24 +1155,24 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } - urls, ok := m.getTaskUrls() + uris, ok := m.getTaskURIs() if !ok { break } - if len(urls) == 0 { + if len(uris) == 0 { m.errorMsg = "No bookmarks for this task" break } - if len(urls) == 1 { - cmds = append(cmds, openURL(urls[0])) + if len(uris) == 1 { + cmds = append(cmds, openURI(uris[0])) break } - bmItems := make([]list.Item, len(urls)) - for i, url := range urls { - bmItems[i] = list.Item(types.ContextBookmark(url)) + bmItems := make([]list.Item, len(uris)) + for i, uri := range uris { + bmItems[i] = list.Item(types.ContextBookmark(uri)) } m.taskBMList.SetItems(bmItems) switch m.activeView { @@ -1189,28 +1189,28 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } - urls, ok := m.getTaskUrls() + uris, ok := m.getTaskURIs() if !ok { break } - if len(urls) == 0 { + if len(uris) == 0 { m.errorMsg = "No bookmarks for this task" break } - if len(urls) == 1 { - cmds = append(cmds, openURL(urls[0])) + if len(uris) == 1 { + cmds = append(cmds, openURI(uris[0])) break } if m.rtos == types.GOOSDarwin { - cmds = append(cmds, openURLsDarwin(urls)) + cmds = append(cmds, openURIsDarwin(uris)) break } - for _, url := range urls { - cmds = append(cmds, openURL(url)) + for _, uri := range uris { + cmds = append(cmds, openURI(uri)) } case "y": @@ -1451,13 +1451,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } cmds = append(cmds, updateTaskContext(m.db, msg.taskIndex, msg.taskId, string(context), m.activeTaskList)) - case urlOpenedMsg: + case uriOpenedMsg: if msg.err != nil { - m.errorMsg = fmt.Sprintf("Error opening url: %s", msg.err) + m.errorMsg = fmt.Sprintf("Error opening uri: %s", msg.err) } - case urlsOpenedDarwinMsg: + case urisOpenedDarwinMsg: if msg.err != nil { - m.errorMsg = fmt.Sprintf("Error opening urls: %s", msg.err) + m.errorMsg = fmt.Sprintf("Error opening uris: %s", msg.err) } case contextWrittenToCBMsg: @@ -1627,7 +1627,7 @@ func (m *model) setContextFSContent(task types.Task) { m.taskDetailsVP.SetContent(details) } -func (m model) getTaskUrls() ([]string, bool) { +func (m model) getTaskURIs() ([]string, bool) { var t types.Task var ok bool @@ -1648,11 +1648,11 @@ func (m model) getTaskUrls() ([]string, bool) { return nil, false } - var urls []string - urls = append(urls, utils.ExtractURLs(m.urlRegex, t.Summary)...) + var uris []string + uris = append(uris, utils.ExtractURIs(m.uriRegex, t.Summary)...) if t.Context != nil { - urls = append(urls, utils.ExtractURLs(m.urlRegex, *t.Context)...) + uris = append(uris, utils.ExtractURIs(m.uriRegex, *t.Context)...) } - return urls, true + return uris, true } diff --git a/internal/utils/assets/gruvbox.json b/internal/utils/assets/gruvbox.json index f525caf..9a8cf41 100644 --- a/internal/utils/assets/gruvbox.json +++ b/internal/utils/assets/gruvbox.json @@ -68,7 +68,7 @@ "block_prefix": ". " }, "task": { - "ticked": "[✓] ", + "ticked": "[✔] ", "unticked": "[ ] " }, "link": { diff --git a/internal/utils/urls.go b/internal/utils/urls.go new file mode 100644 index 0000000..7265d92 --- /dev/null +++ b/internal/utils/urls.go @@ -0,0 +1,53 @@ +package utils + +import ( + "regexp" + "strings" + + "mvdan.cc/xurls/v2" +) + +// duplicated from https://github.com/mvdan/xurls/blob/master/xurls.go#L83-L88 as xurls doesn't let the user extend or +// override SchemesNoAuthority +var ( + knownSchemes = []string{ + `cid`, + `file`, + `magnet`, + `mailto`, + `mid`, + `sms`, + `tel`, + `xmpp`, + `spotify`, + `facetime`, + `facetime-audio`, + } + anyScheme = `(?:[a-zA-Z][a-zA-Z.\-+]*://|` + anyOf(knownSchemes...) + `:)` +) + +func anyOf(strs ...string) string { + var b strings.Builder + b.WriteString("(?:") + for i, s := range strs { + if i != 0 { + b.WriteByte('|') + } + b.WriteString(regexp.QuoteMeta(s)) + } + b.WriteByte(')') + return b.String() +} + +func GetURIRegex() *regexp.Regexp { + rgx, err := xurls.StrictMatchingScheme(anyScheme) + if err != nil { + return xurls.Strict() + } + + return rgx +} + +func ExtractURIs(rg *regexp.Regexp, text string) []string { + return rg.FindAllString(text, -1) +} diff --git a/internal/utils/utils_test.go b/internal/utils/urls_test.go similarity index 53% rename from internal/utils/utils_test.go rename to internal/utils/urls_test.go index e3be0cc..b5bc0b2 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/urls_test.go @@ -4,10 +4,9 @@ import ( "testing" "github.com/stretchr/testify/assert" - "mvdan.cc/xurls/v2" ) -func TestExtractURLs(t *testing.T) { +func TestExtractURIs(t *testing.T) { testCases := []struct { name string input string @@ -63,15 +62,61 @@ at several points.`, "https://anotherurl.com/path?query=value", }, }, + { + name: "uris with custom schemes", + input: ` +slack link: slack://open?team=T12345678&id=C12345678 +obsidian link without space after the colon:obsidian://open?vault=VAULT&file=FILE +maps: maps://?q=Central+Park,New+York +a scheme from the future: skynet://someresource?query=param +`, + expected: []string{ + "slack://open?team=T12345678&id=C12345678", + "obsidian://open?vault=VAULT&file=FILE", + "maps://?q=Central+Park,New+York", + "skynet://someresource?query=param", + }, + }, + { + name: "known schemes that use `:`", + input: ` +mail: mailto:example@example.com +telephone: tel:+1234567890 +spotify: spotify:track:6rqhFgbbKwnb9MLmUQDhG6 +facetime: facetime:example@example.com +facetime-audio: facetime-audio:example@example.com +`, + expected: []string{ + "mailto:example@example.com", + "tel:+1234567890", + "spotify:track:6rqhFgbbKwnb9MLmUQDhG6", + "facetime:example@example.com", + "facetime-audio:example@example.com", + }, + }, + // failures + { + name: "doesn't match a uri without a scheme", + input: `someurl.com`, + }, + { + name: "unknown schemes that use `:`", + input: "unknown: unknown:example@example.com", + }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - rxStrict := xurls.Strict() + rgx := GetURIRegex() - got := ExtractURLs(rxStrict, tt.input) + got := ExtractURIs(rgx, tt.input) assert.Equal(t, tt.expected, got) + if len(tt.expected) > 0 { + assert.Equal(t, tt.expected, got) + } else { + assert.Nil(t, got) + } }) } } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index a39a0c1..d4f235b 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,7 +3,6 @@ package utils import ( "fmt" "math" - "regexp" "strings" "time" ) @@ -47,7 +46,3 @@ func HumanizeDuration(durationInSecs int) string { return fmt.Sprintf("%dh %dm", int(duration.Hours()), modMins) } - -func ExtractURLs(rg *regexp.Regexp, text string) []string { - return rg.FindAllString(text, -1) -}