├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── .gitignore ├── .vscode └── tasks.json ├── LICENSE ├── README.md ├── cmd ├── create │ └── main.go ├── csearch │ └── main.go ├── link │ └── main.go ├── local-test │ └── main.go ├── search │ └── main.go ├── setcursor │ └── main.go ├── toc │ └── main.go └── wiki-link │ └── main.go ├── core ├── core.go └── core_test.go ├── db ├── db.go └── db_test.go ├── doc ├── Authorize.png ├── BasicSearch.png ├── CreateSearch1.png ├── CreateSearch2.png ├── Link1.png ├── Link2.png ├── NewNote.png ├── RecentNotes.png ├── SearchQueryInBearApp.png ├── SearchSpecialInBearApp.png ├── SpecialSearchAutocomplete.png ├── TagAnyOrder.png ├── TagAutocomplete.png ├── TagAutocompleteMultiple.png ├── TagAutocompleteSearch.png ├── TagFilter.png └── TagTextSearch.png ├── go.mod ├── go.sum ├── icon.png └── info.plist /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: drgrib 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Bug Report 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Have you followed all the installation instructions here? 11 | https://github.com/drgrib/alfred-bear/blob/master/README.md#install 12 | 13 | If you already have and it still isn't working, please provide the debug log you get by following these steps: 14 | https://github.com/drgrib/alfred-bear/issues/56#issuecomment-1546733678 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Feature Request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | cmd/create/create 3 | cmd/csearch/csearch 4 | cmd/link/link 5 | cmd/search/search 6 | cmd/setcursor/setcursor 7 | cmd/toc/toc 8 | cmd/wiki-link/wiki-link -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "go build all", 8 | "dependsOn": [ 9 | "go build csearch", 10 | "go build search", 11 | "go build link", 12 | "go build toc", 13 | "go build create", 14 | "go build setcursor", 15 | "go build wiki-link", 16 | ], 17 | "group": "build" 18 | }, 19 | { 20 | "label": "go build csearch", 21 | "type": "shell", 22 | "options": { 23 | "cwd": "${workspaceFolder}/cmd/csearch" 24 | }, 25 | "command": "rm -f csearch; go build", 26 | "presentation": { 27 | "focus": true, 28 | }, 29 | "group": "build", 30 | "problemMatcher": [] 31 | }, 32 | { 33 | "label": "go build search", 34 | "type": "shell", 35 | "options": { 36 | "cwd": "${workspaceFolder}/cmd/search" 37 | }, 38 | "command": "rm -f search; go build", 39 | "presentation": { 40 | "focus": true, 41 | }, 42 | "group": "build", 43 | "problemMatcher": [] 44 | }, 45 | { 46 | "label": "go build link", 47 | "type": "shell", 48 | "options": { 49 | "cwd": "${workspaceFolder}/cmd/link" 50 | }, 51 | "command": "rm -f link; go build", 52 | "presentation": { 53 | "focus": true, 54 | }, 55 | "group": "build", 56 | "problemMatcher": [] 57 | }, 58 | { 59 | "label": "go build toc", 60 | "type": "shell", 61 | "options": { 62 | "cwd": "${workspaceFolder}/cmd/toc" 63 | }, 64 | "command": "rm -f toc; go build", 65 | "presentation": { 66 | "focus": true, 67 | }, 68 | "group": "build", 69 | "problemMatcher": [] 70 | }, 71 | { 72 | "label": "go build create", 73 | "type": "shell", 74 | "options": { 75 | "cwd": "${workspaceFolder}/cmd/create" 76 | }, 77 | "command": "rm -f create; go build", 78 | "presentation": { 79 | "focus": true, 80 | }, 81 | "group": "build", 82 | "problemMatcher": [] 83 | }, 84 | { 85 | "label": "go build setcursor", 86 | "type": "shell", 87 | "options": { 88 | "cwd": "${workspaceFolder}/cmd/setcursor" 89 | }, 90 | "command": "rm -f setcursor; go build", 91 | "presentation": { 92 | "focus": true, 93 | }, 94 | "group": "build", 95 | "problemMatcher": [] 96 | }, 97 | { 98 | "label": "go build wiki-link", 99 | "type": "shell", 100 | "options": { 101 | "cwd": "${workspaceFolder}/cmd/wiki-link" 102 | }, 103 | "command": "rm -f wiki-link; go build", 104 | "presentation": { 105 | "focus": true, 106 | }, 107 | "group": "build", 108 | "problemMatcher": [] 109 | }, 110 | ] 111 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Chris Redford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?business=N2GLXLS5KBFBY&item_name=Chris+Redford¤cy_code=USD) 2 | 3 | # Bear Workflow 4 | 5 | Streamlined note searching and creation for [Bear](http://www.bear-writer.com/) using [Alfred](https://www.alfredapp.com/workflows/). 6 | 7 | ## Install 8 | 9 | 1. [Download](https://github.com/drgrib/alfred-bear/releases/download/v1.2.4/Bear.alfredworkflow) the latest release and double-click _Bear.alfredworkflow_. Alfred will open the workflow and install it. 10 | 2. [Authorize all the executables used by the workflow](#authorize-all-executables). 11 | 3. If you are on an Apple Silicon Mac (M1, M2, etc.), [install Rosetta](https://github.com/drgrib/alfred-bear#install-rosetta) 12 | 13 | If you are using an old version of Bear before 2.0 use [this](https://github.com/drgrib/alfred-bear/releases/download/1.1.9/Bear.alfredworkflow) download link for the latest version supporting it. 14 | 15 | ## Search 16 | 17 | `bs` or `bsearch` 18 | 19 | ### Recent Notes 20 | 21 | Leave the search field empty to see recent notes with their tags as subtitles. 22 | 23 | 24 | 25 | ### Basic Search 26 | 27 | Start typing to search through the titles and text of most recent notes, title matches first. 28 | 29 | 30 | 31 | ### Tag Search 32 | 33 | Type `#` at any time to autocomplete your tags. 34 | 35 | 36 | 37 | Start typing to search tags. 38 | 39 | 40 | 41 | Once completed, the notes will be filtered by that tag. 42 | 43 | 44 | 45 | Add more tags to filter by multiple tags. 46 | 47 | 48 | 49 | Start typing to search titles and text within a tag. 50 | 51 | 52 | 53 | All these terms can be typed in any order and they will work the same. For example, if you want to add a tag after typing a bare search term, the autocomplete will still help you. Or if you remember you want to filter by another tag after typing the first tag and a bare search term, you can autocomplete and add the second tag by typing `#` again. 54 | 55 | 56 | 57 | ### Search in Bear App 58 | 59 | You can search any _query_ you type in the Bear app's main window by holding down the option key. If you've entered a tag, it will open the Bear main window to that tag for further browsing. 60 | 61 | 62 | 63 | ### Open Note in Bear App 64 | 65 | Similarly, you can open any _note_ you select in the Bear app's main window by holding down the command key. 66 | 67 | ### Link Pasting 68 | 69 | While in your Bear notes, you can paste a link to another note by searching for it and holding down the shift key. 70 | 71 | 72 | 73 | 74 | ### Table of Contents Pasting 75 | 76 | Similarly, you can paste a table of contents for a note by searching for it and holding down the command and shift keys. 77 | 78 | ## New Notes 79 | 80 | `bn` or `bnew` followed by title and optional tags. 81 | 82 | 83 | 84 | Tag autocomplete works the same. Also, any text in your clipboard can be added to the new note by holding down the command key. 85 | 86 | ## Create/Search 87 | 88 | `bcs` or `bcsearch` 89 | 90 | You may find sometimes that you want to retrieve a note if it exists and create it if it does not. This command provides that functionality by combining the behavior of search and create. It will provide all the same search results as normal search and additionally add a create item third in the list using normal create options. 91 | 92 | 93 | 94 | If there are less than two search items, the create item will be the last or only item. 95 | 96 | 97 | 98 | You can additionally create links to notes by holding the shift key while selecting a search item. Selecting the create item while holding the shift key will do nothing. 99 | 100 | ## Why I created this 101 | 102 | I am especially grateful to Chris Brown, who created a [Python based Bear workflow](https://github.com/chrisbro/alfred-bear). It was the basis for this project. However, I decided to create my own project for a few reasons: 103 | 104 | - Compiled Go is faster than interpretted Python. Not that much faster but fast enough for me to notice when searching and creating notes throughout the day. 105 | - I wanted the features involving tag searching and autocompletion, link pasting, and automatic clipboard note content. 106 | - I wanted fewer, more optimized SQL queries into the Bear database to increase speed since this appears to be the main bottleneck on performance. 107 | 108 | ## Authorization 109 | 110 | The first time you use the workflow after installing or upgrading, you will see a security warning: 111 | 112 | 113 | 114 | This is a quirk of macOS 10.15 and above. Apple currently forces developers to pay $99 a year to be able to officially sign their executables and avoid this warning, which I'm not going to pay since I'm providing this workflow for free as an open source project. 115 | 116 | After seeing this warning, you have to go to **System Preferences > Security & Privacy > General** and click the new button that has appeared to allow the executable to run. You then have to run it again and you will see this security warning _again_ but now it will have a new button that lets you allow the executable to run. 117 | 118 | These warnings will appear once for each of the executables inside the workflow as you use new features. Once you have authorized all of them, you won't see these warnings anymore until you install a new version. 119 | 120 | ### Authorize All Executables 121 | 122 | If you want to authorize all Bear Workflow executables at once or if you do not see the above security warnings but the workflow isn't working, you can do the following: 123 | 124 | 1. Go to the **Workflows** section in Alfred Preferences 125 | 2. Right click on **Bear** _by drgrib_ and select **Open in Terminal** 126 | 3. Copy this command and execute it: 127 | 128 | ``` 129 | xattr -rd com.apple.quarantine cmd 130 | ``` 131 | 132 | This should authorize all the Alfred Bear the executables and fix the security errors. 133 | 134 | If you are trying to use the Bear workflow loaded from prefrences via cloud (e.g. through Google Drive) on a new Mac, you may also need to run this command, which will make the executables executable: 135 | ``` 136 | find . -mindepth 3 -maxdepth 3 -type f \! -name "*.go" -exec chmod +x {} \; 137 | ``` 138 | 139 | ## Install Rosetta 140 | 141 | If your mac is based on an Apple Silicon chip (M1, M2, etc.), you need to have Rosetta installed on your system, otherwise Alfred workflows will fail silently. 142 | 143 | Copy this command and execute in terminal to install Rosetta: 144 | 145 | ```sh 146 | softwareupdate --install-rosetta 147 | ``` 148 | 149 | If that fails, try this version, which requires your admin password: 150 | 151 | ``` 152 | sudo softwareupdate --install-rosetta --agree-to-license 153 | ``` 154 | -------------------------------------------------------------------------------- /cmd/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/drgrib/alfred" 7 | 8 | "github.com/drgrib/alfred-bear/core" 9 | "github.com/drgrib/alfred-bear/db" 10 | ) 11 | 12 | func main() { 13 | query := core.ParseQuery(os.Args[1]) 14 | 15 | litedb, err := db.NewBearDB() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | autocompleted, err := core.AutocompleteTags(litedb, query) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | if !autocompleted { 26 | item, err := core.GetCreateItem(query) 27 | if err != nil { 28 | panic(err) 29 | } 30 | alfred.Add(*item) 31 | } 32 | 33 | alfred.Run() 34 | } 35 | -------------------------------------------------------------------------------- /cmd/csearch/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | 8 | "github.com/drgrib/alfred" 9 | 10 | "github.com/drgrib/alfred-bear/core" 11 | "github.com/drgrib/alfred-bear/db" 12 | ) 13 | 14 | func main() { 15 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 16 | 17 | createIndex := 2 18 | query := core.ParseQuery(os.Args[1]) 19 | 20 | litedb, err := db.NewBearDB() 21 | if err != nil { 22 | log.Fatalf("%+v", err) 23 | } 24 | 25 | autocompleted, err := core.Autocomplete(litedb, query) 26 | if err != nil { 27 | log.Fatalf("%+v", err) 28 | } 29 | 30 | if !autocompleted { 31 | searchRows, err := core.GetSearchRows(litedb, query) 32 | if err != nil { 33 | log.Fatalf("%+v", err) 34 | } 35 | 36 | appSearchItem, err := core.GetAppSearchItem(query) 37 | if err != nil { 38 | log.Fatalf("%+v", err) 39 | } 40 | 41 | createItem, err := core.GetCreateItem(query) 42 | if err != nil { 43 | log.Fatalf("%+v", err) 44 | } 45 | 46 | if len(searchRows) > 0 { 47 | endIndex := createIndex 48 | if len(searchRows) < createIndex { 49 | endIndex = len(searchRows) 50 | } 51 | for _, row := range searchRows[:endIndex] { 52 | alfred.Add(core.RowToItem(row, query)) 53 | } 54 | } else if strings.Contains(query.WordString, "@") { 55 | alfred.Add(*appSearchItem) 56 | } 57 | alfred.Add(*createItem) 58 | if len(searchRows) > createIndex { 59 | for _, row := range searchRows[createIndex:] { 60 | alfred.Add(core.RowToItem(row, query)) 61 | } 62 | } 63 | } 64 | 65 | alfred.Run() 66 | } 67 | -------------------------------------------------------------------------------- /cmd/link/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "golang.org/x/text/unicode/norm" 9 | 10 | "github.com/drgrib/alfred-bear/db" 11 | ) 12 | 13 | func main() { 14 | noteID := norm.NFC.String(os.Args[1]) 15 | 16 | litedb, err := db.NewBearDB() 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | callback := fmt.Sprintf("bear://x-callback-url/open-note?id=%s&show_window=yes&new_window=yes", noteID) 22 | rows, err := litedb.Query(fmt.Sprintf(db.NOTE_TITLE_BY_ID, noteID)) 23 | if err != nil { 24 | panic(err) 25 | } 26 | title := rows[0][db.TitleKey] 27 | title = strings.ReplaceAll(title, "[", "- ") 28 | title = strings.ReplaceAll(title, "]", " -") 29 | link := fmt.Sprintf("[%s](%s)", title, callback) 30 | 31 | fmt.Print(link) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/local-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "text/template" 9 | 10 | "github.com/drgrib/alfred-bear/db" 11 | ) 12 | 13 | type TagQueryArg struct { 14 | Text string 15 | IntersectQuery string 16 | } 17 | 18 | func TemplateToString(templateStr string, data any) (string, error) { 19 | var buffer bytes.Buffer 20 | t := template.Must(template.New("").Parse(templateStr)) 21 | err := t.Execute(&buffer, data) 22 | return buffer.String(), err 23 | } 24 | 25 | func main() { 26 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 27 | 28 | litedb, err := db.NewBearDB() 29 | if err != nil { 30 | log.Fatalf("%+v", err) 31 | } 32 | 33 | targetTags := []string{"#health/sleep", "#media/youtube"} 34 | notes, err := litedb.QueryNotesByTextAndTags("", targetTags) 35 | if err != nil { 36 | log.Fatalf("%+v", err) 37 | } 38 | 39 | for _, n := range notes { 40 | tags := strings.Split(n[db.TagsKey], ",") 41 | found := map[string]bool{} 42 | for _, t := range tags { 43 | found[t] = true 44 | } 45 | invalid := false 46 | for _, t := range targetTags { 47 | if !found[db.RemoveTagHashes(t)] { 48 | invalid = true 49 | } 50 | } 51 | if invalid { 52 | fmt.Print("INVALID ") 53 | } 54 | fmt.Println(n[db.TitleKey], n[db.TagsKey]) 55 | } 56 | 57 | fmt.Println() 58 | 59 | queryTemplate := ` 60 | WITH 61 | joined AS ( 62 | SELECT 63 | note.ZUNIQUEIDENTIFIER, 64 | note.ZTITLE, 65 | note.ZTEXT, 66 | note.ZMODIFICATIONDATE, 67 | tag.ZTITLE AS TAG_TITLE, 68 | images.ZSEARCHTEXT 69 | FROM 70 | ZSFNOTE note 71 | INNER JOIN 72 | Z_5TAGS nTag ON note.Z_PK = nTag.Z_5NOTES 73 | INNER JOIN 74 | ZSFNOTETAG tag ON nTag.Z_13TAGS = tag.Z_PK 75 | LEFT JOIN 76 | ZSFNOTEFILE images ON images.ZNOTE = note.Z_PK 77 | WHERE 78 | note.ZARCHIVED = 0 79 | AND note.ZTRASHED = 0 80 | AND note.ZTEXT IS NOT NULL 81 | ), 82 | hasSearchedTags AS ( 83 | {{ .IntersectQuery}} 84 | ) 85 | SELECT 86 | ZUNIQUEIDENTIFIER, 87 | ZTITLE, 88 | GROUP_CONCAT(DISTINCT TAG_TITLE) AS TAGS 89 | FROM 90 | joined 91 | WHERE 92 | ZUNIQUEIDENTIFIER IN hasSearchedTags 93 | AND ( 94 | utflower(ZTITLE) LIKE utflower('%{{ .Text}}%') OR 95 | utflower(ZTEXT) LIKE utflower('%{{ .Text}}%') OR 96 | ZSEARCHTEXT LIKE utflower('%{{ .Text}}%') 97 | ) 98 | GROUP BY 99 | ZUNIQUEIDENTIFIER, 100 | ZTITLE 101 | ORDER BY 102 | CASE WHEN utflower(ZTITLE) LIKE utflower('%{{ .Text}}%') THEN 0 ELSE 1 END, 103 | ZMODIFICATIONDATE DESC 104 | LIMIT 20 105 | ` 106 | 107 | var selectStatements []string 108 | for _, t := range targetTags { 109 | s := fmt.Sprintf("SELECT ZUNIQUEIDENTIFIER FROM joined WHERE utflower(TAG_TITLE) = utflower('%s')", db.RemoveTagHashes(t)) 110 | selectStatements = append(selectStatements, s) 111 | } 112 | 113 | tagQueryArg := TagQueryArg{ 114 | Text: "", 115 | IntersectQuery: strings.Join(selectStatements, "\nINTERSECT\n"), 116 | } 117 | 118 | query, err := TemplateToString(queryTemplate, tagQueryArg) 119 | if err != nil { 120 | log.Fatalf("%+v", err) 121 | } 122 | 123 | notes, err = litedb.Query(query) 124 | if err != nil { 125 | log.Fatalf("%+v", err) 126 | } 127 | 128 | for _, n := range notes { 129 | fmt.Println(n[db.TitleKey], n[db.TagsKey]) 130 | } 131 | 132 | fmt.Println(tagQueryArg.IntersectQuery) 133 | } 134 | -------------------------------------------------------------------------------- /cmd/search/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/drgrib/alfred" 8 | 9 | "github.com/drgrib/alfred-bear/core" 10 | "github.com/drgrib/alfred-bear/db" 11 | ) 12 | 13 | func main() { 14 | query := core.ParseQuery(os.Args[1]) 15 | 16 | litedb, err := db.NewBearDB() 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | autocompleted, err := core.Autocomplete(litedb, query) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | if !autocompleted { 27 | rows, err := core.GetSearchRows(litedb, query) 28 | if err != nil { 29 | panic(err) 30 | } 31 | core.AddNoteRowsToAlfred(rows, query) 32 | if len(rows) == 0 { 33 | if strings.Contains(query.WordString, "@") { 34 | mainWindowItem, err := core.GetAppSearchItem(query) 35 | if err != nil { 36 | panic(err) 37 | } 38 | alfred.Add(*mainWindowItem) 39 | } else { 40 | alfred.Add(alfred.Item{ 41 | Title: "No matching items found", 42 | Valid: alfred.Bool(false), 43 | }) 44 | } 45 | } 46 | } 47 | 48 | alfred.Run() 49 | } 50 | -------------------------------------------------------------------------------- /cmd/setcursor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/drgrib/mac" 8 | ) 9 | 10 | func main() { 11 | timeoutChan := time.After(10 * time.Second) 12 | loop: 13 | for { 14 | select { 15 | case <-timeoutChan: 16 | panic(errors.New("timed out without Bear activating")) 17 | default: 18 | application, err := mac.GetFrontMostApplication() 19 | if err != nil { 20 | panic(err) 21 | } 22 | if application == "Bear" { 23 | break loop 24 | } 25 | time.Sleep(50 * time.Millisecond) 26 | } 27 | } 28 | 29 | script := ` 30 | tell application "System Events" 31 | tell process "Bear" 32 | key code 126 using {command down} 33 | end tell 34 | end tell 35 | ` 36 | _, err := mac.RunApplescript(script) 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/toc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "regexp" 8 | "strings" 9 | 10 | "golang.org/x/text/unicode/norm" 11 | 12 | "github.com/drgrib/alfred-bear/db" 13 | ) 14 | 15 | func main() { 16 | noteID := norm.NFC.String(os.Args[1]) 17 | 18 | litedb, err := db.NewBearDB() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | rows, err := litedb.Query(fmt.Sprintf(db.NOTE_TEXT_BY_ID, noteID)) 24 | if err != nil { 25 | panic(err) 26 | } 27 | text := rows[0][db.TextKey] 28 | 29 | headerRe := regexp.MustCompile(`^#+ .+`) 30 | var tocLines []string 31 | textLines := strings.Split(text, "\n") 32 | for _, l := range textLines { 33 | if headerRe.MatchString(l) { 34 | hashCount := 0 35 | for _, r := range l { 36 | if r != '#' { 37 | break 38 | } 39 | hashCount++ 40 | } 41 | 42 | header := strings.TrimLeft(l, "# ") 43 | escaped := url.PathEscape(header) 44 | callback := fmt.Sprintf( 45 | "bear://x-callback-url/open-note?id=%s&header=%s&show_window=yes&new_window=yes", noteID, escaped) 46 | 47 | header = strings.ReplaceAll(header, "[", "- ") 48 | header = strings.ReplaceAll(header, "]", " -") 49 | 50 | tocLines = append(tocLines, 51 | fmt.Sprintf("%s* [%s](%s)", strings.Repeat("\t", hashCount-1), header, callback), 52 | ) 53 | } 54 | } 55 | 56 | toc := strings.Join(tocLines, "\n") 57 | fmt.Print(toc) 58 | } 59 | -------------------------------------------------------------------------------- /cmd/wiki-link/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "golang.org/x/text/unicode/norm" 9 | 10 | "github.com/drgrib/alfred-bear/db" 11 | ) 12 | 13 | func main() { 14 | noteID := norm.NFC.String(os.Args[1]) 15 | 16 | litedb, err := db.NewBearDB() 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | rows, err := litedb.Query(fmt.Sprintf(db.NOTE_TITLE_BY_ID, noteID)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | title := rows[0][db.TitleKey] 26 | title = strings.ReplaceAll(title, "[", `\[`) 27 | title = strings.ReplaceAll(title, "]", `\]`) 28 | title = strings.ReplaceAll(title, "/", `\/`) 29 | link := fmt.Sprintf("[[%s]]", title) 30 | 31 | fmt.Print(link) 32 | } 33 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "regexp" 7 | "sort" 8 | "strings" 9 | 10 | "github.com/atotto/clipboard" 11 | "github.com/drgrib/alfred" 12 | "github.com/pkg/errors" 13 | "golang.org/x/text/unicode/norm" 14 | 15 | "github.com/drgrib/alfred-bear/db" 16 | ) 17 | 18 | var special = []string{ 19 | "@tagged", 20 | "@untagged", 21 | "@today", 22 | "@yesterday", 23 | "@lastXdays", 24 | "@images", 25 | "@files", 26 | "@attachments", 27 | "@task", 28 | "@todo", 29 | "@done", 30 | "@code", 31 | "@title", 32 | "@locked", 33 | "@date(", 34 | "@cdate(", 35 | } 36 | 37 | const argSplit = "|" 38 | 39 | func getUniqueTagString(tagString string) string { 40 | if tagString == "" { 41 | return "" 42 | } 43 | tags := strings.Split(tagString, ",") 44 | uniqueTags := []string{} 45 | for _, t := range tags { 46 | isPrefix := false 47 | for _, other := range tags { 48 | if t != other && strings.HasPrefix(other, t) { 49 | isPrefix = true 50 | break 51 | } 52 | } 53 | if !isPrefix { 54 | // Multiword tag. 55 | if strings.Contains(t, " ") { 56 | t += "#" 57 | } 58 | uniqueTags = append(uniqueTags, t) 59 | } 60 | } 61 | sort.Strings(uniqueTags) 62 | return "#" + strings.Join(uniqueTags, " #") 63 | } 64 | 65 | func RowToItem(row db.Note, query Query) alfred.Item { 66 | searchCallbackString := getSearchCallbackString(query) 67 | return alfred.Item{ 68 | Title: row[db.TitleKey], 69 | Subtitle: getUniqueTagString(row[db.TagsKey]), 70 | Arg: strings.Join([]string{ 71 | row[db.NoteIDKey], 72 | searchCallbackString, 73 | }, 74 | argSplit, 75 | ), 76 | Valid: alfred.Bool(true), 77 | } 78 | } 79 | 80 | func AddNoteRowsToAlfred(rows []db.Note, query Query) { 81 | for _, row := range rows { 82 | item := RowToItem(row, query) 83 | alfred.Add(item) 84 | } 85 | } 86 | 87 | type Query struct { 88 | Tokens []string 89 | Tags []string 90 | LastToken string 91 | WordString string 92 | } 93 | 94 | func (query Query) String() string { 95 | return strings.Join(query.Tokens, " ") 96 | } 97 | 98 | var spaces = regexp.MustCompile(`\s+`) //nolint:gochecknoglobals 99 | 100 | func ParseQuery(arg string) Query { 101 | query := Query{Tokens: spaces.Split(norm.NFC.String(arg), -1)} 102 | 103 | var words []string 104 | var buffer []string 105 | tagStarted := false 106 | for _, t := range query.Tokens { 107 | switch { 108 | case strings.HasSuffix(t, "#"): 109 | if tagStarted { 110 | // Add the token to the buffer and record tag. 111 | // #a multiword tag# 112 | buffer = append(buffer, t) 113 | tag := strings.Join(buffer, " ") 114 | query.Tags = append(query.Tags, tag) 115 | buffer = nil 116 | tagStarted = false 117 | } else { 118 | words = append(words, t) 119 | } 120 | case strings.HasPrefix(t, "#"): 121 | if tagStarted { 122 | // Split the non-tag tokens from previous tag and 123 | // restart buffer with new token. 124 | // #tag1 some some words #tag2 125 | query.Tags = append(query.Tags, buffer[0]) 126 | words = append(words, buffer[1:]...) 127 | buffer = []string{t} 128 | } else { 129 | buffer = append(buffer, t) 130 | tagStarted = true 131 | } 132 | default: 133 | if tagStarted { 134 | buffer = append(buffer, t) 135 | } else { 136 | words = append(words, t) 137 | } 138 | } 139 | } 140 | if len(buffer) != 0 { 141 | if tagStarted { 142 | query.Tags = append(query.Tags, buffer[0]) 143 | words = append(words, buffer[1:]...) 144 | } else { 145 | words = append(words, buffer...) 146 | } 147 | } 148 | 149 | query.LastToken = query.Tokens[len(query.Tokens)-1] 150 | query.WordString = strings.TrimSpace(strings.Join(words, " ")) 151 | 152 | return query 153 | } 154 | 155 | func Autocomplete(litedb db.LiteDB, query Query) (bool, error) { 156 | autocompleted, err := AutocompleteTags(litedb, query) 157 | if err != nil { 158 | return false, err 159 | } 160 | if autocompleted { 161 | return autocompleted, nil 162 | } 163 | 164 | return AutocompleteSpecial(litedb, query) 165 | } 166 | 167 | func AutocompleteSpecial(litedb db.LiteDB, query Query) (bool, error) { 168 | if strings.HasPrefix(query.LastToken, "@") { 169 | for _, s := range special { 170 | if strings.HasPrefix(s, query.LastToken) { 171 | autocomplete := strings.Join(query.Tokens[:len(query.Tokens)-1], " ") + " " + s + " " 172 | alfred.Add(alfred.Item{ 173 | Title: s, 174 | Autocomplete: strings.TrimLeft(autocomplete, " "), 175 | Valid: alfred.Bool(false), 176 | UID: s, 177 | }) 178 | } 179 | } 180 | return true, nil 181 | } 182 | 183 | if strings.HasPrefix(query.LastToken, "-@") { 184 | for _, s := range special { 185 | if strings.HasPrefix(s, query.LastToken[1:]) { 186 | s = "-" + s 187 | autocomplete := strings.Join(query.Tokens[:len(query.Tokens)-1], " ") + " " + s + " " 188 | alfred.Add(alfred.Item{ 189 | Title: s, 190 | Autocomplete: strings.TrimLeft(autocomplete, " "), 191 | Valid: alfred.Bool(false), 192 | UID: s, 193 | }) 194 | } 195 | } 196 | return true, nil 197 | } 198 | 199 | return false, nil 200 | } 201 | 202 | func AutocompleteTags(litedb db.LiteDB, query Query) (bool, error) { 203 | if strings.HasPrefix(query.LastToken, "#") { 204 | rows, err := litedb.Query(fmt.Sprintf(db.TAGS_BY_TITLE, db.RemoveTagHashes(query.LastToken))) 205 | if err != nil { 206 | return false, err 207 | } 208 | 209 | for _, row := range rows { 210 | tag := "#" + row[db.TitleKey] 211 | if strings.Contains(tag, " ") { 212 | tag += "#" 213 | } 214 | autocomplete := strings.Join(query.Tokens[:len(query.Tokens)-1], " ") + " " + tag + " " 215 | alfred.Add(alfred.Item{ 216 | Title: tag, 217 | Autocomplete: strings.TrimLeft(autocomplete, " "), 218 | Valid: alfred.Bool(false), 219 | UID: tag, 220 | }) 221 | } 222 | return true, nil 223 | } 224 | return false, nil 225 | } 226 | 227 | func escape(s string) string { 228 | return strings.Replace(s, "'", "''", -1) 229 | } 230 | 231 | func GetSearchRows(litedb db.LiteDB, query Query) ([]db.Note, error) { 232 | switch { 233 | case query.WordString == "" && len(query.Tags) == 0 && query.LastToken == "": 234 | rows, err := litedb.Query(db.RECENT_NOTES) 235 | if err != nil { 236 | return nil, errors.WithStack(err) 237 | } 238 | return rows, nil 239 | 240 | case len(query.Tags) != 0: 241 | rows, err := litedb.QueryNotesByTextAndTags(query.WordString, query.Tags) 242 | if err != nil { 243 | return nil, errors.WithStack(err) 244 | } 245 | return rows, nil 246 | 247 | default: 248 | rows, err := litedb.QueryNotesByText(query.WordString) 249 | if err != nil { 250 | return nil, errors.WithStack(err) 251 | } 252 | return rows, nil 253 | } 254 | } 255 | 256 | func GetCreateItem(query Query) (*alfred.Item, error) { 257 | callback := []string{} 258 | if query.WordString != "" { 259 | callback = append(callback, "title="+url.PathEscape(query.WordString)) 260 | } 261 | if len(query.Tags) != 0 { 262 | bareTags := []string{} 263 | for _, t := range query.Tags { 264 | bareTags = append(bareTags, url.PathEscape(db.RemoveTagHashes(t))) 265 | } 266 | callback = append(callback, "tags="+strings.Join(bareTags, ",")) 267 | } 268 | 269 | clipString, err := clipboard.ReadAll() 270 | if err != nil { 271 | return nil, err 272 | } 273 | if clipString != "" { 274 | callback = append(callback, "text="+url.PathEscape(clipString)) 275 | } 276 | callbackString := strings.Join(callback, "&") 277 | 278 | title := fmt.Sprintf("Create %q", query.WordString) 279 | if strings.Contains(title, `\"`) { 280 | title = fmt.Sprintf("Create '%s'", query.WordString) 281 | } 282 | item := alfred.Item{ 283 | Title: title, 284 | Arg: callbackString, 285 | Valid: alfred.Bool(true), 286 | } 287 | if len(query.Tags) != 0 { 288 | item.Subtitle = strings.Join(query.Tags, " ") 289 | } 290 | return &item, nil 291 | } 292 | 293 | func getSearchCallbackString(query Query) string { 294 | callback := []string{} 295 | 296 | if query.WordString != "" { 297 | callback = append(callback, "term="+url.PathEscape(query.WordString)) 298 | } 299 | 300 | if len(query.Tags) != 0 { 301 | callback = append(callback, "tag="+db.RemoveTagHashes(query.Tags[0])) 302 | } 303 | 304 | return strings.Join(callback, "&") 305 | } 306 | 307 | func GetAppSearchItem(query Query) (*alfred.Item, error) { 308 | title := "Search in Bear App" 309 | if query.WordString != "" { 310 | title = fmt.Sprintf("Search %#v in Bear App", query.WordString) 311 | } 312 | 313 | callbackString := getSearchCallbackString(query) 314 | 315 | item := alfred.Item{ 316 | Title: title, 317 | Arg: callbackString, 318 | Valid: alfred.Bool(true), 319 | } 320 | if len(query.Tags) != 0 { 321 | item.Subtitle = query.Tags[0] 322 | } 323 | return &item, nil 324 | } 325 | -------------------------------------------------------------------------------- /core/core_test.go: -------------------------------------------------------------------------------- 1 | package core_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smartystreets/assertions" 7 | 8 | "github.com/drgrib/alfred-bear/core" 9 | ) 10 | 11 | func TestParseQuery(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | arg string 15 | expected core.Query 16 | }{ 17 | { 18 | name: "empty arg", 19 | arg: "", 20 | expected: core.Query{Tokens: []string{""}}, 21 | }, 22 | { 23 | name: "single word", 24 | arg: "hello", 25 | expected: core.Query{ 26 | Tokens: []string{"hello"}, 27 | LastToken: "hello", 28 | WordString: "hello", 29 | }, 30 | }, 31 | { 32 | name: "two words", 33 | arg: "hello world", 34 | expected: core.Query{ 35 | Tokens: []string{"hello", "world"}, 36 | LastToken: "world", 37 | WordString: "hello world", 38 | }, 39 | }, 40 | { 41 | name: "multiple words with bad spacing", 42 | arg: "hello \t world", 43 | expected: core.Query{ 44 | Tokens: []string{"hello", "world"}, 45 | LastToken: "world", 46 | WordString: "hello world", 47 | }, 48 | }, 49 | { 50 | name: "argument contains tag", 51 | arg: "hello #inbox stuff", 52 | expected: core.Query{ 53 | Tokens: []string{"hello", "#inbox", "stuff"}, 54 | Tags: []string{"#inbox"}, 55 | LastToken: "stuff", 56 | WordString: "hello stuff", 57 | }, 58 | }, 59 | { 60 | name: "tag is the last token", 61 | arg: "hello #inbox", 62 | expected: core.Query{ 63 | Tokens: []string{"hello", "#inbox"}, 64 | Tags: []string{"#inbox"}, 65 | LastToken: "#inbox", 66 | WordString: "hello", 67 | }, 68 | }, 69 | { 70 | name: "multiword tag", 71 | arg: "oh boy #hello tag#", 72 | expected: core.Query{ 73 | Tokens: []string{"oh", "boy", "#hello", "tag#"}, 74 | Tags: []string{"#hello tag#"}, 75 | LastToken: "tag#", 76 | WordString: "oh boy", 77 | }, 78 | }, 79 | { 80 | name: "multiword tag with later text", 81 | arg: "oh boy #hello tag# more text", 82 | expected: core.Query{ 83 | Tokens: []string{"oh", "boy", "#hello", "tag#", "more", "text"}, 84 | Tags: []string{"#hello tag#"}, 85 | LastToken: "text", 86 | WordString: "oh boy more text", 87 | }, 88 | }, 89 | } 90 | 91 | for _, test := range tests { 92 | // nolint: scopelint 93 | t.Run(test.name, func(t *testing.T) { 94 | ok, msg := assertions.So( 95 | core.ParseQuery(test.arg), 96 | assertions.ShouldResemble, 97 | test.expected, 98 | ) 99 | if !ok { 100 | t.Error(msg) 101 | } 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "os/user" 8 | "path/filepath" 9 | "regexp" 10 | "sort" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/mattn/go-sqlite3" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | const ( 19 | DbPath = "~/Library/Group Containers/9K33E3U3T4.net.shinyfrog.bear/Application Data/database.sqlite" 20 | 21 | TitleKey = "ZTITLE" 22 | TextKey = "ZTEXT" 23 | TagsKey = "TAGS" 24 | NoteIDKey = "ZUNIQUEIDENTIFIER" 25 | 26 | RECENT_NOTES = ` 27 | SELECT 28 | note.ZUNIQUEIDENTIFIER, 29 | note.ZTITLE, 30 | GROUP_CONCAT(tag.ZTITLE) AS TAGS 31 | FROM 32 | ZSFNOTE note 33 | LEFT JOIN 34 | Z_5TAGS nTag ON note.Z_PK = nTag.Z_5NOTES 35 | LEFT JOIN 36 | ZSFNOTETAG tag ON nTag.Z_13TAGS = tag.Z_PK 37 | WHERE 38 | note.ZARCHIVED = 0 39 | AND note.ZTRASHED = 0 40 | GROUP BY 41 | note.ZUNIQUEIDENTIFIER, 42 | note.ZTITLE 43 | ORDER BY 44 | note.ZMODIFICATIONDATE DESC 45 | LIMIT 25 46 | ` 47 | 48 | NOTES_BY_QUERY = ` 49 | SELECT 50 | note.ZUNIQUEIDENTIFIER, 51 | note.ZTITLE, 52 | GROUP_CONCAT(tag.ZTITLE) AS TAGS 53 | FROM 54 | ZSFNOTE note 55 | LEFT JOIN 56 | Z_5TAGS nTag ON note.Z_PK = nTag.Z_5NOTES 57 | LEFT JOIN 58 | ZSFNOTETAG tag ON nTag.Z_13TAGS = tag.Z_PK 59 | WHERE 60 | note.ZUNIQUEIDENTIFIER IN ( 61 | SELECT 62 | note.ZUNIQUEIDENTIFIER 63 | FROM 64 | ZSFNOTE note 65 | LEFT JOIN 66 | ZSFNOTEFILE images ON images.ZNOTE = note.Z_PK 67 | WHERE 68 | note.ZARCHIVED = 0 69 | AND note.ZTRASHED = 0 70 | AND note.ZTEXT IS NOT NULL 71 | AND ( 72 | utflower(note.ZTITLE) LIKE utflower('%'||$1||'%') OR 73 | utflower(note.ZTEXT) LIKE utflower('%'||$1||'%') OR 74 | images.ZSEARCHTEXT LIKE utflower('%'||$1||'%') 75 | ) 76 | ) 77 | GROUP BY 78 | note.ZUNIQUEIDENTIFIER, 79 | note.ZTITLE 80 | ORDER BY 81 | CASE WHEN utflower(note.ZTITLE) LIKE utflower('%'||$1||'%') THEN 0 ELSE 1 END, 82 | note.ZMODIFICATIONDATE DESC 83 | LIMIT 200 84 | ` 85 | 86 | NOTES_BY_TAGS_AND_QUERY = ` 87 | WITH 88 | joined AS ( 89 | SELECT 90 | note.ZUNIQUEIDENTIFIER, 91 | note.ZTITLE, 92 | note.ZTEXT, 93 | note.ZMODIFICATIONDATE, 94 | tag.ZTITLE AS TAG_TITLE, 95 | images.ZSEARCHTEXT 96 | FROM 97 | ZSFNOTE note 98 | INNER JOIN 99 | Z_5TAGS nTag ON note.Z_PK = nTag.Z_5NOTES 100 | INNER JOIN 101 | ZSFNOTETAG tag ON nTag.Z_13TAGS = tag.Z_PK 102 | LEFT JOIN 103 | ZSFNOTEFILE images ON images.ZNOTE = note.Z_PK 104 | WHERE 105 | note.ZARCHIVED = 0 106 | AND note.ZTRASHED = 0 107 | AND note.ZTEXT IS NOT NULL 108 | ), 109 | hasSearchedTags AS ( 110 | {{ .TagIntersection}} 111 | ) 112 | SELECT 113 | ZUNIQUEIDENTIFIER, 114 | ZTITLE, 115 | GROUP_CONCAT(DISTINCT TAG_TITLE) AS TAGS 116 | FROM 117 | joined 118 | WHERE 119 | ZUNIQUEIDENTIFIER IN hasSearchedTags 120 | AND ( 121 | utflower(ZTITLE) LIKE utflower('%{{ .Text}}%') OR 122 | utflower(ZTEXT) LIKE utflower('%{{ .Text}}%') OR 123 | ZSEARCHTEXT LIKE utflower('%{{ .Text}}%') 124 | ) 125 | GROUP BY 126 | ZUNIQUEIDENTIFIER, 127 | ZTITLE 128 | ORDER BY 129 | CASE WHEN utflower(ZTITLE) LIKE utflower('%{{ .Text}}%') THEN 0 ELSE 1 END, 130 | ZMODIFICATIONDATE DESC 131 | LIMIT 200 132 | ` 133 | 134 | TAGS_BY_TITLE = ` 135 | SELECT 136 | DISTINCT t.ZTITLE 137 | FROM 138 | ZSFNOTE n 139 | INNER JOIN 140 | Z_5TAGS nt ON n.Z_PK = nt.Z_5NOTES 141 | INNER JOIN 142 | ZSFNOTETAG t ON nt.Z_13TAGS = t.Z_PK 143 | WHERE 144 | n.ZARCHIVED = 0 145 | AND n.ZTRASHED = 0 146 | AND UTFLOWER(t.ZTITLE) LIKE UTFLOWER('%%%s%%') 147 | ORDER BY 148 | t.ZMODIFICATIONDATE DESC 149 | LIMIT 25 150 | 151 | ` 152 | 153 | NOTE_TITLE_BY_ID = ` 154 | SELECT 155 | DISTINCT ZTITLE 156 | FROM 157 | ZSFNOTE 158 | WHERE 159 | ZARCHIVED = 0 160 | AND ZTRASHED = 0 161 | AND ZUNIQUEIDENTIFIER = '%s' 162 | ORDER BY 163 | ZMODIFICATIONDATE DESC 164 | LIMIT 25 165 | ` 166 | NOTE_TEXT_BY_ID = ` 167 | SELECT 168 | DISTINCT ZTEXT 169 | FROM 170 | ZSFNOTE 171 | WHERE 172 | ZARCHIVED = 0 173 | AND ZTRASHED = 0 174 | AND ZUNIQUEIDENTIFIER = '%s' 175 | ORDER BY 176 | ZMODIFICATIONDATE DESC 177 | LIMIT 25 178 | ` 179 | ) 180 | 181 | type TagQueryArg struct { 182 | Text string 183 | TagIntersection string 184 | } 185 | 186 | func TemplateToString(templateStr string, data any) (string, error) { 187 | var buffer bytes.Buffer 188 | t := template.Must(template.New("").Parse(templateStr)) 189 | err := t.Execute(&buffer, data) 190 | return buffer.String(), errors.WithStack(err) 191 | } 192 | 193 | type Note map[string]string 194 | 195 | func Expanduser(path string) string { 196 | usr, _ := user.Current() 197 | dir := usr.HomeDir 198 | if path[:2] == "~/" { 199 | path = filepath.Join(dir, path[2:]) 200 | } 201 | return path 202 | } 203 | 204 | type LiteDB struct { 205 | db *sql.DB 206 | } 207 | 208 | func NewLiteDB(path string) (LiteDB, error) { 209 | sql.Register("sqlite3_custom", &sqlite3.SQLiteDriver{ 210 | ConnectHook: func(conn *sqlite3.SQLiteConn) error { 211 | return conn.RegisterFunc("utflower", utfLower, true) 212 | }, 213 | }) 214 | 215 | db, err := sql.Open("sqlite3_custom", path) 216 | litedb := LiteDB{db} 217 | return litedb, err 218 | } 219 | 220 | func utfLower(s string) string { 221 | return strings.ToLower(s) 222 | } 223 | 224 | func NewBearDB() (LiteDB, error) { 225 | path := Expanduser(DbPath) 226 | litedb, err := NewLiteDB(path) 227 | return litedb, err 228 | } 229 | 230 | func (litedb LiteDB) Query(q string, args ...interface{}) ([]Note, error) { 231 | results := []Note{} 232 | rows, err := litedb.db.Query(q, args...) 233 | if err != nil { 234 | return results, errors.WithStack(err) 235 | } 236 | 237 | cols, err := rows.Columns() 238 | if err != nil { 239 | return results, errors.WithStack(err) 240 | } 241 | 242 | for rows.Next() { 243 | m := Note{} 244 | columns := make([]interface{}, len(cols)) 245 | columnPointers := make([]interface{}, len(cols)) 246 | for i := range columns { 247 | columnPointers[i] = &columns[i] 248 | } 249 | if err := rows.Scan(columnPointers...); err != nil { 250 | return results, errors.WithStack(err) 251 | } 252 | for i, colName := range cols { 253 | val := columnPointers[i].(*interface{}) 254 | s, ok := (*val).(string) 255 | if ok { 256 | m[colName] = s 257 | } else { 258 | m[colName] = "" 259 | } 260 | } 261 | results = append(results, m) 262 | } 263 | err = rows.Close() 264 | if err != nil { 265 | return results, errors.WithStack(err) 266 | } 267 | err = rows.Err() 268 | if err != nil { 269 | return results, errors.WithStack(err) 270 | } 271 | return results, errors.WithStack(err) 272 | } 273 | 274 | func escape(s string) string { 275 | return strings.Replace(s, "'", "''", -1) 276 | } 277 | 278 | func containsOrderedWords(text string, words []string) bool { 279 | prev := 0 280 | for _, w := range words { 281 | i := strings.Index(text, w) 282 | if i == -1 || i < prev { 283 | return false 284 | } 285 | prev = i 286 | } 287 | return true 288 | } 289 | 290 | func containsWords(text string, words []string) bool { 291 | for _, w := range words { 292 | if !strings.Contains(text, w) { 293 | return false 294 | } 295 | } 296 | return true 297 | } 298 | 299 | func (litedb LiteDB) queryNotesByTextAndTagConjunction(text, tagIntersection string, tags []string) ([]Note, error) { 300 | tagQueryArg := TagQueryArg{ 301 | Text: escape(text), 302 | TagIntersection: tagIntersection, 303 | } 304 | 305 | query, err := TemplateToString(NOTES_BY_TAGS_AND_QUERY, tagQueryArg) 306 | if err != nil { 307 | return nil, err 308 | } 309 | 310 | return litedb.Query(query) 311 | } 312 | 313 | func RemoveTagHashes(tag string) string { 314 | tag = tag[1:] 315 | if strings.HasSuffix(tag, "#") { 316 | tag = tag[:len(tag)-1] 317 | } 318 | return tag 319 | } 320 | 321 | func (litedb LiteDB) QueryNotesByTextAndTags(text string, tags []string) ([]Note, error) { 322 | var selectStatements []string 323 | for _, t := range tags { 324 | s := fmt.Sprintf("SELECT ZUNIQUEIDENTIFIER FROM joined WHERE utflower(TAG_TITLE) = utflower('%s')", RemoveTagHashes(t)) 325 | selectStatements = append(selectStatements, s) 326 | } 327 | tagIntersection := strings.Join(selectStatements, "\nINTERSECT\n") 328 | 329 | wordQuery := func(word string) ([]Note, error) { 330 | return litedb.queryNotesByTextAndTagConjunction(word, tagIntersection, tags) 331 | } 332 | 333 | return multiWordQuery(text, wordQuery) 334 | } 335 | 336 | func (litedb LiteDB) QueryNotesByText(text string) ([]Note, error) { 337 | wordQuery := func(word string) ([]Note, error) { 338 | word = escape(word) 339 | return litedb.Query(NOTES_BY_QUERY, word) 340 | } 341 | return multiWordQuery(text, wordQuery) 342 | } 343 | 344 | func splitSpacesOrQuoted(s string) []string { 345 | r := regexp.MustCompile(`([^\s"']+)|"([^"]*)"`) 346 | matches := r.FindAllStringSubmatch(s, -1) 347 | var split []string 348 | for _, v := range matches { 349 | match := v[1] 350 | if match == "" { 351 | match = v[2] 352 | } 353 | split = append(split, match) 354 | } 355 | return split 356 | } 357 | 358 | type noteRecord struct { 359 | note Note 360 | contains bool 361 | containsOrderedWords bool 362 | containsWords bool 363 | originalIndex int 364 | } 365 | 366 | func NewNoteRecord(i int, note Note, lowerText string) *noteRecord { 367 | title := strings.ToLower(note[TitleKey]) 368 | words := strings.Split(lowerText, " ") 369 | record := noteRecord{ 370 | originalIndex: i, 371 | note: note, 372 | contains: strings.Contains(title, lowerText), 373 | containsOrderedWords: containsOrderedWords(title, words), 374 | containsWords: containsWords(title, words), 375 | } 376 | return &record 377 | } 378 | 379 | func multiWordQuery(text string, wordQuery func(string) ([]Note, error)) ([]Note, error) { 380 | lowerText := strings.ToLower(text) 381 | words := splitSpacesOrQuoted(lowerText) 382 | 383 | var records []*noteRecord 384 | fullMatch := map[string]bool{} 385 | notes, err := wordQuery(lowerText) 386 | if err != nil { 387 | return nil, err 388 | } 389 | for i, note := range notes { 390 | noteId := note[NoteIDKey] 391 | record := NewNoteRecord(i, note, lowerText) 392 | record.originalIndex = i 393 | records = append(records, record) 394 | fullMatch[noteId] = true 395 | } 396 | 397 | var multiRecords []*noteRecord 398 | count := map[string]int{} 399 | for _, word := range words { 400 | notes, err := wordQuery(word) 401 | if err != nil { 402 | return nil, err 403 | } 404 | 405 | for i, note := range notes { 406 | noteId := note[NoteIDKey] 407 | if count[noteId] == 0 && !fullMatch[noteId] { 408 | record := NewNoteRecord(i, note, lowerText) 409 | record.originalIndex = i 410 | multiRecords = append(multiRecords, record) 411 | } 412 | count[noteId]++ 413 | } 414 | } 415 | 416 | for _, record := range multiRecords { 417 | if count[record.note[NoteIDKey]] == len(words) || record.containsWords { 418 | records = append(records, record) 419 | } 420 | } 421 | 422 | sort.Slice(records, func(i, j int) bool { 423 | iRecord := records[i] 424 | jRecord := records[j] 425 | 426 | if iRecord.contains != jRecord.contains { 427 | return iRecord.contains 428 | } 429 | 430 | if iRecord.containsOrderedWords != jRecord.containsOrderedWords { 431 | return iRecord.containsOrderedWords 432 | } 433 | 434 | if iRecord.containsWords != jRecord.containsWords { 435 | return iRecord.containsWords 436 | } 437 | 438 | return iRecord.originalIndex < jRecord.originalIndex 439 | }) 440 | 441 | var finalRows []Note 442 | for _, noteRecord := range records { 443 | finalRows = append(finalRows, noteRecord.note) 444 | } 445 | 446 | return finalRows, nil 447 | } 448 | -------------------------------------------------------------------------------- /db/db_test.go: -------------------------------------------------------------------------------- 1 | package db_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smartystreets/assertions" 7 | 8 | "github.com/drgrib/alfred-bear/db" 9 | ) 10 | 11 | func TestRemoveTagHashes(t *testing.T) { 12 | tests := []struct { 13 | tag string 14 | expected string 15 | }{ 16 | { 17 | tag: "#simple", 18 | expected: "simple", 19 | }, 20 | { 21 | tag: "#multi word#", 22 | expected: "multi word", 23 | }, 24 | { 25 | tag: "#multi word long#", 26 | expected: "multi word long", 27 | }, 28 | } 29 | 30 | for _, test := range tests { 31 | // nolint: scopelint 32 | t.Run(test.tag, func(t *testing.T) { 33 | ok, msg := assertions.So( 34 | db.RemoveTagHashes(test.tag), 35 | assertions.ShouldResemble, 36 | test.expected, 37 | ) 38 | if !ok { 39 | t.Error(msg) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /doc/Authorize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/Authorize.png -------------------------------------------------------------------------------- /doc/BasicSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/BasicSearch.png -------------------------------------------------------------------------------- /doc/CreateSearch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/CreateSearch1.png -------------------------------------------------------------------------------- /doc/CreateSearch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/CreateSearch2.png -------------------------------------------------------------------------------- /doc/Link1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/Link1.png -------------------------------------------------------------------------------- /doc/Link2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/Link2.png -------------------------------------------------------------------------------- /doc/NewNote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/NewNote.png -------------------------------------------------------------------------------- /doc/RecentNotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/RecentNotes.png -------------------------------------------------------------------------------- /doc/SearchQueryInBearApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/SearchQueryInBearApp.png -------------------------------------------------------------------------------- /doc/SearchSpecialInBearApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/SearchSpecialInBearApp.png -------------------------------------------------------------------------------- /doc/SpecialSearchAutocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/SpecialSearchAutocomplete.png -------------------------------------------------------------------------------- /doc/TagAnyOrder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagAnyOrder.png -------------------------------------------------------------------------------- /doc/TagAutocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagAutocomplete.png -------------------------------------------------------------------------------- /doc/TagAutocompleteMultiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagAutocompleteMultiple.png -------------------------------------------------------------------------------- /doc/TagAutocompleteSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagAutocompleteSearch.png -------------------------------------------------------------------------------- /doc/TagFilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagFilter.png -------------------------------------------------------------------------------- /doc/TagTextSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/doc/TagTextSearch.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/drgrib/alfred-bear 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.2 7 | github.com/drgrib/alfred v0.0.0-20180713231015-cbcb1a4b1a30 8 | github.com/drgrib/mac v0.0.0-20200321221020-4f366006daac 9 | github.com/mattn/go-sqlite3 v1.14.9 10 | github.com/pkg/errors v0.9.1 11 | github.com/smartystreets/assertions v1.1.1 12 | golang.org/x/text v0.3.8 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= 2 | github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/drgrib/alfred v0.0.0-20180713231015-cbcb1a4b1a30 h1:YJztq9iEgAjAxoyya/tNRcI3MHH3oZIu6cdwUOSCSAc= 4 | github.com/drgrib/alfred v0.0.0-20180713231015-cbcb1a4b1a30/go.mod h1:i0gA+4g42sioTtf5J8DHLunASnD4RhyzXQqVNeoTV/o= 5 | github.com/drgrib/mac v0.0.0-20200321221020-4f366006daac h1:dbnAsr/ngd8DSzd3x/2CGQ8ubL5oX2R+Cwdin66Snbc= 6 | github.com/drgrib/mac v0.0.0-20200321221020-4f366006daac/go.mod h1:UaS3hPRZNCmKPJ1c9eKlIaehAOpSpBDLEsJTP/U6BSs= 7 | github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= 8 | github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 9 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 10 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 11 | github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= 12 | github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 13 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 14 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 15 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drgrib/alfred-bear/10590fdeefc57ad502aeed4d51cb53e8ed648708/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.drgrib.bear 7 | category 8 | Tools 9 | connections 10 | 11 | 00E5D1C8-D8AE-43EF-9E0D-92F25F59FE62 12 | 13 | 14 | destinationuid 15 | E92972B8-8164-49C7-BBA8-064C3B42EC29 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | 0344727C-3BE1-4BC7-887A-82BECFC34B13 25 | 26 | 27 | destinationuid 28 | EFC3FC25-36AC-43BA-8054-038DDAD82831 29 | modifiers 30 | 0 31 | modifiersubtext 32 | 33 | vitoclose 34 | 35 | 36 | 37 | 11295A3D-C960-41A3-9670-DFCBDF58C9D9 38 | 39 | 40 | destinationuid 41 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 42 | modifiers 43 | 524288 44 | modifiersubtext 45 | Search Query in Bear App 46 | vitoclose 47 | 48 | 49 | 50 | destinationuid 51 | 6CEE5F7C-4415-417F-A095-4CE906185A93 52 | modifiers 53 | 0 54 | modifiersubtext 55 | 56 | vitoclose 57 | 58 | 59 | 60 | destinationuid 61 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 62 | modifiers 63 | 1048576 64 | modifiersubtext 65 | Open Note in Bear App 66 | vitoclose 67 | 68 | 69 | 70 | destinationuid 71 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 72 | modifiers 73 | 131072 74 | modifiersubtext 75 | Paste Wiki-Link to Note 76 | vitoclose 77 | 78 | 79 | 80 | destinationuid 81 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 82 | modifiers 83 | 1179648 84 | modifiersubtext 85 | Paste Link to Note 86 | vitoclose 87 | 88 | 89 | 90 | destinationuid 91 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 92 | modifiers 93 | 1572864 94 | modifiersubtext 95 | Paste Table of Contents 96 | vitoclose 97 | 98 | 99 | 100 | 1197E934-E010-48A8-AF8E-CF0CF301C261 101 | 102 | 103 | destinationuid 104 | 5EC53F91-D330-4140-B05A-96D7DCFCD135 105 | modifiers 106 | 0 107 | modifiersubtext 108 | 109 | vitoclose 110 | 111 | 112 | 113 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 114 | 115 | 116 | destinationuid 117 | A17A4C24-15DE-4D34-9704-76E6483B412C 118 | modifiers 119 | 0 120 | modifiersubtext 121 | 122 | vitoclose 123 | 124 | 125 | 126 | 23C93BAC-491B-42FF-8E63-F529CFCBCCD7 127 | 128 | 129 | destinationuid 130 | 00E5D1C8-D8AE-43EF-9E0D-92F25F59FE62 131 | modifiers 132 | 0 133 | modifiersubtext 134 | 135 | vitoclose 136 | 137 | 138 | 139 | 48FBADDF-A8D0-41D7-B180-469BA359C57F 140 | 141 | 142 | destinationuid 143 | 1E89EC6B-5E60-4AEE-800A-55A1E91B9182 144 | modifiers 145 | 1048576 146 | modifiersubtext 147 | Paste Clipboard Contents 148 | vitoclose 149 | 150 | 151 | 152 | destinationuid 153 | F652448D-56D0-4068-AAA0-9670D6771371 154 | modifiers 155 | 0 156 | modifiersubtext 157 | 158 | vitoclose 159 | 160 | 161 | 162 | 4D2DFA01-3886-42C0-82EE-5B0BE63B02D2 163 | 164 | 165 | destinationuid 166 | 4D5617AA-ADE7-45B5-9C05-DA6C69781662 167 | modifiers 168 | 0 169 | modifiersubtext 170 | 171 | vitoclose 172 | 173 | 174 | 175 | 4D5617AA-ADE7-45B5-9C05-DA6C69781662 176 | 177 | 178 | destinationuid 179 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 180 | modifiers 181 | 524288 182 | modifiersubtext 183 | Search Query in Bear App 184 | vitoclose 185 | 186 | 187 | 188 | destinationuid 189 | 6CEE5F7C-4415-417F-A095-4CE906185A93 190 | modifiers 191 | 0 192 | modifiersubtext 193 | 194 | vitoclose 195 | 196 | 197 | 198 | destinationuid 199 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 200 | modifiers 201 | 1048576 202 | modifiersubtext 203 | Open Note in Bear App 204 | vitoclose 205 | 206 | 207 | 208 | destinationuid 209 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 210 | modifiers 211 | 131072 212 | modifiersubtext 213 | Paste Wiki-Link to Note 214 | vitoclose 215 | 216 | 217 | 218 | destinationuid 219 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 220 | modifiers 221 | 1179648 222 | modifiersubtext 223 | Paste Link to Note 224 | vitoclose 225 | 226 | 227 | 228 | destinationuid 229 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 230 | modifiers 231 | 1572864 232 | modifiersubtext 233 | Paste Table of Contents 234 | vitoclose 235 | 236 | 237 | 238 | 59A4C675-2898-4D74-AA9C-C3F3E4A96A0A 239 | 240 | 241 | destinationuid 242 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 243 | modifiers 244 | 524288 245 | modifiersubtext 246 | Search Query in Bear App 247 | vitoclose 248 | 249 | 250 | 251 | destinationuid 252 | 6CEE5F7C-4415-417F-A095-4CE906185A93 253 | modifiers 254 | 0 255 | modifiersubtext 256 | 257 | vitoclose 258 | 259 | 260 | 261 | destinationuid 262 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 263 | modifiers 264 | 1048576 265 | modifiersubtext 266 | Open Note in Bear App 267 | vitoclose 268 | 269 | 270 | 271 | destinationuid 272 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 273 | modifiers 274 | 131072 275 | modifiersubtext 276 | Paste Wiki-Link to Note 277 | vitoclose 278 | 279 | 280 | 281 | destinationuid 282 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 283 | modifiers 284 | 1179648 285 | modifiersubtext 286 | Paste Link to Note 287 | vitoclose 288 | 289 | 290 | 291 | destinationuid 292 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 293 | modifiers 294 | 1572864 295 | modifiersubtext 296 | Paste Table of Contents 297 | vitoclose 298 | 299 | 300 | 301 | 5D8E2482-13A9-4FCB-96CA-C66F35949E2A 302 | 303 | 304 | destinationuid 305 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 306 | modifiers 307 | 524288 308 | modifiersubtext 309 | Search Query in Bear App 310 | vitoclose 311 | 312 | 313 | 314 | destinationuid 315 | 6CEE5F7C-4415-417F-A095-4CE906185A93 316 | modifiers 317 | 0 318 | modifiersubtext 319 | 320 | vitoclose 321 | 322 | 323 | 324 | destinationuid 325 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 326 | modifiers 327 | 1048576 328 | modifiersubtext 329 | Open Note in Bear App 330 | vitoclose 331 | 332 | 333 | 334 | destinationuid 335 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 336 | modifiers 337 | 131072 338 | modifiersubtext 339 | Paste Wiki-Link to Note 340 | vitoclose 341 | 342 | 343 | 344 | destinationuid 345 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 346 | modifiers 347 | 1179648 348 | modifiersubtext 349 | Paste Link to Note 350 | vitoclose 351 | 352 | 353 | 354 | destinationuid 355 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 356 | modifiers 357 | 1572864 358 | modifiersubtext 359 | Paste Table of Contents 360 | vitoclose 361 | 362 | 363 | 364 | 5EC53F91-D330-4140-B05A-96D7DCFCD135 365 | 366 | 367 | destinationuid 368 | 27456FAE-DBC0-4A91-9753-517B00831DB3 369 | modifiers 370 | 0 371 | modifiersubtext 372 | 373 | vitoclose 374 | 375 | 376 | 377 | 6CEE5F7C-4415-417F-A095-4CE906185A93 378 | 379 | 380 | destinationuid 381 | F652448D-56D0-4068-AAA0-9670D6771371 382 | modifiers 383 | 0 384 | modifiersubtext 385 | 386 | sourceoutputuid 387 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 388 | vitoclose 389 | 390 | 391 | 392 | destinationuid 393 | EFC3FC25-36AC-43BA-8054-038DDAD82831 394 | modifiers 395 | 0 396 | modifiersubtext 397 | 398 | sourceoutputuid 399 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 400 | vitoclose 401 | 402 | 403 | 404 | destinationuid 405 | EE68D5DB-1C41-45BB-94EF-267F95F56024 406 | modifiers 407 | 0 408 | modifiersubtext 409 | 410 | vitoclose 411 | 412 | 413 | 414 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 415 | 416 | 417 | destinationuid 418 | 23C93BAC-491B-42FF-8E63-F529CFCBCCD7 419 | modifiers 420 | 0 421 | modifiersubtext 422 | 423 | vitoclose 424 | 425 | 426 | 427 | 954F3962-1F0B-42A0-9C03-127E2690FA49 428 | 429 | 430 | destinationuid 431 | 9E833A18-9A79-45A4-808C-D1891E6147CE 432 | modifiers 433 | 0 434 | modifiersubtext 435 | 436 | vitoclose 437 | 438 | 439 | 440 | 9E833A18-9A79-45A4-808C-D1891E6147CE 441 | 442 | 443 | destinationuid 444 | 7936E4EC-9DD4-4C26-BFFD-9A300B4DC3D4 445 | modifiers 446 | 0 447 | modifiersubtext 448 | 449 | vitoclose 450 | 451 | 452 | 453 | A17A4C24-15DE-4D34-9704-76E6483B412C 454 | 455 | 456 | destinationuid 457 | B5A664D0-6B7A-4F14-9967-A9D90AA32295 458 | modifiers 459 | 0 460 | modifiersubtext 461 | 462 | vitoclose 463 | 464 | 465 | 466 | A6BFE29A-BCCF-476F-8E07-A7C9CA647422 467 | 468 | 469 | destinationuid 470 | 2B3D525C-64BF-4146-92F4-A7012F05F6A2 471 | modifiers 472 | 0 473 | modifiersubtext 474 | 475 | vitoclose 476 | 477 | 478 | 479 | B39E12A9-4E49-45C4-AE46-F56DEBD40B39 480 | 481 | 482 | destinationuid 483 | B0876656-9842-4ED2-A972-BD955ED9E68A 484 | modifiers 485 | 0 486 | modifiersubtext 487 | 488 | vitoclose 489 | 490 | 491 | 492 | B5A664D0-6B7A-4F14-9967-A9D90AA32295 493 | 494 | 495 | destinationuid 496 | DA7313D4-6521-4C7B-B408-903CF0C760FB 497 | modifiers 498 | 0 499 | modifiersubtext 500 | 501 | vitoclose 502 | 503 | 504 | 505 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 506 | 507 | 508 | destinationuid 509 | B39E12A9-4E49-45C4-AE46-F56DEBD40B39 510 | modifiers 511 | 0 512 | modifiersubtext 513 | 514 | sourceoutputuid 515 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 516 | vitoclose 517 | 518 | 519 | 520 | destinationuid 521 | EFC3FC25-36AC-43BA-8054-038DDAD82831 522 | modifiers 523 | 0 524 | modifiersubtext 525 | 526 | sourceoutputuid 527 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 528 | vitoclose 529 | 530 | 531 | 532 | destinationuid 533 | 954F3962-1F0B-42A0-9C03-127E2690FA49 534 | modifiers 535 | 0 536 | modifiersubtext 537 | 538 | vitoclose 539 | 540 | 541 | 542 | C3AB4283-78DC-43E8-A8E5-BAB521E3E48D 543 | 544 | 545 | destinationuid 546 | 1E89EC6B-5E60-4AEE-800A-55A1E91B9182 547 | modifiers 548 | 1048576 549 | modifiersubtext 550 | Paste Clipboard Contents 551 | vitoclose 552 | 553 | 554 | 555 | destinationuid 556 | F652448D-56D0-4068-AAA0-9670D6771371 557 | modifiers 558 | 0 559 | modifiersubtext 560 | 561 | vitoclose 562 | 563 | 564 | 565 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 566 | 567 | 568 | destinationuid 569 | 1197E934-E010-48A8-AF8E-CF0CF301C261 570 | modifiers 571 | 0 572 | modifiersubtext 573 | 574 | vitoclose 575 | 576 | 577 | 578 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 579 | 580 | 581 | destinationuid 582 | B39E12A9-4E49-45C4-AE46-F56DEBD40B39 583 | modifiers 584 | 0 585 | modifiersubtext 586 | 587 | sourceoutputuid 588 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 589 | vitoclose 590 | 591 | 592 | 593 | destinationuid 594 | 0344727C-3BE1-4BC7-887A-82BECFC34B13 595 | modifiers 596 | 0 597 | modifiersubtext 598 | 599 | vitoclose 600 | 601 | 602 | 603 | destinationuid 604 | EFC3FC25-36AC-43BA-8054-038DDAD82831 605 | modifiers 606 | 0 607 | modifiersubtext 608 | 609 | sourceoutputuid 610 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 611 | vitoclose 612 | 613 | 614 | 615 | EE68D5DB-1C41-45BB-94EF-267F95F56024 616 | 617 | 618 | destinationuid 619 | A6BFE29A-BCCF-476F-8E07-A7C9CA647422 620 | modifiers 621 | 0 622 | modifiersubtext 623 | 624 | vitoclose 625 | 626 | 627 | 628 | F652448D-56D0-4068-AAA0-9670D6771371 629 | 630 | 631 | destinationuid 632 | 1E89EC6B-5E60-4AEE-800A-55A1E91B9182 633 | modifiers 634 | 0 635 | modifiersubtext 636 | 637 | vitoclose 638 | 639 | 640 | 641 | FED949AC-9EA3-45BB-90ED-8309D3BFB323 642 | 643 | 644 | destinationuid 645 | 5D8E2482-13A9-4FCB-96CA-C66F35949E2A 646 | modifiers 647 | 0 648 | modifiersubtext 649 | 650 | vitoclose 651 | 652 | 653 | 654 | 655 | createdby 656 | drgrib 657 | description 658 | Streamlined note searching and creation for Bear using Alfred 659 | disabled 660 | 661 | name 662 | Bear 663 | objects 664 | 665 | 666 | config 667 | 668 | alfredfiltersresults 669 | 670 | alfredfiltersresultsmatchmode 671 | 0 672 | argumenttreatemptyqueryasnil 673 | 674 | argumenttrimmode 675 | 0 676 | argumenttype 677 | 0 678 | escaping 679 | 102 680 | keyword 681 | bn 682 | queuedelaycustom 683 | 3 684 | queuedelayimmediatelyinitially 685 | 686 | queuedelaymode 687 | 0 688 | queuemode 689 | 1 690 | runningsubtext 691 | Creating... 692 | script 693 | cmd/create/create "{query}" 694 | scriptargtype 695 | 0 696 | scriptfile 697 | 698 | subtext 699 | 700 | title 701 | Create New Bear Note 702 | type 703 | 0 704 | withspace 705 | 706 | 707 | type 708 | alfred.workflow.input.scriptfilter 709 | uid 710 | 48FBADDF-A8D0-41D7-B180-469BA359C57F 711 | version 712 | 3 713 | 714 | 715 | config 716 | 717 | concurrently 718 | 719 | escaping 720 | 102 721 | script 722 | open "bear://x-callback-url/create?{query}&show_window=yes&new_window=yes&edit=yes" 723 | scriptargtype 724 | 0 725 | scriptfile 726 | 727 | type 728 | 0 729 | 730 | type 731 | alfred.workflow.action.script 732 | uid 733 | 1E89EC6B-5E60-4AEE-800A-55A1E91B9182 734 | version 735 | 2 736 | 737 | 738 | config 739 | 740 | action 741 | 0 742 | argument 743 | 0 744 | focusedappvariable 745 | 746 | focusedappvariablename 747 | 748 | hotkey 749 | 0 750 | hotmod 751 | 0 752 | hotstring 753 | 754 | leftcursor 755 | 756 | modsmode 757 | 0 758 | relatedAppsMode 759 | 0 760 | 761 | type 762 | alfred.workflow.trigger.hotkey 763 | uid 764 | FED949AC-9EA3-45BB-90ED-8309D3BFB323 765 | version 766 | 2 767 | 768 | 769 | config 770 | 771 | alfredfiltersresults 772 | 773 | alfredfiltersresultsmatchmode 774 | 0 775 | argumenttreatemptyqueryasnil 776 | 777 | argumenttrimmode 778 | 0 779 | argumenttype 780 | 0 781 | escaping 782 | 102 783 | keyword 784 | bnew 785 | queuedelaycustom 786 | 3 787 | queuedelayimmediatelyinitially 788 | 789 | queuedelaymode 790 | 0 791 | queuemode 792 | 1 793 | runningsubtext 794 | Creating... 795 | script 796 | cmd/create/create "{query}" 797 | scriptargtype 798 | 0 799 | scriptfile 800 | 801 | subtext 802 | 803 | title 804 | Create New Bear Note 805 | type 806 | 0 807 | withspace 808 | 809 | 810 | type 811 | alfred.workflow.input.scriptfilter 812 | uid 813 | C3AB4283-78DC-43E8-A8E5-BAB521E3E48D 814 | version 815 | 3 816 | 817 | 818 | config 819 | 820 | concurrently 821 | 822 | escaping 823 | 102 824 | script 825 | open "bear://x-callback-url/create?{query}&show_window=yes&new_window=no&edit=yes" 826 | scriptargtype 827 | 0 828 | scriptfile 829 | 830 | type 831 | 0 832 | 833 | type 834 | alfred.workflow.action.script 835 | uid 836 | B0876656-9842-4ED2-A972-BD955ED9E68A 837 | version 838 | 2 839 | 840 | 841 | config 842 | 843 | matchmode 844 | 1 845 | matchstring 846 | &text=.+ 847 | regexcaseinsensitive 848 | 849 | regexmultiline 850 | 851 | replacestring 852 | 853 | 854 | type 855 | alfred.workflow.utility.replace 856 | uid 857 | F652448D-56D0-4068-AAA0-9670D6771371 858 | version 859 | 2 860 | 861 | 862 | config 863 | 864 | alfredfiltersresults 865 | 866 | alfredfiltersresultsmatchmode 867 | 0 868 | argumenttreatemptyqueryasnil 869 | 870 | argumenttrimmode 871 | 0 872 | argumenttype 873 | 1 874 | escaping 875 | 102 876 | keyword 877 | bcs 878 | queuedelaycustom 879 | 3 880 | queuedelayimmediatelyinitially 881 | 882 | queuedelaymode 883 | 0 884 | queuemode 885 | 1 886 | runningsubtext 887 | Searching... 888 | script 889 | cmd/csearch/csearch "$1" 890 | scriptargtype 891 | 1 892 | scriptfile 893 | 894 | subtext 895 | 896 | title 897 | Create/Search Bear Notes 898 | type 899 | 0 900 | withspace 901 | 902 | 903 | type 904 | alfred.workflow.input.scriptfilter 905 | uid 906 | 5D8E2482-13A9-4FCB-96CA-C66F35949E2A 907 | version 908 | 3 909 | 910 | 911 | config 912 | 913 | conditions 914 | 915 | 916 | inputstring 917 | 918 | matchcasesensitive 919 | 920 | matchmode 921 | 4 922 | matchstring 923 | ^(title=|tags=|text=) 924 | outputlabel 925 | CreateItem 926 | uid 927 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 928 | 929 | 930 | inputstring 931 | 932 | matchcasesensitive 933 | 934 | matchmode 935 | 4 936 | matchstring 937 | ^(term=|tag=)|^$ 938 | outputlabel 939 | AppSearchItem 940 | uid 941 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 942 | 943 | 944 | elselabel 945 | NoteID|searchCallback 946 | hideelse 947 | 948 | 949 | type 950 | alfred.workflow.utility.conditional 951 | uid 952 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 953 | version 954 | 1 955 | 956 | 957 | config 958 | 959 | matchmode 960 | 1 961 | matchstring 962 | &text=.+ 963 | regexcaseinsensitive 964 | 965 | regexmultiline 966 | 967 | replacestring 968 | 969 | 970 | type 971 | alfred.workflow.utility.replace 972 | uid 973 | B39E12A9-4E49-45C4-AE46-F56DEBD40B39 974 | version 975 | 2 976 | 977 | 978 | config 979 | 980 | matchmode 981 | 1 982 | matchstring 983 | ([^\|]+)\|([^\|]*) 984 | regexcaseinsensitive 985 | 986 | regexmultiline 987 | 988 | replacestring 989 | $2 990 | 991 | type 992 | alfred.workflow.utility.replace 993 | uid 994 | 0344727C-3BE1-4BC7-887A-82BECFC34B13 995 | version 996 | 2 997 | 998 | 999 | config 1000 | 1001 | concurrently 1002 | 1003 | escaping 1004 | 102 1005 | script 1006 | open "bear://x-callback-url/search?{query}" 1007 | scriptargtype 1008 | 0 1009 | scriptfile 1010 | 1011 | type 1012 | 0 1013 | 1014 | type 1015 | alfred.workflow.action.script 1016 | uid 1017 | EFC3FC25-36AC-43BA-8054-038DDAD82831 1018 | version 1019 | 2 1020 | 1021 | 1022 | config 1023 | 1024 | alfredfiltersresults 1025 | 1026 | alfredfiltersresultsmatchmode 1027 | 0 1028 | argumenttreatemptyqueryasnil 1029 | 1030 | argumenttrimmode 1031 | 0 1032 | argumenttype 1033 | 1 1034 | escaping 1035 | 102 1036 | keyword 1037 | bcsearch 1038 | queuedelaycustom 1039 | 3 1040 | queuedelayimmediatelyinitially 1041 | 1042 | queuedelaymode 1043 | 0 1044 | queuemode 1045 | 1 1046 | runningsubtext 1047 | Searching... 1048 | script 1049 | cmd/csearch/csearch "{query}" 1050 | scriptargtype 1051 | 0 1052 | scriptfile 1053 | 1054 | subtext 1055 | 1056 | title 1057 | Create/Search Bear Notes 1058 | type 1059 | 0 1060 | withspace 1061 | 1062 | 1063 | type 1064 | alfred.workflow.input.scriptfilter 1065 | uid 1066 | 59A4C675-2898-4D74-AA9C-C3F3E4A96A0A 1067 | version 1068 | 3 1069 | 1070 | 1071 | config 1072 | 1073 | conditions 1074 | 1075 | 1076 | inputstring 1077 | 1078 | matchcasesensitive 1079 | 1080 | matchmode 1081 | 4 1082 | matchstring 1083 | ^(title=|tags=|text=) 1084 | outputlabel 1085 | CreateItem 1086 | uid 1087 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 1088 | 1089 | 1090 | inputstring 1091 | 1092 | matchcasesensitive 1093 | 1094 | matchmode 1095 | 4 1096 | matchstring 1097 | ^(term=|tag=)|^$ 1098 | outputlabel 1099 | AppSearchItem 1100 | uid 1101 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 1102 | 1103 | 1104 | elselabel 1105 | NoteID|searchCallback 1106 | hideelse 1107 | 1108 | 1109 | type 1110 | alfred.workflow.utility.conditional 1111 | uid 1112 | 6CEE5F7C-4415-417F-A095-4CE906185A93 1113 | version 1114 | 1 1115 | 1116 | 1117 | config 1118 | 1119 | conditions 1120 | 1121 | 1122 | inputstring 1123 | 1124 | matchcasesensitive 1125 | 1126 | matchmode 1127 | 4 1128 | matchstring 1129 | ^(title=|tags=|text=) 1130 | outputlabel 1131 | CreateItem 1132 | uid 1133 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 1134 | 1135 | 1136 | inputstring 1137 | 1138 | matchcasesensitive 1139 | 1140 | matchmode 1141 | 4 1142 | matchstring 1143 | ^(term=|tag=)|^$ 1144 | outputlabel 1145 | AppSearchItem 1146 | uid 1147 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 1148 | 1149 | 1150 | elselabel 1151 | NoteID|searchCallback 1152 | hideelse 1153 | 1154 | 1155 | type 1156 | alfred.workflow.utility.conditional 1157 | uid 1158 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 1159 | version 1160 | 1 1161 | 1162 | 1163 | config 1164 | 1165 | concurrently 1166 | 1167 | escaping 1168 | 102 1169 | script 1170 | cmd/setcursor/setcursor 1171 | scriptargtype 1172 | 1 1173 | scriptfile 1174 | 1175 | type 1176 | 0 1177 | 1178 | type 1179 | alfred.workflow.action.script 1180 | uid 1181 | 2B3D525C-64BF-4146-92F4-A7012F05F6A2 1182 | version 1183 | 2 1184 | 1185 | 1186 | config 1187 | 1188 | concurrently 1189 | 1190 | escaping 1191 | 102 1192 | script 1193 | open "bear://x-callback-url/open-note?id={query}&show_window=yes&new_window=yes&edit=yes" 1194 | scriptargtype 1195 | 0 1196 | scriptfile 1197 | 1198 | type 1199 | 0 1200 | 1201 | type 1202 | alfred.workflow.action.script 1203 | uid 1204 | A6BFE29A-BCCF-476F-8E07-A7C9CA647422 1205 | version 1206 | 2 1207 | 1208 | 1209 | config 1210 | 1211 | matchmode 1212 | 1 1213 | matchstring 1214 | ([^\|]+)\|([^\|]*) 1215 | regexcaseinsensitive 1216 | 1217 | regexmultiline 1218 | 1219 | replacestring 1220 | $1 1221 | 1222 | type 1223 | alfred.workflow.utility.replace 1224 | uid 1225 | EE68D5DB-1C41-45BB-94EF-267F95F56024 1226 | version 1227 | 2 1228 | 1229 | 1230 | config 1231 | 1232 | concurrently 1233 | 1234 | escaping 1235 | 102 1236 | script 1237 | open "bear://x-callback-url/open-note?id={query}&show_window=yes&new_window=no&edit=yes" 1238 | scriptargtype 1239 | 0 1240 | scriptfile 1241 | 1242 | type 1243 | 0 1244 | 1245 | type 1246 | alfred.workflow.action.script 1247 | uid 1248 | 9E833A18-9A79-45A4-808C-D1891E6147CE 1249 | version 1250 | 2 1251 | 1252 | 1253 | config 1254 | 1255 | concurrently 1256 | 1257 | escaping 1258 | 102 1259 | script 1260 | cmd/setcursor/setcursor 1261 | scriptargtype 1262 | 1 1263 | scriptfile 1264 | 1265 | type 1266 | 0 1267 | 1268 | type 1269 | alfred.workflow.action.script 1270 | uid 1271 | 7936E4EC-9DD4-4C26-BFFD-9A300B4DC3D4 1272 | version 1273 | 2 1274 | 1275 | 1276 | config 1277 | 1278 | matchmode 1279 | 1 1280 | matchstring 1281 | ([^\|]+)\|([^\|]*) 1282 | regexcaseinsensitive 1283 | 1284 | regexmultiline 1285 | 1286 | replacestring 1287 | $1 1288 | 1289 | type 1290 | alfred.workflow.utility.replace 1291 | uid 1292 | 954F3962-1F0B-42A0-9C03-127E2690FA49 1293 | version 1294 | 2 1295 | 1296 | 1297 | config 1298 | 1299 | alfredfiltersresults 1300 | 1301 | alfredfiltersresultsmatchmode 1302 | 0 1303 | argumenttreatemptyqueryasnil 1304 | 1305 | argumenttrimmode 1306 | 0 1307 | argumenttype 1308 | 1 1309 | escaping 1310 | 102 1311 | keyword 1312 | bs 1313 | queuedelaycustom 1314 | 3 1315 | queuedelayimmediatelyinitially 1316 | 1317 | queuedelaymode 1318 | 0 1319 | queuemode 1320 | 1 1321 | runningsubtext 1322 | Searching... 1323 | script 1324 | cmd/search/search "$1" 1325 | scriptargtype 1326 | 1 1327 | scriptfile 1328 | 1329 | subtext 1330 | 1331 | title 1332 | Search Bear Notes 1333 | type 1334 | 0 1335 | withspace 1336 | 1337 | 1338 | type 1339 | alfred.workflow.input.scriptfilter 1340 | uid 1341 | 11295A3D-C960-41A3-9670-DFCBDF58C9D9 1342 | version 1343 | 3 1344 | 1345 | 1346 | config 1347 | 1348 | availableviaurlhandler 1349 | 1350 | triggerid 1351 | search bear 1352 | 1353 | type 1354 | alfred.workflow.trigger.external 1355 | uid 1356 | 4D2DFA01-3886-42C0-82EE-5B0BE63B02D2 1357 | version 1358 | 1 1359 | 1360 | 1361 | config 1362 | 1363 | alfredfiltersresults 1364 | 1365 | alfredfiltersresultsmatchmode 1366 | 0 1367 | argumenttreatemptyqueryasnil 1368 | 1369 | argumenttrimmode 1370 | 0 1371 | argumenttype 1372 | 1 1373 | escaping 1374 | 102 1375 | keyword 1376 | bsearch 1377 | queuedelaycustom 1378 | 3 1379 | queuedelayimmediatelyinitially 1380 | 1381 | queuedelaymode 1382 | 0 1383 | queuemode 1384 | 1 1385 | runningsubtext 1386 | Searching... 1387 | script 1388 | cmd/search/search "{query}" 1389 | scriptargtype 1390 | 0 1391 | scriptfile 1392 | 1393 | subtext 1394 | 1395 | title 1396 | Search Bear Notes 1397 | type 1398 | 0 1399 | withspace 1400 | 1401 | 1402 | type 1403 | alfred.workflow.input.scriptfilter 1404 | uid 1405 | 4D5617AA-ADE7-45B5-9C05-DA6C69781662 1406 | version 1407 | 3 1408 | 1409 | 1410 | config 1411 | 1412 | conditions 1413 | 1414 | 1415 | inputstring 1416 | 1417 | matchcasesensitive 1418 | 1419 | matchmode 1420 | 4 1421 | matchstring 1422 | ^(title=|tags=|text=) 1423 | outputlabel 1424 | CreateItem 1425 | uid 1426 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 1427 | 1428 | 1429 | inputstring 1430 | 1431 | matchcasesensitive 1432 | 1433 | matchmode 1434 | 4 1435 | matchstring 1436 | ^(term=|tag=)|^$ 1437 | outputlabel 1438 | AppSearchItem 1439 | uid 1440 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 1441 | 1442 | 1443 | elselabel 1444 | NoteID|searchCallback 1445 | hideelse 1446 | 1447 | 1448 | type 1449 | alfred.workflow.utility.conditional 1450 | uid 1451 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 1452 | version 1453 | 1 1454 | 1455 | 1456 | config 1457 | 1458 | autopaste 1459 | 1460 | clipboardtext 1461 | {query} 1462 | ignoredynamicplaceholders 1463 | 1464 | transient 1465 | 1466 | 1467 | type 1468 | alfred.workflow.output.clipboard 1469 | uid 1470 | E92972B8-8164-49C7-BBA8-064C3B42EC29 1471 | version 1472 | 3 1473 | 1474 | 1475 | config 1476 | 1477 | concurrently 1478 | 1479 | escaping 1480 | 102 1481 | script 1482 | cmd/wiki-link/wiki-link "{query}" 1483 | scriptargtype 1484 | 0 1485 | scriptfile 1486 | 1487 | type 1488 | 0 1489 | 1490 | type 1491 | alfred.workflow.action.script 1492 | uid 1493 | 00E5D1C8-D8AE-43EF-9E0D-92F25F59FE62 1494 | version 1495 | 2 1496 | 1497 | 1498 | config 1499 | 1500 | matchmode 1501 | 1 1502 | matchstring 1503 | ([^\|]+)\|([^\|]*) 1504 | regexcaseinsensitive 1505 | 1506 | regexmultiline 1507 | 1508 | replacestring 1509 | $1 1510 | 1511 | type 1512 | alfred.workflow.utility.replace 1513 | uid 1514 | 23C93BAC-491B-42FF-8E63-F529CFCBCCD7 1515 | version 1516 | 2 1517 | 1518 | 1519 | config 1520 | 1521 | conditions 1522 | 1523 | 1524 | inputstring 1525 | 1526 | matchcasesensitive 1527 | 1528 | matchmode 1529 | 4 1530 | matchstring 1531 | ^(title=|tags=|text=) 1532 | outputlabel 1533 | CreateItem 1534 | uid 1535 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 1536 | 1537 | 1538 | inputstring 1539 | 1540 | matchcasesensitive 1541 | 1542 | matchmode 1543 | 4 1544 | matchstring 1545 | ^(term=|tag=)|^$ 1546 | outputlabel 1547 | AppSearchItem 1548 | uid 1549 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 1550 | 1551 | 1552 | elselabel 1553 | NoteID|searchCallback 1554 | hideelse 1555 | 1556 | 1557 | type 1558 | alfred.workflow.utility.conditional 1559 | uid 1560 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 1561 | version 1562 | 1 1563 | 1564 | 1565 | config 1566 | 1567 | concurrently 1568 | 1569 | escaping 1570 | 102 1571 | script 1572 | cmd/link/link "{query}" 1573 | scriptargtype 1574 | 0 1575 | scriptfile 1576 | 1577 | type 1578 | 0 1579 | 1580 | type 1581 | alfred.workflow.action.script 1582 | uid 1583 | 5EC53F91-D330-4140-B05A-96D7DCFCD135 1584 | version 1585 | 2 1586 | 1587 | 1588 | config 1589 | 1590 | autopaste 1591 | 1592 | clipboardtext 1593 | {query} 1594 | ignoredynamicplaceholders 1595 | 1596 | transient 1597 | 1598 | 1599 | type 1600 | alfred.workflow.output.clipboard 1601 | uid 1602 | 27456FAE-DBC0-4A91-9753-517B00831DB3 1603 | version 1604 | 3 1605 | 1606 | 1607 | config 1608 | 1609 | matchmode 1610 | 1 1611 | matchstring 1612 | ([^\|]+)\|([^\|]*) 1613 | regexcaseinsensitive 1614 | 1615 | regexmultiline 1616 | 1617 | replacestring 1618 | $1 1619 | 1620 | type 1621 | alfred.workflow.utility.replace 1622 | uid 1623 | 1197E934-E010-48A8-AF8E-CF0CF301C261 1624 | version 1625 | 2 1626 | 1627 | 1628 | config 1629 | 1630 | conditions 1631 | 1632 | 1633 | inputstring 1634 | 1635 | matchcasesensitive 1636 | 1637 | matchmode 1638 | 4 1639 | matchstring 1640 | ^(title=|tags=|text=) 1641 | outputlabel 1642 | CreateItem 1643 | uid 1644 | 296ECF8E-6E4D-49DA-9B92-9883C5F5605A 1645 | 1646 | 1647 | inputstring 1648 | 1649 | matchcasesensitive 1650 | 1651 | matchmode 1652 | 4 1653 | matchstring 1654 | ^(term=|tag=)|^$ 1655 | outputlabel 1656 | AppSearchItem 1657 | uid 1658 | FDEE12A9-8486-48CB-B48E-6893D573BBD7 1659 | 1660 | 1661 | elselabel 1662 | NoteID|searchCallback 1663 | hideelse 1664 | 1665 | 1666 | type 1667 | alfred.workflow.utility.conditional 1668 | uid 1669 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 1670 | version 1671 | 1 1672 | 1673 | 1674 | config 1675 | 1676 | concurrently 1677 | 1678 | escaping 1679 | 102 1680 | script 1681 | cmd/toc/toc "{query}" 1682 | scriptargtype 1683 | 0 1684 | scriptfile 1685 | 1686 | type 1687 | 0 1688 | 1689 | type 1690 | alfred.workflow.action.script 1691 | uid 1692 | B5A664D0-6B7A-4F14-9967-A9D90AA32295 1693 | version 1694 | 2 1695 | 1696 | 1697 | config 1698 | 1699 | autopaste 1700 | 1701 | clipboardtext 1702 | {query} 1703 | ignoredynamicplaceholders 1704 | 1705 | transient 1706 | 1707 | 1708 | type 1709 | alfred.workflow.output.clipboard 1710 | uid 1711 | DA7313D4-6521-4C7B-B408-903CF0C760FB 1712 | version 1713 | 3 1714 | 1715 | 1716 | config 1717 | 1718 | matchmode 1719 | 1 1720 | matchstring 1721 | ([^\|]+)\|([^\|]*) 1722 | regexcaseinsensitive 1723 | 1724 | regexmultiline 1725 | 1726 | replacestring 1727 | $1 1728 | 1729 | type 1730 | alfred.workflow.utility.replace 1731 | uid 1732 | A17A4C24-15DE-4D34-9704-76E6483B412C 1733 | version 1734 | 2 1735 | 1736 | 1737 | readme 1738 | 1739 | uidata 1740 | 1741 | 00E5D1C8-D8AE-43EF-9E0D-92F25F59FE62 1742 | 1743 | note 1744 | Format Link 1745 | xpos 1746 | 1180 1747 | ypos 1748 | 1050 1749 | 1750 | 0344727C-3BE1-4BC7-887A-82BECFC34B13 1751 | 1752 | xpos 1753 | 1095 1754 | ypos 1755 | 435 1756 | 1757 | 11295A3D-C960-41A3-9670-DFCBDF58C9D9 1758 | 1759 | xpos 1760 | 170 1761 | ypos 1762 | 880 1763 | 1764 | 1197E934-E010-48A8-AF8E-CF0CF301C261 1765 | 1766 | xpos 1767 | 1090 1768 | ypos 1769 | 1235 1770 | 1771 | 1E42F790-0DFA-452B-A0F5-5FEA0ACA22E5 1772 | 1773 | note 1774 | Paste Link to Table of Contents 1775 | xpos 1776 | 795 1777 | ypos 1778 | 1350 1779 | 1780 | 1E89EC6B-5E60-4AEE-800A-55A1E91B9182 1781 | 1782 | note 1783 | Create Note 1784 | xpos 1785 | 1380 1786 | ypos 1787 | 35 1788 | 1789 | 23C93BAC-491B-42FF-8E63-F529CFCBCCD7 1790 | 1791 | xpos 1792 | 1090 1793 | ypos 1794 | 1080 1795 | 1796 | 27456FAE-DBC0-4A91-9753-517B00831DB3 1797 | 1798 | note 1799 | Paste Link 1800 | xpos 1801 | 1335 1802 | ypos 1803 | 1205 1804 | 1805 | 2B3D525C-64BF-4146-92F4-A7012F05F6A2 1806 | 1807 | note 1808 | Set Cursor 1809 | xpos 1810 | 1380 1811 | ypos 1812 | 635 1813 | 1814 | 48FBADDF-A8D0-41D7-B180-469BA359C57F 1815 | 1816 | xpos 1817 | 355 1818 | ypos 1819 | 35 1820 | 1821 | 4D2DFA01-3886-42C0-82EE-5B0BE63B02D2 1822 | 1823 | xpos 1824 | 30 1825 | ypos 1826 | 1015 1827 | 1828 | 4D5617AA-ADE7-45B5-9C05-DA6C69781662 1829 | 1830 | xpos 1831 | 170 1832 | ypos 1833 | 1015 1834 | 1835 | 59A4C675-2898-4D74-AA9C-C3F3E4A96A0A 1836 | 1837 | xpos 1838 | 30 1839 | ypos 1840 | 450 1841 | 1842 | 5D8E2482-13A9-4FCB-96CA-C66F35949E2A 1843 | 1844 | xpos 1845 | 30 1846 | ypos 1847 | 315 1848 | 1849 | 5EC53F91-D330-4140-B05A-96D7DCFCD135 1850 | 1851 | note 1852 | Format Link 1853 | xpos 1854 | 1180 1855 | ypos 1856 | 1205 1857 | 1858 | 6CEE5F7C-4415-417F-A095-4CE906185A93 1859 | 1860 | note 1861 | Open Note in New Window 1862 | xpos 1863 | 810 1864 | ypos 1865 | 455 1866 | 1867 | 7936E4EC-9DD4-4C26-BFFD-9A300B4DC3D4 1868 | 1869 | note 1870 | Set Cursor 1871 | xpos 1872 | 1380 1873 | ypos 1874 | 805 1875 | 1876 | 905FC2FC-858B-476F-9DBA-D143CD446DFC 1877 | 1878 | note 1879 | Paste Wiki-Link to Note 1880 | xpos 1881 | 795 1882 | ypos 1883 | 1025 1884 | 1885 | 954F3962-1F0B-42A0-9C03-127E2690FA49 1886 | 1887 | xpos 1888 | 1105 1889 | ypos 1890 | 835 1891 | 1892 | 9E833A18-9A79-45A4-808C-D1891E6147CE 1893 | 1894 | note 1895 | Open Note by ID in App 1896 | xpos 1897 | 1190 1898 | ypos 1899 | 805 1900 | 1901 | A17A4C24-15DE-4D34-9704-76E6483B412C 1902 | 1903 | xpos 1904 | 1090 1905 | ypos 1906 | 1405 1907 | 1908 | A6BFE29A-BCCF-476F-8E07-A7C9CA647422 1909 | 1910 | note 1911 | Open Note by ID 1912 | xpos 1913 | 1190 1914 | ypos 1915 | 635 1916 | 1917 | B0876656-9842-4ED2-A972-BD955ED9E68A 1918 | 1919 | note 1920 | Create Note in App 1921 | xpos 1922 | 1380 1923 | ypos 1924 | 185 1925 | 1926 | B39E12A9-4E49-45C4-AE46-F56DEBD40B39 1927 | 1928 | xpos 1929 | 1195 1930 | ypos 1931 | 320 1932 | 1933 | B5A664D0-6B7A-4F14-9967-A9D90AA32295 1934 | 1935 | note 1936 | ToC 1937 | xpos 1938 | 1180 1939 | ypos 1940 | 1375 1941 | 1942 | C09F10FC-E57F-47DA-9C55-84E9A68D2348 1943 | 1944 | note 1945 | Open Note in Bear App 1946 | xpos 1947 | 810 1948 | ypos 1949 | 610 1950 | 1951 | C3AB4283-78DC-43E8-A8E5-BAB521E3E48D 1952 | 1953 | xpos 1954 | 355 1955 | ypos 1956 | 170 1957 | 1958 | D3FAABE3-B891-4CF6-BD99-89994FAC5A36 1959 | 1960 | note 1961 | Paste Link to Note 1962 | xpos 1963 | 795 1964 | ypos 1965 | 1180 1966 | 1967 | DA7313D4-6521-4C7B-B408-903CF0C760FB 1968 | 1969 | note 1970 | Paste Link 1971 | xpos 1972 | 1335 1973 | ypos 1974 | 1375 1975 | 1976 | E2FCB7C2-A812-4419-9CD0-2E74282DF3FE 1977 | 1978 | note 1979 | Search Query in Bear App 1980 | xpos 1981 | 810 1982 | ypos 1983 | 315 1984 | 1985 | E92972B8-8164-49C7-BBA8-064C3B42EC29 1986 | 1987 | note 1988 | Paste Link 1989 | xpos 1990 | 1335 1991 | ypos 1992 | 1050 1993 | 1994 | EE68D5DB-1C41-45BB-94EF-267F95F56024 1995 | 1996 | xpos 1997 | 1105 1998 | ypos 1999 | 665 2000 | 2001 | EFC3FC25-36AC-43BA-8054-038DDAD82831 2002 | 2003 | note 2004 | Search in App 2005 | xpos 2006 | 1380 2007 | ypos 2008 | 450 2009 | 2010 | F652448D-56D0-4068-AAA0-9670D6771371 2011 | 2012 | xpos 2013 | 1130 2014 | ypos 2015 | 200 2016 | 2017 | FED949AC-9EA3-45BB-90ED-8309D3BFB323 2018 | 2019 | xpos 2020 | 30 2021 | ypos 2022 | 160 2023 | 2024 | 2025 | userconfigurationconfig 2026 | 2027 | variablesdontexport 2028 | 2029 | version 2030 | 1.2.3 2031 | webaddress 2032 | https://github.com/drgrib/alfred-bear 2033 | 2034 | 2035 | --------------------------------------------------------------------------------