├── .gitignore ├── Markdown Notes.alfredworkflow ├── README.md ├── _config.yml └── src ├── 01B01E1C-F114-4C29-9C0C-2881FA84A5D1.png ├── 5FA571C6-6183-44C9-B18F-2D4C73A0C48F.png ├── 661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4.png ├── 898E9FE0-5165-4530-98E9-9497DDF6DA0D.png ├── Alfred3.py ├── E6223AE8-EC41-40DA-88AC-8476D6F73ED1.png ├── F8CB74BA-4C13-4621-BC60-E50650C85A9D.png ├── MD Notes Help.md ├── MyNotes.py ├── QuerySplitter.py ├── asset_upload.py ├── create_index.py ├── create_note.py ├── delete_note.py ├── docs ├── bookmark_tag.md ├── default_date_format.md ├── default_template.md ├── exact_match.md ├── ext.md ├── filename_format.md ├── path_to_notes.md ├── search_yaml_tags_only.md ├── template_tag.md ├── todo_newest_oldest.md └── url_scheme.md ├── get_md_link.py ├── html_fetch.py ├── icon.png ├── icons ├── action.png ├── back.png ├── check.png ├── clipboard.png ├── colors.txt ├── delete.png ├── edit.png ├── hand.png ├── hashtag.png ├── link.png ├── markdown.png ├── marked.png ├── paste.png ├── question.png ├── scheme.png └── unchecked.png ├── info.plist ├── md_clean.py ├── notes_search.py ├── search_actions.py ├── setup.py ├── tag_search.py ├── template.md ├── template_selector.py ├── todo_search.py ├── url_scheme.py └── url_search.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc -------------------------------------------------------------------------------- /Markdown Notes.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/Markdown Notes.alfredworkflow -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alfred Markdown Notes 2 | 3 | https://acidham.github.io/alfred-markdown-notes/ 4 | 5 | Markdown Notes is a comprehensive note taking tool embedded into Aflred with powerful full text search (supports & and |), tag search and search capabilities for todos ( `- [ ]` or `* [ ]`) . With MD Notes you can quickly create new notes based on custom templates, e.g. meeting notes, bookmarks, project documentation, etc. 6 | 7 | If you are interested in the full journey read *[Productivity at your Finger Tips](https://acidham.medium.com/productivity-at-your-finger-tips-d6ef24770e0b)* 8 | 9 | MD Notes works with any mardkown editor. 10 | 11 | > [Typora](https://typora.io/) is set up in Alfred Workflow as preferred Markdown editor but it is possible to use another MD Editor or Text Editor if required. If you want to use another Editor you to define the Editor in the worklow steps at the end of the WF (right side of the workflow viz in Alfred) 12 | 13 | ## Installation 14 | 15 | 1. Download [Alfred Markdown Notes](https://github.com/Acidham/alfred-markdown-notes/releases/latest) 16 | 2. Double click downloaded file to install in Alfred 17 | 18 | ## Requirements 19 | 20 | 1. [Alfred Powerpack](https://www.alfredapp.com/powerpack/) 21 | 2. Python 3 22 | 23 | ## Configuration 24 | 25 | To get MD Notes to work properly you first need to configure the worklfow. Click on `Configure Workflow` in Alfred Workflow Tab. 26 | 27 | Variables marked with * are required for running MD Notes properly, the others are optional and can be ignored. 28 | 29 | * **Path to Notes:** * The path where MD Notes will be stored. 30 | The path can be absolute or relative but has to be a user directory! 31 | Examples: 32 | 33 | * `/Users/yourname/Dropbox/Notes` → works 34 | * `yourname/Dropbox/Notes` → works 35 | * `/yourname/Dropbox/Notes` → works 36 | * `/Volumes/usb` → will not work! 37 | 38 | * **Editor:** Select preferred Markdown Editor 39 | 40 | * **Default Date Format:** Defines date format when creating new notes or when using placeholders in templates: {date} e.g. %d.%m.%Y %H.%M 41 | 42 | * **Default Template:** * The file name that will be used as the default Template. Before templates can be used it is required to create the template.md e.g. `Template.md` (see [Working with Templates](#Working%20with%20Templates)) 43 | **Note**: Enter the file name ONLY without path e.g. `myTemplate.md` 44 | 45 | * **Extension:** * The MD files are text files with a specific extension (usually `.txt`or `.md`) any other extension can be defined if required. 46 | **Note:** The files must be type text files. 47 | 48 | * **Search in Tags in YMF only:** * 49 | 50 | Tags can be used in the YAML front matter (`Tags: #mytag`) or within the MD note. 51 | 52 | 1. When set to `True` tag search only search with YMF. 53 | 2. When set to `False` tags will be searched the whole MD note. 54 | 55 | * **Exact Match:** Defines if the search should match the exact search term (`True`) or the string (`False`) in markdown notes. 56 | 57 | **Note:** When exact match is set to `True` it is possible to enhance the search term with wildcards 58 | 59 | * **Todo sort order:** Sort order when using Todo Search ( `mdt`). 60 | `True` newest todos will be shown on top of the search results 61 | `False` oldest todos will be shown on top of the search results 62 | 63 | * **URL scheme:** (OPTIONAL) I figured out that some web app like Todoist is using web interface where, due to OS restrictions, file paths cannot be opened. To work around this URL scheme can be configured to open the note in markdown editor or viewer, e.g. Marked or iA Writer. Add URL Scheme like `x-writer://create?file=` and after `file=` will be enhanced with the MD Note path when executed. 64 | 65 | * **Template Tag:** The template tag defines which (`#Tagname`) defines a Template. Once you created a template just add template tag name to the MD Note and it will be recognized when you create a new MD Note from Template (see [Create new MD Notes from Template](#Create%20new%20MD%20Notes%20from%20Template)) 66 | 67 | * **Bookmark Tag:** Name of the tag which marks Notes containing URL/Bookmarks. 68 | 69 | * **Filename Format:** Standard file format for new MD notes. Default is the title of the notes but in some cases it is useful to add a date format e.g. when using Zettelkasten file format. 70 | The two placeholders can be used: 71 | 72 | * e.g. `{%d-%m-%Y}` or any other strftime format. 73 | *Be careful when using strftime format. Wrong format result in wrong file names!* 74 | * `{title}`: The title of the MD Note 75 | * Example: `{title}-{%d%m%Y}` 76 | 77 | ### Optional: QLMarkdown 78 | 79 | To use quicklook for Markdown files there is a QLMarkdown plugin available on git: [https://github.com/toland/qlmarkdown](https://github.com/toland/qlmarkdown) 80 | 81 | ## Features 82 | 83 | ### Full Text Search 84 | 85 | Type `mds` keyword into Alfred and get a list of all MD files sorted by last modified date. After `mds` keyword you can type a search term and text will be searched instantly. 86 | The search runs with exact match and with partial match by using wildcards `*` before or after the search term 87 | 88 | #### Syntax 89 | 90 | * `Hello Alfred` searches for exact match of the phrase 91 | * `Hello&Alfred` search for Notes containing the two words somewhere in the text 92 | * `Hello|Alfred` search for `Hello` or `Alfred` somwhere in the text 93 | * `Book` match exact word in the text 94 | * `Book*` machtes `Bookstore` and `Booking` 95 | * *Tip:* You can type `#Tag` in `mds` to search for Notes with specific Tag in text and using & and | in the same way than with text search. 96 | 97 | #### Options 98 | 99 | With the Alfred search results from `mds` and `mdt` you can perform additional actions to the note: 100 | 101 | * Pressing `Shift`you can quicklook the file. 102 | *Tip*: To quicklook markdown files formatted you can install [QLMarkdown](https://github.com/toland/qlmarkdown) 103 | * With Pressing `CMD` you can open the action menu. The following actions are available: 104 | * **Markdown Link**: Copy markdown link of the note to the clipboard for pasting into another app or markdown file 105 | * **Delete Note**: Delete the file and all associated assets such as images or other file types. 106 | * **Marked 2**: Opens the Note in Marked 2 107 | **Note:** The Markdown Editor can be changed in Alfred Preferences → Workflow 108 | * **URL Scheme**: Generate MD link for URL Scheme and copy to the clipboard e.g. `[My Notes](x-writer://open?path=/Users/joe/Documents/Notes/doc.md)` 109 | * It is possible to perform additional actions to one or more Notes by proceeding with File Actions (press `TAB` or `ALT+TAB` on a note or multiple notes): 110 | * **Delete MD Notes**: Same as *Delete Note* in action menu but it also deletes multiple Notes 111 | * **MD Link to Note**: Generates relative Link to a markdown document for referencing Notes in other Notes e.g. `[My Notes](mynote.md)` 112 | * **Create Markdown Index**: Selected Markdown files will be linked into a new Index file e.g. to collect links to all invoices for an insurance 113 | 114 | ### Tag Search 115 | 116 | Type `md#` to get a list of all tags found in the Notes or search for Tags (see [Options](#Options) as well) 117 | 118 | The tag search can also be used to search for already existing Tags to paste it into a markdown note. By pressing `CMD` and `Enter` the tag will be pasted into the frontmost app. 119 | 120 | ### Search in Todos 121 | 122 | Type `mdt` to get all Todos found in the MD Notes. The list is sorted based on when Notes with the todo was created (older notes first). As well you can search for full-text search in todo and use the modify keys (see [Options](#Options)) 123 | 124 | ### MD Bookmarks 125 | 126 | Bookmarks/ URLs stored in MD Notes can be opened using MD Notes. If an MD Note contains the bookmark tag (see [Configuration](#Configuration) → Bookmark Tag) and one or more URLs, they can be opened directly from Alfred directly. 127 | 128 | Type `mdb` to find Bookmarks found in the MD Notes. The list shows the links found in the MD Notes and corresponding note. 129 | 130 | ### Create a MD Note 131 | 132 | #### MD Note with Title 133 | 134 | Type `mdc` followed by a **title** to create a new MD Note with title. The default Templates will be used (see [Configuration](#Configuration)) 135 | 136 | #### Note with Title and Tags 137 | 138 | Type `mdc` followed by **title** and **tags** separated by space will create a Note with **title** and **tags**. 139 | 140 | #### MD Notes from Template 141 | 142 | Type `mdc` and you get a list of all Templates in your folder. After a template was selected the title can be entered as described above. 143 | 144 | **Note**: Templates are Notes tagged with `#Template` or whatever you defined as the template tag. 145 | 146 | #### YAML Fronter 147 | 148 | MD Notes uses YAML Fronter when searching in Tags. Therefore it is required to add YAML Fronter to the top of the notes, with the following format: 149 | 150 | ``` 151 | --- 152 | Tags: #mytag 153 | --- 154 | ``` 155 | 156 | ### Delete MD Note(s) 157 | 158 | There are various ways of deleting MD Notes. Important is to not use Alfred standard file deletion because using delete feature coming with MD notes workflow ensures that asset dependencies will also be deleted: 159 | 160 | 1. **Delete via action menu**: Press `CMD+Enter` when search results will be shown 161 | 2. **Delete via file action**: This option also allows to collect some notes via Alfred file buffer first before deleting them. 162 | 3. **Delete via in batch mode**. This method allows to tag specific MD notes for deletion eg. By adding `#delete` tag to the notes and mark them for deletion. Any other tag name can be used. 163 | To delete MD notes tagged with a specific tag, just execute `mdd` followed by the tag name. The search will show the total number of affected notes with a preview option. 164 | 165 | ### Working with Templates 166 | 167 | Templates are a great way to quickly create a MD Note based on a Markdown template file. The Template files are created and stored in the same way than normal notes and must contain the Template tag (see [Configuration](#Configuration)) in the YAML Fronter section (see [YAML Fronter](#YAML%20Fonter)). 168 | 169 | There are two way to create a file based on a template: 170 | 171 | * Via `mdc` → Create Note: A Note with a title will be created based on default template 172 | * Via `mdc` → `Template`: The Note will be created based on selected template. After the corresponding template was selected, the title can be entered. 173 | 174 | #### Using placeholder in Templates 175 | 176 | The MD Notes Templates can contain two placeholder values and will be replaced when using Templates with the command `mdc`. 177 | With `mdc` it is also possible to directly create a note. In this case the default template will be used. The default template can be configured in Alfred Workflow settings (see [Configuration](#Configuration)): 178 | 179 | * `{title}`: The title will be used that you entered when creating a new note 180 | * `{date}`: Today's date will be used when creating a new note 181 | 182 | ### Adding a file link to a Markdown Note 183 | 184 | Images can be usually added via drag&drop to the markdown editor, esp. with Typora. It is also possible to add other file-types by using `Upload Asset for MD Notes` file action in Alfred. 185 | 186 | To add a file to a Markdown Note, search for the File in Alfred and then press `Tab` to enter file actions. Search for `Upload Asset for MD Notes` and execute. The Markdown Link will be copied to the Clipboard. Just paste the MD Link to a Markdown Note. 187 | 188 | ### Fetch HTML Pages 189 | 190 | MD Notes provides the possibility to fetch pages from an URL and store it in a new note. The note will be created with the containing Page title. 191 | In case the page cannot be fetched MD Notes create a note and add the URL into the note. 192 | 193 | To fetch an URL use `mdf` and enter the target URL. 194 | 195 | **Note:** To import HTML content [Pandoc](https://pandoc.org/installing.html) is required. If Pandoc is not installed the note will only contain the URL to the Page. 196 | 197 | ### Upload Asset for MD Note (file action) 198 | 199 | Depending on the MD Editor used, assets (png, pdf, etc.) can be drag and drop into the MD document. Some editors use absolute or relative paths, and some create an assets' directory automatically and copy the asset into the asset folder. The assets are stored with the MD Notes and when you delete the original asset file, it ensures that the MD note keeps a copy. 200 | 201 | In cases where the file will be just linked in MD Editor, the Workflow provides the ability to upload the asset under the `Path to Notes` (see [Options](file:///Users/jjung/Documents/Git/alfred-markdown-notes/README.md#Options)) with the name `_NoteAssets.` The folder will be automatically created. 202 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: Alfred Markdown Notes 2 | theme: jekyll-theme-midnight 3 | -------------------------------------------------------------------------------- /src/01B01E1C-F114-4C29-9C0C-2881FA84A5D1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/01B01E1C-F114-4C29-9C0C-2881FA84A5D1.png -------------------------------------------------------------------------------- /src/5FA571C6-6183-44C9-B18F-2D4C73A0C48F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/5FA571C6-6183-44C9-B18F-2D4C73A0C48F.png -------------------------------------------------------------------------------- /src/661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4.png -------------------------------------------------------------------------------- /src/898E9FE0-5165-4530-98E9-9497DDF6DA0D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/898E9FE0-5165-4530-98E9-9497DDF6DA0D.png -------------------------------------------------------------------------------- /src/Alfred3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import json 4 | import os 5 | import sys 6 | import time 7 | from plistlib import dump, load 8 | 9 | """ 10 | Alfred Script Filter generator class 11 | Version 4.1 12 | Python 3 required! 13 | """ 14 | 15 | 16 | class Items(object): 17 | """ 18 | Alfred WF Items object to generate Script Filter object 19 | 20 | Returns: 21 | 22 | object: WF object 23 | """ 24 | 25 | def __init__(self): 26 | self.item = {} 27 | self.items = [] 28 | self.mods = {} 29 | 30 | def getItemsLengths(self) -> int: 31 | """ 32 | Get amount of items in object 33 | 34 | Returns: 35 | 36 | int: Number of items 37 | 38 | """ 39 | return len(self.items) 40 | 41 | def setKv(self, key: str, value: str) -> None: 42 | """ 43 | Set a key value pair to item 44 | 45 | Args: 46 | 47 | key (str): Name of the Key 48 | value (str): Value of the Key 49 | """ 50 | self.item.update({key: value}) 51 | 52 | def addItem(self) -> None: 53 | """ 54 | Add/commits an item to the Script Filter Object 55 | 56 | Note: addItem needs to be called after setItem, addMod, setIcon 57 | """ 58 | self.addModsToItem() 59 | self.items.append(self.item) 60 | self.item = {} 61 | self.mods = {} 62 | 63 | def setItem(self, **kwargs: str) -> None: 64 | """ 65 | Add multiple key values to define an item 66 | 67 | Note: addItem needs to be called to submit a Script Filter item 68 | to the Script Filter object 69 | 70 | Args: 71 | 72 | kwargs (kwargs): title,subtitle,arg,valid,quicklookurl,uid,automcomplete,type 73 | """ 74 | for key, value in kwargs.items(): 75 | self.setKv(key, value) 76 | 77 | def getItem(self, d_type: str = "") -> str: 78 | """ 79 | Get current item definition for validation 80 | 81 | Args: 82 | 83 | d_type (str, optional): defines returned object format "JSON" if it needs to be readable . Defaults to "". 84 | 85 | Returns: 86 | 87 | str: JSON represenation of an item 88 | """ 89 | if d_type == "": 90 | return self.item 91 | else: 92 | return json.dumps(self.item, default=str, indent=4) 93 | 94 | def getItems(self, response_type: str = "json") -> json: 95 | """ 96 | get the final items data for which represents the script filter output 97 | 98 | Args: 99 | 100 | response_type (str, optional): "dict"|"json". Defaults to "json". 101 | 102 | Raises: 103 | 104 | ValueError: If key is not "dict"|"json" 105 | 106 | Returns: 107 | 108 | str: returns the item representing script filter output 109 | """ 110 | valid_keys = {"json", "dict"} 111 | if response_type not in valid_keys: 112 | raise ValueError(f"Type must be in: {valid_keys}") 113 | the_items = dict() 114 | the_items.update({"items": self.items}) 115 | if response_type == "dict": 116 | return the_items 117 | elif response_type == "json": 118 | return json.dumps(the_items, default=str, indent=4) 119 | 120 | def setIcon(self, m_path: str, m_type: str = "") -> None: 121 | """ 122 | Set the icon of an item. 123 | Needs to be called before addItem! 124 | 125 | Args: 126 | 127 | m_path (str): Path to the icon 128 | m_type (str, optional): "icon"|"fileicon". Defaults to "". 129 | """ 130 | self.setKv("icon", self.__define_icon(m_path, m_type)) 131 | 132 | def __define_icon(self, path: str, m_type: str = "") -> dict: 133 | """ 134 | Private method to create icon set 135 | 136 | Args: 137 | 138 | path (str): Path to the icon file 139 | 140 | m_type (str, optional): "image"|"fileicon". Defaults to "". 141 | 142 | Returns: 143 | 144 | dict: icon and type 145 | """ 146 | icon = {} 147 | if m_type != "": 148 | icon.update({"type": m_type}) 149 | icon.update({"path": path}) 150 | return icon 151 | 152 | def addMod( 153 | self, 154 | key: str, 155 | arg: str, 156 | subtitle: str, 157 | valid: bool = True, 158 | icon_path: str = "", 159 | icon_type: str = "", 160 | ) -> None: 161 | """ 162 | Add a mod to an item 163 | 164 | Args: 165 | 166 | key (str): "alt"|"cmd"|"shift"|"fn"|"ctrl 167 | arg (str): Value of Mod arg 168 | subtitle (str): Subtitle 169 | valid (bool, optional): Arg valid or not. Defaults to True. 170 | icon_path (str, optional): Path to the icon relative to WF dir. Defaults to "". 171 | icon_type (str, optional): "image"|"fileicon". Defaults to "". 172 | 173 | Raises: 174 | 175 | ValueError: if key is not in list 176 | """ 177 | valid_keys = {"alt", "cmd", "shift", "ctrl", "fn"} 178 | if key not in valid_keys: 179 | raise ValueError(f"Key must be in: {valid_keys}") 180 | mod = {} 181 | mod.update({"arg": arg}) 182 | mod.update({"subtitle": subtitle}) 183 | mod.update({"valid": valid}) 184 | if icon_path != "": 185 | the_icon = self.__define_icon(icon_path, icon_type) 186 | mod.update({"icon": the_icon}) 187 | self.mods.update({key: mod}) 188 | 189 | def addModsToItem(self) -> None: 190 | """ 191 | Adds mod to an item 192 | """ 193 | if bool(self.mods): 194 | self.setKv("mods", self.mods) 195 | self.mods = dict() 196 | 197 | def updateItem(self, id: int, key: str, value: str) -> None: 198 | """ 199 | Update an Alfred script filter item key with a new value 200 | 201 | Args: 202 | 203 | id (int): list indes 204 | key (str): key which needs to be updated 205 | value (str): new value 206 | """ 207 | dict_item = self.items[id] 208 | kv = dict_item[key] 209 | dict_item[key] = kv + value 210 | self.items[id] = dict_item 211 | 212 | def write(self, response_type: str = "json") -> None: 213 | """ 214 | Generate Script Filter Output and write back to stdout 215 | 216 | Args: 217 | 218 | response_type (str, optional): json or dict as output format. Defaults to 'json'. 219 | """ 220 | output = self.getItems(response_type=response_type) 221 | sys.stdout.write(output) 222 | 223 | 224 | class Tools(object): 225 | """ 226 | Alfred Tools, helpful methos when dealing with Scripts in Alfred 227 | 228 | Args: 229 | 230 | object (obj): Object class 231 | """ 232 | @staticmethod 233 | def logPyVersion() -> None: 234 | """ 235 | Log Python Version to shell 236 | """ 237 | Tools.log("PYTHON VERSION:", sys.version) 238 | 239 | @staticmethod 240 | def log(*message) -> None: 241 | """ 242 | Log message to stderr 243 | """ 244 | sys.stderr.write(f'{" ".join(message)}\n') 245 | 246 | @staticmethod 247 | def getEnv(var: str, default: str = str()) -> str: 248 | """ 249 | Reads environment variable 250 | 251 | Args: 252 | 253 | var (string}: Variable name 254 | default (string, optional): fallback if None 255 | 256 | Returns: 257 | 258 | (str): Env value or string if not available 259 | """ 260 | return os.getenv(var) if os.getenv(var) is not None else default 261 | 262 | @staticmethod 263 | def getEnvBool(var: str, default: bool = False) -> bool: 264 | """ 265 | Reads boolean env variable provided as text. 266 | 0 will be treated as False 267 | >1 will be treated as True 268 | 269 | Args: 270 | 271 | var (str): Name of the env variable 272 | default (bool, optional): Default if not found. Defaults to False. 273 | 274 | Returns: 275 | 276 | bool: True or False as bool 277 | """ 278 | try: 279 | if os.getenv(var).isdigit(): 280 | if os.getenv(var) == '0': 281 | return False 282 | else: 283 | return True 284 | if os.getenv(var).lower() == "true": 285 | return True 286 | else: 287 | return default 288 | except AttributeError as e: 289 | sys.exit(f'ERROR: Alfred Environment "{var}" Variable not found!') 290 | 291 | @staticmethod 292 | def getArgv(i: int, default=str()) -> str: 293 | """ 294 | Get argument values from input in Alfred or empty if not available 295 | 296 | Args: 297 | 298 | i (int): index of argument 299 | default (string, optional): Fallback if None, default string 300 | 301 | Returns: 302 | 303 | response_type (str) -- argv string or None 304 | """ 305 | try: 306 | return sys.argv[i] 307 | except IndexError: 308 | return default 309 | pass 310 | 311 | @staticmethod 312 | def getDateStr(float_time: float, format: str = "%d.%m.%Y") -> str: 313 | """ 314 | Format float time to string 315 | 316 | Args: 317 | 318 | float_time (float): Time in float 319 | 320 | format (str, optional): format string. Defaults to '%d.%m.%Y'. 321 | 322 | Returns: 323 | 324 | str: Formatted Date String 325 | """ 326 | time_struct = time.gmtime(float_time) 327 | return time.strftime(format, time_struct) 328 | 329 | @staticmethod 330 | def getDateEpoch(float_time: float) -> str: 331 | return time.strftime("%d.%m.%Y", time.gmtime(float_time / 1000)) 332 | 333 | @staticmethod 334 | def sortListDict(list_dict: list, key: str, reverse: bool = True) -> list: 335 | """ 336 | Sort List with Dictionary based on given key in Dict 337 | 338 | Args: 339 | 340 | list_dict (list(dict)): List which contains unsorted dictionaries 341 | 342 | key (str): name of the key of the dict 343 | 344 | reverse (bool, optional): Reverse order. Defaults to True. 345 | 346 | Returns: 347 | 348 | list(dict): sorted list of dictionaries 349 | """ 350 | return sorted(list_dict, key=lambda k: k[key], reverse=reverse) 351 | 352 | @staticmethod 353 | def sortListTuple(list_tuple: list, el: int, reverse: bool = True) -> list: 354 | """ 355 | Sort List with Tubles based on a given element in Tuple 356 | 357 | Args: 358 | 359 | list_tuple (list(tuble)): Sort List with Tubles based on a given element in Tuple 360 | el (int): which element 361 | reverse (bool, optional): Reverse order. Defaults to True. 362 | 363 | Returns: 364 | 365 | list(tuble) -- sorted list with tubles 366 | """ 367 | return sorted(list_tuple, key=lambda tup: tup[el], reverse=reverse) 368 | 369 | @staticmethod 370 | def notify(title: str, text: str) -> None: 371 | """ 372 | Send Notification to mac Notification Center 373 | 374 | Arguments: 375 | 376 | title (str): Title String 377 | text (str): The message 378 | """ 379 | os.system( 380 | f""" 381 | osascript -e 'display notification "{text}" with title "{title}"' 382 | """ 383 | ) 384 | 385 | @staticmethod 386 | def strJoin(*args: str) -> str: 387 | """Joins a list of strings 388 | 389 | Arguments: 390 | 391 | *args (list): List which contains strings 392 | 393 | Returns: 394 | 395 | str: joined str 396 | """ 397 | return str().join(args) 398 | 399 | @staticmethod 400 | def chop(theString: str, ext: str) -> str: 401 | """ 402 | Cuts a string from the end and return the remaining 403 | 404 | Args: 405 | 406 | theString (str): The String to cut 407 | ext (str): String which needs to be removed 408 | 409 | Returns: 410 | 411 | str: chopped string 412 | """ 413 | if theString.endswith(ext): 414 | return theString[: -len(ext)] 415 | return theString 416 | 417 | @staticmethod 418 | def getEnvironment() -> dict: 419 | """ 420 | Get all environment variablse as a dict 421 | 422 | Returns: 423 | 424 | dict: Dict with env variables e.g. {"env1": "value"} 425 | """ 426 | environment = os.environ 427 | env_dict = dict() 428 | for k, v in environment.iteritems(): 429 | env_dict.update({k: v}) 430 | return env_dict 431 | 432 | @staticmethod 433 | def getDataDir() -> str: 434 | data_dir = os.getenv("alfred_workflow_data") 435 | if not (os.path.isdir(data_dir)): 436 | os.mkdir(data_dir) 437 | return data_dir 438 | 439 | @staticmethod 440 | def getCacheDir() -> str: 441 | cache_dir = os.getenv("alfred_workflow_cache") 442 | if not(os.path.isdir(cache_dir)): 443 | os.mkdir(cache_dir) 444 | return cache_dir 445 | 446 | 447 | class Plist: 448 | """ 449 | Plist handling class 450 | 451 | Returns: 452 | 453 | object: A plist object 454 | 455 | 456 | """ 457 | 458 | def __init__(self): 459 | # Read info.plist into a standard Python dictionary 460 | with open("info.plist", "rb") as fp: 461 | self.info = load(fp) 462 | 463 | def getConfig(self) -> str: 464 | return self.info["variables"] 465 | 466 | def getVariable(self, variable: str) -> str: 467 | """ 468 | Get Plist variable with name 469 | 470 | Args: 471 | 472 | variable (str): Name of the variable 473 | 474 | Returns: 475 | 476 | str: Value of variable with name 477 | 478 | """ 479 | try: 480 | return self.info["variables"][variable] 481 | except KeyError: 482 | pass 483 | 484 | def setVariable(self, variable: str, value: str) -> None: 485 | """ 486 | Set a Plist variable 487 | 488 | Args: 489 | 490 | variable (str): Name of Plist Variable 491 | value (str): Value of Plist Variable 492 | 493 | """ 494 | # Set a variable 495 | self.info["variables"][variable] = value 496 | self._saveChanges() 497 | 498 | def deleteVariable(self, variable: str) -> None: 499 | """ 500 | Delete a Plist variable with name 501 | 502 | Args: 503 | 504 | variable (str): Name of the Plist variable 505 | 506 | """ 507 | try: 508 | del self.info["variables"][variable] 509 | self._saveChanges() 510 | except KeyError: 511 | pass 512 | 513 | def _saveChanges(self) -> None: 514 | """ 515 | Save changes to Plist 516 | """ 517 | with open("info.plist", "wb") as fp: 518 | dump(self.info, fp) 519 | 520 | 521 | class Keys(object): 522 | CMD = u'\u2318' 523 | SHIFT = u'\u21E7' 524 | ENTER = u'\u23CE' 525 | ARROW_RIGHT = u'\u2192' 526 | 527 | 528 | class AlfJson(object): 529 | 530 | def __init__(self) -> None: 531 | self.arg: dict = dict() 532 | self.config: dict = dict() 533 | self.variables: dict = dict() 534 | 535 | def add_args(self, d) -> None: 536 | """ 537 | Add arg dictionary 538 | 539 | Args: 540 | 541 | d (dict): Key-Value pairs of args 542 | 543 | """ 544 | self.arg.update(d) 545 | 546 | def add_configs(self, d) -> None: 547 | """ 548 | Add config dictionary 549 | 550 | Args: 551 | 552 | d (dict): Key-Value pairs of configs 553 | 554 | """ 555 | self.config.update(d) 556 | 557 | def add_variables(self, d) -> None: 558 | """ 559 | Add variables dictionary 560 | 561 | Args: 562 | 563 | d (dict): Key-Value pairs of variables 564 | 565 | """ 566 | self.variables.update(d) 567 | 568 | def write_json(self) -> None: 569 | """ 570 | Write Alfred JSON config object to std out 571 | """ 572 | out = {"alfredworkflow": {"arg": self.arg, "config": self.config, "variables": self.variables}} 573 | sys.stdout.write(json.dumps(out)) 574 | -------------------------------------------------------------------------------- /src/E6223AE8-EC41-40DA-88AC-8476D6F73ED1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/E6223AE8-EC41-40DA-88AC-8476D6F73ED1.png -------------------------------------------------------------------------------- /src/F8CB74BA-4C13-4621-BC60-E50650C85A9D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/F8CB74BA-4C13-4621-BC60-E50650C85A9D.png -------------------------------------------------------------------------------- /src/MD Notes Help.md: -------------------------------------------------------------------------------- 1 | # MD Notes Help for Alfred 2 | 3 | Markdown Notes is a comprehensive note-taking tool embedded into Alfred with powerful full-text search (supports & and |), tag search and search capabilities for todos ( `- [ ]` or `* [ ]`) . With MD Notes you can quickly create new notes based on custom templates, e.g. meeting notes, bookmarks, project documentation, etc. 4 | 5 | MD Notes works with any markdown editor. 6 | 7 | > [Typora](https://typora.io/) is set up in Alfred Workflow as preferred Markdown editor but it is possible to use another MD Editor or Text Editor if required. To use another Editor it is required to define the Editor in the worklow steps at the end of the WF. 8 | 9 | ## Configuration 10 | 11 | To get MD Notes to work properly you first need to configure the worklfow. Click on `Configure Workflow` in Alfred Workflow Tab. 12 | 13 | ### Variables 14 | 15 | Variables marked with * are required for running MD Notes properly, the others are optional and can be ignored. 16 | 17 | * **Path to Notes** * (`path_to_notes`): The path where MD Notes will be stored. 18 | The path can be absolute or relative but has to be a user directory! 19 | Examples: 20 | 21 | * `/Users/yourname/Dropbox/Notes` → works 22 | * `yourname/Dropbox/Notes` → works 23 | * `/yourname/Dropbox/Notes` → works 24 | * `/Volumes/usb` → will not work! 25 | 26 | * **Editor** 27 | 28 | * **Default Date Format** (`default_date_format`): Defines date format when creating new notes or when using placeholders in templates: {date} e.g. %d.%m.%Y %H.%M 29 | 30 | * **Default Template** * (`default_template`): The file name that will be used as the default Template. Before templates can be used it is required to create the template.md e.g. `Template.md` (see [Working with Templates](#Working%20with%20Templates)) 31 | **Note**: Enter the file name ONLY without path e.g. `myTemplate.md` 32 | 33 | * **Extension** * (`ext`): The MD files are text files with a specific extension (usually `.txt`or `.md`) any other extension can be defined if required. 34 | **Note:** The files must be type text files. 35 | 36 | * **Search in Tags in YMF only** * (`search_yaml_tags_only`) 37 | 38 | Tags can be used in the YAML front matter (`Tags: #mytag`) or within the MD note. 39 | 40 | 1. When set to `True` tag search only search with YMF. 41 | 2. When set to `False` tags will be searched the whole MD note. 42 | 43 | * **Exact Match** (`exact_match`): Defines if the search should match the exact search term (`True`) or the string (`False`) in markdown notes. 44 | 45 | **Note:** When exact match is set to `True` it is possible to enhance the search term with wildcards 46 | 47 | * **Todo sort order** (todo_newest_oldest): Sort order when using Todo Search ( `mdt`). 48 | `True` newest todos will be shown on top of the search results 49 | `False` oldest todos will be shown on top of the search results 50 | 51 | * **URL scheme** (`url_scheme`): (OPTIONAL) I figured out that some web app like Todoist is using web interface where, due to OS restrictions, file paths cannot be opened. To work around this URL scheme can be configured to open the note in markdown editor or viewer, e.g. Marked or iA Writer. Add URL Scheme like `x-writer://create?file=` and after `file=` will be enhanced with the MD Note path when executed. 52 | 53 | * **Template Tag** (`template_tag`): The template tag defines which `#Tagname`) defines a Template. Once you created a template just add template tag name to the MD Note and it will be recognized when you create a new MD Note from Template (see [Create new MD Notes from Template](#Create%20new%20MD%20Notes%20from%20Template)) 54 | 55 | * **Bookmark Tag** (`bookmark_tag`): Name of the tag which marks Notes containing URL/Bookmarks. 56 | 57 | * **Filename Format** (`filename_format`): Standard file format for new MD notes. Default is the title of the notes but in some cases it is useful to add a date format e.g. when using Zettelkasten file format. 58 | The two placeholders can be used: 59 | 60 | * e.g. `{%d-%m-%Y}` or any other strftime format. 61 | *Be careful when using strftime format. Wrong format result in wrong file names!* 62 | * `{title}`: The title of the MD Note 63 | * Example: `{title}-{%d%m%Y}` 64 | 65 | 66 | ### Optional: QLMarkdown 67 | 68 | To use quicklook for Markdown files there is a QLMarkdown plugin available on git: [https://github.com/toland/qlmarkdown](https://github.com/toland/qlmarkdown) 69 | 70 | ## Features 71 | 72 | ### Full Text Search 73 | 74 | Type `mds` keyword into Alfred and get a list of all MD files sorted by last modified date. After `mds` keyword you can type a search term and text will be searched instantly. 75 | The search runs with exact match and with partial match by using wildcards `*` before or after the search term 76 | 77 | #### Syntax 78 | 79 | * `Hello Alfred` searches for exact match of the phrase 80 | * `Hello&Alfred` search for Notes containing the two words somewhere in the text 81 | * `Hello|Alfred` search for `Hello` or `Alfred` somwhere in the text 82 | * `Book` match exact word in the text 83 | * `Book*` machtes `Bookstore` and `Booking` 84 | * *Tip:* You can type `#Tag` in `mds` to search for Notes with specific Tag in text and using & and | in the same way than with text search. 85 | 86 | #### Options 87 | 88 | With the Alfred search results from `mds` and `mdt` you can perform additional actions to the note: 89 | 90 | * Pressing `Shift`you can quicklook the file. 91 | *Tip*: To quicklook markdown files formatted you can install [QLMarkdown](https://github.com/toland/qlmarkdown) 92 | * With Pressing `CMD` you can open the action menu. The following actions are available: 93 | * **Markdown Link**: Copy markdown link of the note to the clipboard for pasting into another app or markdown file 94 | * **Delete Note**: Delete the file and all associated assets such as images or other file types. 95 | * **Marked 2**: Opens the Note in Marked 2 96 | **Note:** The Markdown Editor can be changed in Alfred Preferences → Workflow 97 | * **URL Scheme**: Generate MD link for URL Scheme and copy to the clipboard e.g. `[My Notes](x-writer://open?path=/Users/joe/Documents/Notes/doc.md)` 98 | * It is possible to perform additional actions to one or more Notes by proceeding with File Actions (press `TAB` or `ALT+TAB` on a note or multiple notes): 99 | * **Delete MD Notes**: Same as *Delete Note* in action menu but it also deletes multiple Notes 100 | * **MD Link to Note**: Generates relative Link to a markdown document for referencing Notes in other Notes e.g. `[My Notes](mynote.md)` 101 | * **Create Markdown Index**: Selected Markdown files will be linked into a new Index file e.g. to collect links to all invoices for an insurance 102 | 103 | ### Tag Search 104 | 105 | Type `md#` to get a list of all tags found in the Notes or search for Tags (see [Options](#Options) as well) 106 | 107 | The tag search can also be used to search for already existing Tags to paste it into a markdown note. By pressing `CMD` and `Enter` the tag will be pasted into the frontmost app. 108 | 109 | ### Search in Todos 110 | 111 | Type `mdt` to get all Todos found in the MD Notes. The list is sorted based on when Notes with the todo was created (older notes first). As well you can search for full-text search in todo and use the modify keys (see [Options](#Options)) 112 | 113 | ### Create a MD Note 114 | 115 | #### MD Note with Title 116 | 117 | Type `mdc` followed by a **title** to create a new MD Note with title. The default Templates will be used (see [Configuration](#Configuration)) 118 | 119 | #### Note with Title and Tags 120 | 121 | Type `mdc` followed by **title** and **tags** separated by space will create a Note with **title** and **tags**. 122 | 123 | #### MD Notes from Template 124 | 125 | Type `mdc` and you get a list of all Templates in your folder. After a template was selected the title can be entered as described above. 126 | 127 | **Note**: Templates are Notes tagged with `#Template` or whatever you defined as the template tag. 128 | 129 | #### YAML Fronter 130 | 131 | MD Notes uses YAML Fronter when searching in Tags. Therefore it is required to add YAML Fronter to the top of the notes, with the following format: 132 | 133 | ``` 134 | --- 135 | Tags: #mytag 136 | --- 137 | ``` 138 | 139 | ### Delete MD Note(s) 140 | 141 | There are various ways of deleting MD Notes. Important is to not use Alfred standard file deletion because using delete feature coming with MD notes workflow ensures that asset dependencies will also be deleted: 142 | 143 | 1. **Delete via action menu**: Press `CMD+Enter` when search results will be shown 144 | 2. **Delete via file action**: This option also allows to collect some notes via Alfred file buffer first before deleting them. 145 | 3. **Delete via batch mode**. This method allows to tag specific MD notes for deletion eg. By adding `#delete` tag to the notes and mark them for deletion. Any other tag name can be used. 146 | To delete MD notes tagged with a specific tag, just execute `mdd` followed by the tag name. The search will show the total number of affected notes with a preview option. 147 | 148 | ### Working with Templates 149 | 150 | Templates are a great way to quickly create a MD Note based on a Markdown template file. The Template files are created and stored in the same way than normal notes and must contain the Template tag (see [Configuration](#Configuration)) in the YAML Fronter section (see [YAML Fronter](#YAML%20Fonter)). 151 | 152 | There are two way to create a file based on a template: 153 | 154 | * Via `mdc` → Create Note: A Note with a title will be created based on default template 155 | * Via `mdc` → `Template`: The Note will be created based on selected template. After the corresponding template was selected, the title can be entered. 156 | 157 | #### Using placeholder in Templates 158 | 159 | The MD Notes Templates can contain two placeholder values and will be replaced when using Templates with the command `mdc`. 160 | With `mdc` it is also possible to directly create a note. In this case the default template will be used. The default template can be configured in Alfred Workflow settings (see [Configuration](#Configuration)): 161 | 162 | * `{title}`: The title will be used that you entered when creating a new note 163 | * `{date}`: Today's date will be used when creating a new note 164 | 165 | ### Adding a file link to a Markdown Note 166 | 167 | Images can be usually added via drag&drop to the markdown editor, esp. with Typora. It is also possible to add other file-types by using `Upload Asset for MD Notes` file action in Alfred. 168 | 169 | To add a file to a Markdown Note, search for the File in Alfred and then press `Tab` to enter file actions. Search for `Upload Asset for MD Notes` and execute. The Markdown Link will be copied to the Clipboard. Just paste the MD Link to a Markdown Note. 170 | 171 | ### Fetch HTML Pages 172 | 173 | MD Notes provides the possibility to fetch pages from an URL and store it in a new note. The note will be created with the containing Page title. 174 | In case the page cannot be fetched MD Notes create a note and add the URL into the note. 175 | 176 | To fetch an URL use `mdf` and enter the target URL. 177 | 178 | **Note:** To import HTML content [Pandoc](https://pandoc.org/installing.html) is required. If Pandoc is not installed the note will only contain the URL to the Page. 179 | -------------------------------------------------------------------------------- /src/MyNotes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import os 6 | import re 7 | import sys 8 | from collections import Counter, OrderedDict 9 | from urllib.request import pathname2url 10 | 11 | from Alfred3 import Tools 12 | 13 | 14 | class Notes(object): 15 | 16 | REPL_MAP = { 17 | '[': '', 18 | ']': ' ', 19 | '(': '', 20 | ')': ' ', 21 | '\n': ' ', 22 | '*': ' ', 23 | ',': ' ', 24 | '.': ' ', 25 | '-': ' ', 26 | ':': ' ', 27 | '?': ' ', 28 | '!': ' ' 29 | } 30 | 31 | # Replacement map for Filename when new file created 32 | CHAR_REPLACEMENT_MAP = { 33 | '/': '-', 34 | '\\': '-', 35 | ':': '-', 36 | '|': '-', 37 | ',': '', 38 | '#': '-' 39 | } 40 | 41 | # Fallback Content when no Template is available 42 | FALLBACK_CONTENT = "---\n" \ 43 | "Created: {date}\n" \ 44 | "Tags: \n" \ 45 | "---\n" \ 46 | "# {title}\n" \ 47 | "```\n" \ 48 | "This is the fallback Template.\n" \ 49 | "Create your own template, see help!\n" \ 50 | "```" 51 | 52 | def __init__(self): 53 | if not self.isPython3(): 54 | Tools.log("PYTHON VERSION:", sys.version) 55 | raise ModuleNotFoundError("Python version 3.7.0 or higher required!") 56 | self.extension = self.__buildNotesExtension() 57 | self.path = self.__buildNotesPath() 58 | self.default_template = os.getenv('default_template') 59 | self.template_tag = os.getenv('template_tag') 60 | self.url_scheme = os.getenv('url_scheme') 61 | self.search_yaml_tags_only = Tools.getEnvBool('search_yaml_tags_only') 62 | self.default_date_format = os.getenv('default_date_format') 63 | self.exact_match = Tools.getEnvBool('exact_match') 64 | self.todo_newest_oldest = Tools.getEnvBool('todo_newest_oldest') 65 | 66 | @staticmethod 67 | def isPython3() -> bool: 68 | """ 69 | Check if script is executed with Python 3.7 or higher 70 | 71 | Returns: 72 | 73 | bool: True if Python3 else false 74 | """ 75 | is_python3 = True 76 | if sys.version_info < (3, 7): 77 | is_python3 = False 78 | return is_python3 79 | 80 | @staticmethod 81 | def __buildNotesExtension() -> str: 82 | """ 83 | Get notes extension configured in workflow preference 84 | 85 | Returns: 86 | 87 | str: extension incl. dot e.g. .md 88 | 89 | """ 90 | ext = os.getenv('ext') 91 | if ext is None: 92 | ext = '.md' 93 | return ext if '.' in ext else str().join(['.', ext]) 94 | 95 | @staticmethod 96 | def __buildNotesPath() -> str: 97 | """ 98 | Create Notes path configured in preferences 99 | 100 | Returns: 101 | 102 | str: home path to notes directory 103 | 104 | """ 105 | user_dir = os.path.expanduser('~') 106 | path = os.getenv('path_to_notes') 107 | if path.startswith('~'): 108 | path = path.replace('~', user_dir) 109 | if not (path.startswith('/')): 110 | path = os.path.join("/", path) 111 | if not (path.startswith('/Users')): 112 | path = os.path.join(user_dir, path) 113 | if not (os.path.exists(path)): 114 | sys.stderr.write(f"ERROR: {path} is not a valid notes directory. Add a valid path for path_to_notes") 115 | sys.exit(0) 116 | return path 117 | 118 | @staticmethod 119 | def getTodayDate(fmt: str = "%d.%m.%Y") -> str: 120 | """ 121 | Get today's date 122 | 123 | Args: 124 | 125 | fmt (str, optional): Date format. Defaults to "%d.%m.%Y". 126 | 127 | Returns: 128 | 129 | str: formatted today's date 130 | 131 | """ 132 | now = datetime.datetime.now() 133 | return now.strftime(fmt) 134 | 135 | def getDefaultDate(self) -> str: 136 | """ 137 | Read default date format from environment variable 138 | 139 | Returns: 140 | 141 | str: default date format file name or default format 142 | """ 143 | return "%d.%m.%Y %H.%M" if self.default_date_format == str() else self.default_date_format 144 | 145 | def getNotesPath(self) -> str: 146 | """ 147 | Get path to notes home directory 148 | 149 | Returns: 150 | 151 | str: Path to notes home 152 | 153 | """ 154 | return self.path 155 | 156 | def getNotesExtension(self) -> str: 157 | """ 158 | Get notes extension from .env 159 | 160 | Returns: 161 | 162 | str: File extension for md files 163 | 164 | """ 165 | return self.extension 166 | 167 | @staticmethod 168 | def strJoin(*args: str) -> str: 169 | """ 170 | Join multiple strings 171 | 172 | Arguments: 173 | 174 | *args (str): strings to join 175 | 176 | Returns: 177 | 178 | (str): joined string 179 | 180 | """ 181 | return str().join(args) 182 | 183 | @staticmethod 184 | def strReplace(text: str, replace_map: dict, lowercase: bool = True) -> str: 185 | """ 186 | Replace in text from a replacement map 187 | 188 | Args: 189 | 190 | text (str): The string which needs to be processed 191 | replace_map (dict): dict with search:replace 192 | 193 | Returns: 194 | 195 | str : String with replacements 196 | 197 | """ 198 | for k in replace_map.keys(): 199 | text = text.replace(k, replace_map[k]) 200 | return text.lower() if lowercase else text 201 | 202 | 203 | class Search(Notes): 204 | """ 205 | Search in Notes 206 | 207 | Returns: 208 | 209 | (object): a Search object 210 | 211 | """ 212 | 213 | def __init__(self): 214 | super(Search, self).__init__() 215 | 216 | def _match(self, search_terms, content, operator): 217 | """ 218 | Find matches of search_terms list with OR or AND 219 | 220 | Args: 221 | 222 | search_terms (list): Search terms 223 | content (str): Text to search 224 | operator (str): 'OR' or 'AND' 225 | 226 | Returns: 227 | 228 | bool: True if search terms matches 229 | 230 | """ 231 | content = content.lower() 232 | content = self.strReplace(content, self.REPL_MAP) 233 | word_list = content.split(' ') 234 | word_list = [self._chop(w, '#') for w in word_list] 235 | search_terms = [s.lower() for s in search_terms] 236 | match = False 237 | matches = list() 238 | 239 | for st in search_terms: 240 | search_str = st.replace('*', str()) 241 | # search if search term contains a whitespace 242 | if ' ' in st: 243 | regexp = re.compile(f'({st})', re.I) 244 | match = True if len(re.findall(regexp, content)) > 0 else False 245 | # search if wildcard search in the end 246 | elif st.endswith('*'): 247 | match_list = [x for x in word_list if x.startswith(search_str)] 248 | match = True if len(match_list) > 0 else False 249 | # search if wildcard search in front 250 | elif st.startswith('*'): 251 | match_list = [x for x in word_list if x.endswith(search_str)] 252 | match = True if len(match_list) > 0 else False 253 | # search if exact match is true 254 | elif self.exact_match: 255 | match = True if search_str in word_list else False 256 | # search with exact match is false 257 | else: 258 | match = True if search_str in str(word_list) else False 259 | matches.append(match) 260 | match = all(matches) if operator == 'AND' else any(matches) 261 | return match 262 | 263 | def notes_search(self, search_terms: list, search_type: str) -> list: 264 | """ 265 | Search with search terms in all markdown files 266 | 267 | Args: 268 | 269 | search_terms (list): Search terms in a list 270 | search_type (str): OR or AND search 271 | 272 | Returns: 273 | 274 | list: list of files matches the search 275 | 276 | """ 277 | file_list = self.getFilesListSorted() 278 | #search_terms = [normalize('NFD', s.decode('utf-8')) for s in search_terms] 279 | new_list = list() 280 | if file_list is not None: 281 | for f in file_list: 282 | content = self._getFileContent(f['path']) 283 | if content != str() and (search_type == 'and' and self._match(search_terms, content, 'AND')) or ( 284 | search_type == 'or' and self._match(search_terms, content, 'OR')): 285 | new_list.append(f) 286 | return new_list 287 | 288 | def url_search(self, search_terms: list) -> list: 289 | """ 290 | Search Notes with bookmarks (URLs) 291 | 292 | Args: 293 | 294 | search_terms (list): Search terms in a list 295 | 296 | Returns: 297 | 298 | list: List of Notes found 299 | 300 | """ 301 | notes = self.notes_search(search_terms, 'and') 302 | note_list = list() 303 | if notes: 304 | for f in notes: 305 | note_title = f['title'] 306 | note_path = f['path'] 307 | content = self._getFileContent(f['path']) 308 | matches = re.findall(r'\[(.*)\]\((https?.*)\)', content) 309 | link_list = list() 310 | # TODO: Implement url only match, links without markdown syntax 311 | # url_only_matches = re.findall(r'https?://', content) 312 | for m in matches: 313 | url_title = m[0] 314 | url = m[1] 315 | link_list.append({'url_title': url_title, 'url': url}) 316 | note_list.append({'title': note_title, 'path': note_path, 'links': link_list}) 317 | return note_list 318 | 319 | def getNoteTitle(self, path: str) -> str: 320 | """ 321 | Get the title of a note 322 | 323 | Args: 324 | 325 | path (str): Full path to note 326 | 327 | Returns: 328 | 329 | str: Title of the note 330 | """ 331 | content = self._getFileContent(path) 332 | title = self._chop(os.path.basename(path), self.extension) 333 | obj = re.search(r'^#{1}\s{1}(.*)', content, re.MULTILINE | re.UNICODE) 334 | if obj is not None: 335 | title = obj.group(1) if len(re.findall(r'\{.*\}', obj.group(1))) == 0 else title 336 | # return title.encode('ascii', 'ignore').decode('ascii') 337 | return title 338 | 339 | @staticmethod 340 | def _chop(theString: str, ext: str) -> str: 341 | if theString.endswith(ext): 342 | return theString[:-len(ext)] 343 | return theString 344 | 345 | def getFileMeta(self, path: str, item: str) -> str: 346 | """ 347 | Get file meta data of given file 348 | 349 | Args: 350 | 351 | path (str): file path 352 | item (str): meta data name 353 | 354 | Returns: 355 | 356 | item str(): Metadata of the file 357 | """ 358 | # os.stat_float_times(True) 359 | file_stats = os.stat(path) 360 | switch = { 361 | 'ctime': file_stats.st_birthtime, 362 | 'mtime': file_stats.st_mtime, 363 | 'size': file_stats.st_size 364 | } 365 | return switch[item] 366 | 367 | def getFilesListSorted(self, reverse: bool = True) -> list: 368 | """ 369 | Get list of files in directory as dict 370 | 371 | Args: 372 | 373 | reverse (boolean): True to sort reverse 374 | 375 | Returns: 376 | 377 | list(dict): sorted dict with file meta information 378 | """ 379 | err = 0 380 | file_list = list() 381 | try: 382 | file_list = os.listdir(self.path) 383 | # file_list = os.walk(self.path) 384 | except OSError as e: 385 | err = e.errno 386 | pass 387 | if err == 0: 388 | seq = list() 389 | for f in file_list: 390 | f_path = os.path.join(self.path, f) 391 | not (f.startswith('.')) and f.endswith(self.extension) and seq.append({ 392 | 'filename': f, 393 | 'path': f_path, 394 | 'title': self.getNoteTitle(f_path), 395 | 'ctime': self.getFileMeta(f_path, 'ctime'), 396 | 'mtime': self.getFileMeta(f_path, 'mtime'), 397 | 'size': self.getFileMeta(f_path, 'size') 398 | }) 399 | sorted_file_list = sorted(seq, key=lambda k: k['mtime'], reverse=reverse) 400 | return sorted_file_list 401 | 402 | def tagSearch(self, tag, sort_by: str = 'tag', reverse: bool = False) -> list: 403 | """ 404 | Search for notes with tag 405 | 406 | Args: 407 | 408 | tag (str): tag to search for in a note 409 | sort_by (str, optional): Sort results by. Defaults to 'tag'. 410 | reverse (bool, optional): Sort reverse. Defaults to False. 411 | 412 | Returns: 413 | 414 | list(dict): results list with dicts 415 | 416 | """ 417 | i = {'tag': 0, 'count': 1} 418 | matches = list() 419 | sorted_file_list = self.getFilesListSorted() 420 | regex = re.compile( 421 | r'#{1}(\w+)\s?', re.I) if tag == '' else re.compile(r'#{1}(' + tag + r'\w*)\s?', re.I | re.UNICODE) 422 | for f in sorted_file_list: 423 | content = self._getFileContent(f['path']) 424 | if content != str(): 425 | if self.search_yaml_tags_only: 426 | match_obj = re.search(r'\bTags:.*', content, re.IGNORECASE | re.UNICODE) 427 | if match_obj: 428 | r = match_obj.group(0) 429 | results = re.findall(regex, r) 430 | matches.extend(results) 431 | else: 432 | results = re.findall(regex, content) 433 | matches.extend(results) 434 | 435 | counted_matches = Counter([v.lower() for v in matches]) 436 | # Sorted by match counter x[1] if sort by key (tag name) is required change to x[0] 437 | sorted_matches = OrderedDict( 438 | sorted(counted_matches.items(), key=lambda x: x[i[sort_by]], reverse=reverse)) 439 | return sorted_matches 440 | 441 | def todoSearch(self, todo: str) -> list: 442 | """ 443 | Search for todos in md notes 444 | 445 | Args: 446 | 447 | todo (str): Search string 448 | 449 | Returns: 450 | 451 | list(dict): returns matches as list with dict 452 | """ 453 | matches = list() 454 | sorted_file_list = self.getFilesListSorted() 455 | regex = re.compile(r'[-|\*] {1}\[ \] {1}(.+)', re.I) if todo == '' else re.compile( 456 | r'[-|\*] {1}\[ \] {1}(.*' + todo + '.+)', re.I) 457 | for f in sorted_file_list: 458 | content = self._getFileContent(f['path']) 459 | if content != str(): 460 | results = re.findall(regex, content) 461 | for i in results: 462 | r_dict = { 463 | 'path': f['path'], 464 | 'todo': i.replace("*", ""), 465 | 'filename': f['filename'], 466 | 'title': f['title'], 467 | 'mtime': self.getFileMeta(f['path'], 'mtime'), 468 | 'ctime': self.getFileMeta(f['path'], 'ctime') 469 | } 470 | matches.append(r_dict) 471 | ret_list_dict = sorted(matches, key=lambda k: k['mtime'], reverse=self.todo_newest_oldest) 472 | return ret_list_dict 473 | 474 | def _getFileContent(self, file_path: str) -> str: 475 | """ 476 | Read file content from md/txt file 477 | 478 | Args: 479 | 480 | file_path (str): Path to file to read 481 | 482 | Returns: 483 | 484 | str: content 485 | """ 486 | if str(file_path).endswith(self.extension): 487 | with open(file_path, 'r') as c: 488 | content = c.read() 489 | else: 490 | content = str() 491 | return content 492 | 493 | def isNoteTagged(self, file_path: str, tag: str) -> bool: 494 | """ 495 | Is the note tagged with tag? 496 | 497 | Args: 498 | 499 | file_path (str): path to note 500 | tag (str): tag to search for 501 | 502 | Returns: 503 | boolean: True if note is tagged otherwise false 504 | """ 505 | match = False 506 | with open(file_path, 'r') as c: 507 | lines = c.readlines()[0:5] 508 | for line in lines: 509 | match_obj = re.search(r'Tags:.*' + tag, line, re.IGNORECASE) 510 | if match_obj: 511 | match = True 512 | break 513 | return match 514 | 515 | @staticmethod 516 | def get_search_config(q: str) -> tuple: 517 | """ 518 | Returns search config tuple 519 | 520 | Args: 521 | 522 | q (string): Search Query e.g. Searchterm1&Searchtem2 523 | 524 | Returns: 525 | 526 | tuple: Search Terms and operator 527 | """ 528 | if '&' in q: 529 | s_terms = q.split('&') 530 | s_type = 'and' 531 | elif '|' in q: 532 | s_terms = q.split('|') 533 | s_type = 'or' 534 | elif q == str(): 535 | s_terms = list() 536 | s_type = 'or' 537 | else: 538 | s_terms = [q] 539 | s_type = 'or' 540 | return s_terms, s_type 541 | 542 | def getUrlScheme(self, f: str) -> str: 543 | """ 544 | Gets the URL Scheme setup in Alfred Preferences 545 | 546 | Args: 547 | f(str): md file to add at the end of url scheme 548 | 549 | Returns: 550 | str: URL scheme 551 | """ 552 | return self.strJoin(self.url_scheme, pathname2url(f)) 553 | 554 | 555 | class NewNote(Notes): 556 | """ 557 | Creates a new note with title, template and tags 558 | 559 | Args: 560 | 561 | note_title (str): Title of the Note 562 | template_path (str): Path to the template used 563 | tags (str): Tag line with format: #tag1 #tag2 564 | content (str): Addtional content after Headline 565 | 566 | """ 567 | 568 | def __init__(self, note_title, template_path=str(), tags=str(), content=str()): 569 | super(NewNote, self).__init__() 570 | self.filename_format = self.getFilenameFormat() 571 | self.tags = tags 572 | self.content = content 573 | self.note_title = note_title 574 | self.note_path = self.getTargetFilePath(self.__normalize_filename(note_title)) 575 | self.template_path = self.getTemplate(template_path) 576 | 577 | def getFilenameFormat(self): 578 | """ 579 | Get fileformat from WF env 580 | 581 | Returns: 582 | 583 | str: fileformat or fallback 584 | """ 585 | frmt_env = Tools.getEnv('filename_format') 586 | if frmt_env is None or frmt_env.strip() == "": 587 | frmt_env = '{title}' 588 | return frmt_env 589 | 590 | def getTargetFilePath(self, file_name: str) -> str: 591 | """ 592 | construct markdown file path 593 | 594 | Returns: 595 | 596 | str: markdown file path 597 | """ 598 | def applyFilenameFormat(title: str): 599 | """Appliies configured Fileformat to filename""" 600 | frmt = self.filename_format 601 | res = re.findall(r"\{[\.\-:%a-zA-Z]*\}", frmt) 602 | for r in res: 603 | if "%" in r: 604 | date_f = r.replace('{', "").replace('}', "") 605 | dte = self.getTodayDate(date_f) 606 | frmt = frmt.replace(r, dte) 607 | elif '{title}' in res: 608 | frmt = frmt.replace(r, title) 609 | return frmt 610 | 611 | file_name = file_name.rstrip().lstrip() 612 | file_name = applyFilenameFormat(file_name) 613 | file_path = os.path.join(self.path, f"{file_name}{self.extension}") 614 | if os.path.isfile(file_path): 615 | new_file_name = Tools.strJoin(file_name, ' ', self.getTodayDate('%d-%m-%Y %H-%M-%S')) 616 | file_path = os.path.join(self.path, f"{new_file_name}{self.extension}") 617 | return file_path 618 | 619 | def getDefaultTemplate(self) -> str: 620 | """ 621 | Read default template setting from environment variable 622 | 623 | Returns: 624 | 625 | str: default template file name 626 | """ 627 | return 'template.md' if self.default_template == str() else self.default_template 628 | 629 | def getTemplate(self, template_path: str) -> str: 630 | """ 631 | Get template path from previous wf step, reads env variable 632 | 633 | Returns: 634 | 635 | str: path to template.md 636 | """ 637 | notes_path = self.path 638 | default_template = self.getDefaultTemplate() 639 | return os.path.join(notes_path, default_template) if template_path == str() else template_path 640 | 641 | def readTemplate(self, **kwargs: str) -> str: 642 | """ 643 | Read template markdkown file and fill placeholder defined in template 644 | with data provides as kwargs 645 | 646 | Args: 647 | 648 | file_path (str): Path to Template file 649 | 650 | Returns: 651 | 652 | str: Content 653 | """ 654 | if '#' not in self.template_tag or self.template_tag == str(): 655 | self.template_tag = '#Template' 656 | if os.path.exists(self.template_path): 657 | with open(self.template_path, "r") as f: 658 | content = f.read() 659 | else: 660 | content = self.FALLBACK_CONTENT 661 | content = content.replace(self.template_tag, '') 662 | for k, v in kwargs.items(): 663 | content = content.replace('{' + k + '}', v) 664 | tag_line = f'Tags: {self.tags} ' 665 | if self.tags: 666 | content = content.replace('Tags: ', tag_line) 667 | return content 668 | 669 | def __normalize_filename(self, f: str) -> str: 670 | """ 671 | Replace special characters in filename of md file 672 | 673 | Returns: 674 | 675 | str: filename 676 | """ 677 | return self.strReplace(f, self.CHAR_REPLACEMENT_MAP, lowercase=False) 678 | 679 | def createNote(self) -> str: 680 | """ 681 | Creates the markdown note 682 | 683 | Returns: 684 | 685 | str: full path to notes 686 | """ 687 | try: 688 | with open(self.note_path, "w+") as f: 689 | default_date = self.getDefaultDate() 690 | file_content = self.readTemplate( 691 | date=self.getTodayDate(default_date), title=self.note_title) 692 | file_content = f"{file_content}\n{self.content}" if self.content else file_content 693 | f.write(file_content) 694 | return self.note_path 695 | except IOError as e: 696 | sys.stderr.write(e) 697 | -------------------------------------------------------------------------------- /src/QuerySplitter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | class QuerySplitter(object): 3 | """ 4 | Split Query into title and tags 5 | 6 | Args: 7 | 8 | object (str): Query string 9 | """ 10 | 11 | def __init__(self, query): 12 | self.title = str() 13 | self.tag_list = list() 14 | self.tags = str() 15 | self._split(query) 16 | 17 | def _split(self, query): 18 | term_list = query.split(' ') 19 | title_list = list() 20 | self.tag_list = list() 21 | for t in term_list: 22 | if str(t).startswith('#'): 23 | self.tag_list.append(t) 24 | else: 25 | title_list.append(t) 26 | self.title = ' '.join(title_list) 27 | self.tags = ' '.join(self.tag_list) 28 | -------------------------------------------------------------------------------- /src/asset_upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | from shutil import copy2 6 | from urllib.request import pathname2url 7 | 8 | from MyNotes import Search 9 | 10 | ASSETS_FOLDER = '_NoteAssets' 11 | 12 | 13 | def getAssetsFolder(): 14 | """ 15 | Get Assets Upload Folder from config 16 | 17 | Returns: 18 | 19 | str: Asset Folder for md Notes 20 | """ 21 | my_notes = Search() 22 | notes_path = my_notes.getNotesPath() 23 | assets_path = os.path.join(notes_path, ASSETS_FOLDER) 24 | if not os.path.exists(assets_path): 25 | os.mkdir(assets_path) 26 | return assets_path 27 | 28 | 29 | def copyFile(source, target): 30 | """ 31 | Copy file to target folder 32 | 33 | Args: 34 | 35 | source (str): Source File path 36 | target (str): Target folder 37 | 38 | Raises: 39 | 40 | ValueError: if source file does not exists 41 | 42 | Returns: 43 | 44 | str: path to file after copied 45 | """ 46 | file_name = str() 47 | if os.path.isfile(source): 48 | copy2(source, target, follow_symlinks=True) 49 | file_name = os.path.basename(source) 50 | else: 51 | raise ValueError 52 | return os.path.join(ASSETS_FOLDER, file_name) 53 | 54 | 55 | source_file = sys.argv[1] # File path to asset 56 | 57 | target_folder = getAssetsFolder() 58 | asset_file = copyFile(source_file, target_folder) 59 | 60 | file_url = pathname2url(asset_file) 61 | asset_file = os.path.basename(asset_file) 62 | md_link = f"[{asset_file}]({file_url})" 63 | 64 | sys.stdout.write(md_link) 65 | -------------------------------------------------------------------------------- /src/create_index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import sys 4 | from urllib.request import pathname2url 5 | 6 | from Alfred3 import Tools 7 | from MyNotes import NewNote, Search 8 | from QuerySplitter import QuerySplitter 9 | 10 | 11 | def get_mdfiles_list_content() -> str: 12 | """ 13 | Get markdown links in unordered markdown list 14 | 15 | Returns: 16 | 17 | str: Markdown unordered list 18 | 19 | """ 20 | ns = Search() 21 | output = list() 22 | files = os.getenv('files').split('|') # one or list of md file paths from prev wf step 23 | for f in files: 24 | link_title = ns.getNoteTitle(f) 25 | file_name = os.path.basename(f) 26 | output.append(f'* [{link_title}]({pathname2url(file_name)})') 27 | return '\n'.join(output) 28 | 29 | 30 | query = Tools.getArgv(1) # Title of the Note 31 | qs = QuerySplitter(query) 32 | 33 | MyNote = NewNote(qs.title, tags=qs.tags, content=get_mdfiles_list_content()) 34 | md_path = MyNote.createNote() 35 | sys.stdout.write(md_path) 36 | -------------------------------------------------------------------------------- /src/create_note.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | 5 | from Alfred3 import Tools 6 | from MyNotes import NewNote 7 | from QuerySplitter import QuerySplitter 8 | 9 | query = Tools.getArgv(1) # Note title, can contain Tags → #TAG 10 | template = Tools.getEnv('template_path') # Read template path from previous wf step in case template ws choosen 11 | if query.isspace(): # if query contains a SPACE aka nothing was entered 12 | today = NewNote.getTodayDate(fmt='%d-%m-%Y %H-%M-%S') 13 | query = f"My Note {today}" 14 | qs = QuerySplitter(query) 15 | 16 | if query: 17 | Note = NewNote(qs.title, template_path=template, tags=qs.tags) 18 | fPath = Note.createNote() 19 | sys.stdout.write(fPath) 20 | -------------------------------------------------------------------------------- /src/delete_note.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import re 5 | import shutil 6 | import sys 7 | from urllib.request import url2pathname 8 | 9 | from Alfred3 import Tools as Tools 10 | from MyNotes import Search 11 | 12 | 13 | def rmDir(path: str, ignore_errors: bool = True) -> bool: 14 | """ 15 | Remove directory and it's content 16 | 17 | Args: 18 | 19 | path (string): path to specific markdown notes 20 | ignore_errors (bool, optional): Ignore errors. Defaults to True. 21 | 22 | Returns: 23 | 24 | bool: True in case removal was successful otherwise False 25 | """ 26 | if os.path.exists(path): 27 | shutil.rmtree(path, ignore_errors) 28 | Tools.log(f"DELETED DIR: {path}") 29 | return not (os.path.exists(path)) 30 | else: 31 | return False 32 | 33 | 34 | def rmFile(path: str) -> bool: 35 | """ 36 | 37 | Remove file of a given path if 38 | 39 | Returns: 40 | 41 | bool: True if successful other False 42 | """ 43 | path = url2pathname(path) 44 | if os.path.isfile(path) and os.path.exists(path): 45 | os.remove(path) 46 | return not (os.path.exists(path)) 47 | else: 48 | return False 49 | 50 | 51 | def getFileQuery(q: str) -> list: 52 | ret = q.split('>') if '>' in q else [q, str()] 53 | return ret 54 | 55 | 56 | def getAssetsLinks(parent_path: str, p: str) -> list: 57 | def is_in_notes(f_path): 58 | return not (str(f_path).startswith('..')) and not (str(f_path).startswith('/')) 59 | with open(p, 'r') as f: 60 | content = f.read() 61 | matches = re.findall(r'\[.*\]\((.*)\)', content) 62 | return [os.path.join(parent_path, m) for m in matches if is_in_notes(m)] 63 | 64 | 65 | def get_arguments() -> list: 66 | # Get all files which needs to be deleted from input 67 | ret_value = sys.argv[1:] 68 | # split if files list was provided with | seprator in argv 69 | if '|' in ret_value[0]: 70 | ret_value = ret_value[0].split('|') 71 | return ret_value 72 | 73 | 74 | mn = Search() 75 | 76 | # Load extentions env variables from settings 77 | ext = mn.getNotesExtension() 78 | files_to_delete = get_arguments() 79 | 80 | 81 | return_text = str() 82 | for query in files_to_delete: 83 | file_path, last_query = getFileQuery(query) 84 | if os.path.isfile(file_path) and file_path.endswith(ext): 85 | file_name = os.path.basename(file_path) 86 | # Search for links to other assets and delete each file 87 | parent = mn.getNotesPath() 88 | assetfile_links = getAssetsLinks(parent, file_path) 89 | is_assetfile_deleted = False 90 | for alink in assetfile_links: 91 | # Avoid Markdown file removal 92 | if not alink.endswith(ext): 93 | is_assetfile_deleted = rmFile(alink) 94 | 95 | # Delete Assets Folder 96 | remove_ext = len(ext) 97 | assets_path = Tools.strJoin(file_path[:-remove_ext], ".assets") 98 | assets_path_legacy = Tools.strJoin(file_path[:-remove_ext]) 99 | is_asset_deleted = rmDir(assets_path) or rmDir(assets_path_legacy) or is_assetfile_deleted 100 | 101 | # Finally delete the MD File 102 | is_file_deleted = rmFile(file_path) 103 | 104 | # Create Notification Message 105 | if len(files_to_delete) == 1: 106 | return_text = '- MD Note DELETED'if is_file_deleted else "Cannot delete file: {0}".format( 107 | file_name) 108 | return_text += '\n- Assets DELETED' if is_asset_deleted else str() 109 | 110 | if len(files_to_delete) > 1: 111 | return_text = f"{len(files_to_delete)} Notes and coresponding Assets deleted" 112 | 113 | Tools.notify('MD Note deleted!', return_text) 114 | 115 | sys.stdout.write(last_query) 116 | -------------------------------------------------------------------------------- /src/docs/bookmark_tag.md: -------------------------------------------------------------------------------- 1 | **Bookmark Tag** (`bookmark_tag`) 2 | 3 | Name of the tag which marks Notes containing URL/Bookmarks. 4 | 5 | -------------------------------------------------------------------------------- /src/docs/default_date_format.md: -------------------------------------------------------------------------------- 1 | **Default Date Format** (`default_date_format`) 2 | 3 | Defines date format when creating new notes or when using placeholders in templates: `{date}` e.g. `%d.%m.%Y %H.%M` 4 | 5 | -------------------------------------------------------------------------------- /src/docs/default_template.md: -------------------------------------------------------------------------------- 1 | **Default Template** (`default_template`) 2 | 3 | The filename of the template default that will be used when creating new Notes e.g. `Template.md` 4 | 5 | **Note**: Enter the file name ONLY without path e.g. `myTemplate.md` 6 | 7 | -------------------------------------------------------------------------------- /src/docs/exact_match.md: -------------------------------------------------------------------------------- 1 | **Exact Match** (`exact_match`) 2 | 3 | Defines if the search should match the exact search term (`True`) or the string (`False`) in markdown notes. 4 | 5 | **Note:** When exact match is set to `True` it is possible to enhance the search term with wildcards 6 | 7 | **Examples** 8 | 9 | * Exact Search is enabled, search `Books` will not match `Bookstore` but `Books`. But `Books*` will match `Bookstore` as well. 10 | * Ecact Search is disabled, search `Books` will macht `Books` as well as `Bookstore` -------------------------------------------------------------------------------- /src/docs/ext.md: -------------------------------------------------------------------------------- 1 | **Extension** (`ext`) 2 | 3 | The md files are text files with a specific extension (usually `.txt` or `.md`) but you can setup whatever extension you prefer. 4 | 5 | **Note:** The files must be text files -------------------------------------------------------------------------------- /src/docs/filename_format.md: -------------------------------------------------------------------------------- 1 | **Filename Format** (`filename_format`): Standard file format for new MD notes. Default is the title of the notes but in some cases it is useful to add a date format e.g. when using Zettelkasten file format. 2 | The two placeholders can be used: 3 | 4 | * e.g. `{%d-%m-%Y}` or any other strftime format. 5 | ***Be careful when using strftime format. Wrong format result in wrong file names!*** 6 | * `{title}`: The title of the MD Note 7 | * Example: `{title}-{%d%m%Y}` 8 | 9 | -------------------------------------------------------------------------------- /src/docs/path_to_notes.md: -------------------------------------------------------------------------------- 1 | **Path to Notes** (`path_to_notes`) 2 | 3 | The path where MD Notes will be stored. 4 | The path can be absolut or relative but has to be a user directory! 5 | 6 | **Examples:** 7 | 8 | * `/Users/yourname/Dropbox/Notes` → works 9 | * `yourname/Dropbox/Notes` → works 10 | * `/yourname/Dropbox/Notes` → works 11 | * `/Volumes/usb` → will not work! -------------------------------------------------------------------------------- /src/docs/search_yaml_tags_only.md: -------------------------------------------------------------------------------- 1 | **Search in Tags in YMF only** (`search_yaml_tags_only`) 2 | 3 | Tags can be used in YAML front matter (`Tags: #mytag`) or within the MD note. 4 | 5 | 1. When set to `True` tag search only search with YMF. 6 | 2. When set to `False` tags will be searched the whole MD note. 7 | -------------------------------------------------------------------------------- /src/docs/template_tag.md: -------------------------------------------------------------------------------- 1 | **Template Tag** (`template_tag`) 2 | 3 | The template tag defines which `#Tagname`) defines a Template. Once you created a template just add template tag name to the MD Note and it will be recognized when you create a new MD Note from Template. -------------------------------------------------------------------------------- /src/docs/todo_newest_oldest.md: -------------------------------------------------------------------------------- 1 | **Todo sort order** (todo_newest_oldest): Sort order when using Todo Search ( `mdt`): 2 | 3 | * `True` newest todos will be shown on top of the search results 4 | * `False` oldest todos will be shown on top of the search results -------------------------------------------------------------------------------- /src/docs/url_scheme.md: -------------------------------------------------------------------------------- 1 | **URL Scheme** (`url_scheme`) 2 | 3 | Web applications does not allow to open file paths. To work around the restrictions a URL Scheme for MD editors help to open the Note in e.g. Marked 2 or iAWriter. 4 | 5 | **Example by using Marked 2:** 6 | Marked is using the URL scheme `x-marked://open/?file=`. The value for `url_scheme` looks like: `x-marked://open/?file=` -------------------------------------------------------------------------------- /src/get_md_link.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | from urllib.request import pathname2url 6 | 7 | from MyNotes import Search 8 | 9 | query = sys.argv[1] # File path to MD Note 10 | file_name = os.path.basename(query) 11 | ms = Search() 12 | title = ms.getNoteTitle(query) 13 | output = f"[{title}]({pathname2url(file_name)})" 14 | sys.stdout.write(output) 15 | -------------------------------------------------------------------------------- /src/html_fetch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import os 6 | import re 7 | import sys 8 | from html.parser import HTMLParser 9 | from urllib.request import unquote, urlopen 10 | 11 | from Alfred3 import Tools 12 | from MyNotes import Search 13 | 14 | 15 | class Markdown(object): 16 | 17 | PANDOC = 'pandoc -f html -t markdown_mmd --strip-comments' 18 | 19 | def __init__(self, url): 20 | self.url = url 21 | self.html = self._fetchHtml() 22 | self.md = self._fetchMd() 23 | self.mdPageUrl = f"[{url}]({url})" 24 | 25 | def _fetchHtml(self): 26 | try: 27 | r = urlopen(self.url) 28 | response = r.read().decode('utf-8') 29 | except: 30 | response = f"{self.url}" 31 | pass 32 | return response 33 | 34 | def getMdUrl(self): 35 | return self.mdPageUrl 36 | 37 | def _fetchMd(self): 38 | try: 39 | cmd = f'{self.PANDOC} {self.url}' 40 | md = os.popen(cmd) 41 | resp = md.read() 42 | except: 43 | resp = f"[{self.url}]({self.url})" 44 | pass 45 | return resp 46 | 47 | def getMd(self): 48 | return self.md 49 | 50 | def getHtml(self): 51 | return self.html 52 | 53 | def getTitle(self): 54 | res = re.findall( 55 | r'[\n\t\s]*(.+)[\n\t\s]*', self.html, re.MULTILINE) 56 | return ''.join(res) 57 | 58 | def _markdownHeader(self): 59 | today = self.getTodayDate(fmt="%d.%m.%Y") 60 | return f"""---\nCreated: {today}\nTags: #WebClip\n---\n""" 61 | 62 | def getMarkdownContent(self): 63 | out = self._markdownHeader() 64 | out += self.getMd() 65 | out += u'\n---\n' 66 | out += self.getMdUrl() 67 | return out 68 | 69 | @staticmethod 70 | def getTodayDate(fmt="%d.%m.%Y"): 71 | now = datetime.datetime.now() 72 | return now.strftime(fmt) 73 | 74 | 75 | def parseFilename(f): 76 | to_replace = ['/', '\\', ':', '|'] 77 | tmp = f.strip() 78 | for i in to_replace: 79 | tmp = tmp.replace(i, '-') 80 | return tmp 81 | 82 | 83 | def writeMarkdown(md_content, md_path): 84 | with open(md_path, "w+") as f: 85 | f.write(md_content) 86 | 87 | 88 | mn = Search() 89 | ext = mn.getNotesExtension() 90 | p = mn.getNotesPath() 91 | argv = Tools.getArgv(1) 92 | url = argv if argv.startswith( 93 | 'http://') or argv.startswith('https://') else str() 94 | 95 | # TODO: When HTML is not fetchable, the URL will be used. 96 | # Fix formatting from to markdown url [title](url) 97 | 98 | if url: 99 | Tools.notify('Fetch URL...', 'The Note will be opened after the import...') 100 | markdown = Markdown(url) 101 | today = markdown.getTodayDate(fmt="%d.%m.%Y") 102 | today_time = markdown.getTodayDate(fmt="%d-%m-%Y %H-%M") 103 | md = markdown.getMarkdownContent() 104 | file_name = parseFilename(markdown.getTitle()) 105 | if file_name == str(): 106 | file_name = Tools.strJoin('WebClip from ', today_time) 107 | 108 | fPath = os.path.join(p, f"{file_name}{ext}") 109 | writeMarkdown(md, fPath) 110 | sys.stdout.write(fPath) 111 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icon.png -------------------------------------------------------------------------------- /src/icons/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/action.png -------------------------------------------------------------------------------- /src/icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/back.png -------------------------------------------------------------------------------- /src/icons/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/check.png -------------------------------------------------------------------------------- /src/icons/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/clipboard.png -------------------------------------------------------------------------------- /src/icons/colors.txt: -------------------------------------------------------------------------------- 1 | green: #88a317 2 | red: E93F33 3 | standard: #37b6ff -------------------------------------------------------------------------------- /src/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/delete.png -------------------------------------------------------------------------------- /src/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/edit.png -------------------------------------------------------------------------------- /src/icons/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/hand.png -------------------------------------------------------------------------------- /src/icons/hashtag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/hashtag.png -------------------------------------------------------------------------------- /src/icons/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/link.png -------------------------------------------------------------------------------- /src/icons/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/markdown.png -------------------------------------------------------------------------------- /src/icons/marked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/marked.png -------------------------------------------------------------------------------- /src/icons/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/paste.png -------------------------------------------------------------------------------- /src/icons/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/question.png -------------------------------------------------------------------------------- /src/icons/scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/scheme.png -------------------------------------------------------------------------------- /src/icons/unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acidham/alfred-markdown-notes/7b196d905705f1c470edaef1c222ae91f29c99eb/src/icons/unchecked.png -------------------------------------------------------------------------------- /src/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.apple.alfred.workflow.mdnotes 7 | category 8 | Productivity 9 | connections 10 | 11 | 1226368E-7C02-479A-958B-6147F924D9F9 12 | 13 | 14 | destinationuid 15 | 93FCC3FA-C642-48CF-BE86-9C5EDAEE07D4 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | destinationuid 25 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 26 | modifiers 27 | 0 28 | modifiersubtext 29 | 30 | sourceoutputuid 31 | 298C21F0-249C-4C74-BFBC-0BE1FF83AB81 32 | vitoclose 33 | 34 | 35 | 36 | 18A8C2F0-9E83-42B2-9FFA-04793FDE69AF 37 | 38 | 39 | destinationuid 40 | BAFF7A0F-AAD1-416B-9279-C500DEA630C3 41 | modifiers 42 | 0 43 | modifiersubtext 44 | 45 | vitoclose 46 | 47 | 48 | 49 | 294CB549-D1EE-4AC0-BF62-B19E2DAD7162 50 | 51 | 52 | destinationuid 53 | F993108F-7C6F-4AF3-90D3-14A1CBAAEBBE 54 | modifiers 55 | 0 56 | modifiersubtext 57 | 58 | vitoclose 59 | 60 | 61 | 62 | 2F777EDC-8764-4AE9-AFC2-65D224BB01DE 63 | 64 | 65 | destinationuid 66 | 294CB549-D1EE-4AC0-BF62-B19E2DAD7162 67 | modifiers 68 | 0 69 | modifiersubtext 70 | 71 | vitoclose 72 | 73 | 74 | 75 | 2FA91C3B-4CED-410B-B2A6-C4BCBC9D6F41 76 | 77 | 78 | destinationuid 79 | 735B5B72-0876-4143-887E-D8191A48154E 80 | modifiers 81 | 0 82 | modifiersubtext 83 | 84 | vitoclose 85 | 86 | 87 | 88 | 454BC87F-9E2E-4C7B-ABFA-2E7709D59244 89 | 90 | 91 | destinationuid 92 | DAFC178B-CF94-4ED2-9E86-4ADA5CB6D961 93 | modifiers 94 | 0 95 | modifiersubtext 96 | 97 | vitoclose 98 | 99 | 100 | 101 | 49185B13-47A9-4B6A-9FF3-7423A9E173EB 102 | 103 | 104 | destinationuid 105 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 106 | modifiers 107 | 0 108 | modifiersubtext 109 | 110 | vitoclose 111 | 112 | 113 | 114 | 49A9F878-DBE6-4A7F-B6A0-FEFD5D77393A 115 | 116 | 117 | destinationuid 118 | DB11D78A-2543-48A3-B195-09CC58045785 119 | modifiers 120 | 0 121 | modifiersubtext 122 | 123 | vitoclose 124 | 125 | 126 | 127 | 4C847DCB-008B-4241-A1C4-35B3895092F7 128 | 129 | 130 | destinationuid 131 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 132 | modifiers 133 | 0 134 | modifiersubtext 135 | 136 | vitoclose 137 | 138 | 139 | 140 | 5739EC91-287F-41C7-8DD5-71C3772036CD 141 | 142 | 143 | destinationuid 144 | E821ABDF-E08D-4BAE-83F0-4F24F4AB7B8C 145 | modifiers 146 | 0 147 | modifiersubtext 148 | 149 | vitoclose 150 | 151 | 152 | 153 | 616E1777-6C1F-406D-B16B-697EA6E8DDA0 154 | 155 | 156 | destinationuid 157 | C277BA58-113D-4741-A306-9A1AAAE9EA61 158 | modifiers 159 | 0 160 | modifiersubtext 161 | 162 | vitoclose 163 | 164 | 165 | 166 | 64388804-3C22-4CB8-8A61-5E2DC6EBF4FD 167 | 168 | 169 | destinationuid 170 | C277BA58-113D-4741-A306-9A1AAAE9EA61 171 | modifiers 172 | 0 173 | modifiersubtext 174 | 175 | vitoclose 176 | 177 | 178 | 179 | 661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4 180 | 181 | 182 | destinationuid 183 | 2F777EDC-8764-4AE9-AFC2-65D224BB01DE 184 | modifiers 185 | 1048576 186 | modifiersubtext 187 | 188 | vitoclose 189 | 190 | 191 | 192 | destinationuid 193 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 194 | modifiers 195 | 0 196 | modifiersubtext 197 | 198 | vitoclose 199 | 200 | 201 | 202 | 73F51F35-8DE7-4E97-A18D-ADA7046EB4A7 203 | 204 | 205 | destinationuid 206 | FA2A221A-6386-4B79-950C-7C46E10196EE 207 | modifiers 208 | 0 209 | modifiersubtext 210 | 211 | vitoclose 212 | 213 | 214 | 215 | 7EF41C6D-4E2F-4BED-B733-B3538C647263 216 | 217 | 218 | destinationuid 219 | A1994305-42C8-4C6A-BB26-0D822F6B021A 220 | modifiers 221 | 0 222 | modifiersubtext 223 | 224 | vitoclose 225 | 226 | 227 | 228 | 8354F261-D7A0-4848-8312-9EC40100DFA6 229 | 230 | 231 | destinationuid 232 | 2F777EDC-8764-4AE9-AFC2-65D224BB01DE 233 | modifiers 234 | 1048576 235 | modifiersubtext 236 | 237 | vitoclose 238 | 239 | 240 | 241 | destinationuid 242 | 1226368E-7C02-479A-958B-6147F924D9F9 243 | modifiers 244 | 0 245 | modifiersubtext 246 | 247 | vitoclose 248 | 249 | 250 | 251 | 867A6B07-A86D-464E-9AA2-F4F85D33901E 252 | 253 | 254 | destinationuid 255 | E9AAB61A-3037-496F-B9A7-1D9166D62D6D 256 | modifiers 257 | 0 258 | modifiersubtext 259 | 260 | vitoclose 261 | 262 | 263 | 264 | 88BD02F5-4A73-4701-98D0-450B1F338238 265 | 266 | 267 | destinationuid 268 | 8354F261-D7A0-4848-8312-9EC40100DFA6 269 | modifiers 270 | 0 271 | modifiersubtext 272 | 273 | vitoclose 274 | 275 | 276 | 277 | 898E9FE0-5165-4530-98E9-9497DDF6DA0D 278 | 279 | 280 | destinationuid 281 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 282 | modifiers 283 | 0 284 | modifiersubtext 285 | 286 | vitoclose 287 | 288 | 289 | 290 | destinationuid 291 | 8BFF5D9F-F5E7-4CC8-AC7D-C3DAA406DA19 292 | modifiers 293 | 524288 294 | modifiersubtext 295 | 296 | vitoclose 297 | 298 | 299 | 300 | destinationuid 301 | 01EBC50C-380F-4FA3-B537-D755B7134966 302 | modifiers 303 | 0 304 | modifiersubtext 305 | 306 | vitoclose 307 | 308 | 309 | 310 | 8BFF5D9F-F5E7-4CC8-AC7D-C3DAA406DA19 311 | 312 | 313 | destinationuid 314 | 0615CE75-2C53-400F-BD6E-FC6A98EE6F0A 315 | modifiers 316 | 0 317 | modifiersubtext 318 | 319 | vitoclose 320 | 321 | 322 | 323 | 8D265164-F63E-461B-B67B-BA20069C5E5F 324 | 325 | 326 | destinationuid 327 | A1994305-42C8-4C6A-BB26-0D822F6B021A 328 | modifiers 329 | 0 330 | modifiersubtext 331 | 332 | vitoclose 333 | 334 | 335 | 336 | destinationuid 337 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 338 | modifiers 339 | 0 340 | modifiersubtext 341 | 342 | sourceoutputuid 343 | E4890EEF-C064-437F-84D4-B238D82C5B92 344 | vitoclose 345 | 346 | 347 | 348 | 8F8F1A2F-E9D7-4EF4-B3C9-3BE1B75DEBFB 349 | 350 | 351 | destinationuid 352 | 9CFA7349-15E0-4F12-8E6D-251360FC1EAC 353 | modifiers 354 | 0 355 | modifiersubtext 356 | 357 | vitoclose 358 | 359 | 360 | 361 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 362 | 363 | 364 | destinationuid 365 | EAEF0CF6-8AEA-4B7D-8EAB-80FC16EDDAAC 366 | modifiers 367 | 0 368 | modifiersubtext 369 | 370 | vitoclose 371 | 372 | 373 | 374 | 9CFA7349-15E0-4F12-8E6D-251360FC1EAC 375 | 376 | 377 | destinationuid 378 | 7EF41C6D-4E2F-4BED-B733-B3538C647263 379 | modifiers 380 | 0 381 | modifiersubtext 382 | 383 | sourceoutputuid 384 | 3961D0C5-8015-4EDE-8753-F201EB4B11C1 385 | vitoclose 386 | 387 | 388 | 389 | destinationuid 390 | D5001574-5650-42E7-84FC-A51EAA4CE8D1 391 | modifiers 392 | 0 393 | modifiersubtext 394 | 395 | sourceoutputuid 396 | 1B17EF3D-7C92-4BD1-BCC3-C88806025AA7 397 | vitoclose 398 | 399 | 400 | 401 | destinationuid 402 | 2FA91C3B-4CED-410B-B2A6-C4BCBC9D6F41 403 | modifiers 404 | 0 405 | modifiersubtext 406 | 407 | sourceoutputuid 408 | 325682B6-E189-423E-A2E9-2D12608FDA53 409 | vitoclose 410 | 411 | 412 | 413 | destinationuid 414 | F92C44A1-5C7A-4863-9FC1-F0188FF998E3 415 | modifiers 416 | 0 417 | modifiersubtext 418 | 419 | sourceoutputuid 420 | 6D724BD9-BEF7-4D28-8DCC-A594E968CD3E 421 | vitoclose 422 | 423 | 424 | 425 | destinationuid 426 | 73F51F35-8DE7-4E97-A18D-ADA7046EB4A7 427 | modifiers 428 | 0 429 | modifiersubtext 430 | 431 | sourceoutputuid 432 | 03A97A98-84AF-47FA-BD7A-F5BD9090159F 433 | vitoclose 434 | 435 | 436 | 437 | A1994305-42C8-4C6A-BB26-0D822F6B021A 438 | 439 | 440 | destinationuid 441 | 735B5B72-0876-4143-887E-D8191A48154E 442 | modifiers 443 | 0 444 | modifiersubtext 445 | 446 | vitoclose 447 | 448 | 449 | 450 | AE883F79-FCA1-448E-AF9A-D8FF94C93552 451 | 452 | 453 | destinationuid 454 | 8D265164-F63E-461B-B67B-BA20069C5E5F 455 | modifiers 456 | 0 457 | modifiersubtext 458 | 459 | vitoclose 460 | 461 | 462 | 463 | BAFF7A0F-AAD1-416B-9279-C500DEA630C3 464 | 465 | 466 | destinationuid 467 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 468 | modifiers 469 | 0 470 | modifiersubtext 471 | 472 | vitoclose 473 | 474 | 475 | 476 | BC9416FD-DA8C-4E89-BB77-2300E23FC2AB 477 | 478 | 479 | destinationuid 480 | 18A8C2F0-9E83-42B2-9FFA-04793FDE69AF 481 | modifiers 482 | 0 483 | modifiersubtext 484 | 485 | vitoclose 486 | 487 | 488 | 489 | C277BA58-113D-4741-A306-9A1AAAE9EA61 490 | 491 | 492 | destinationuid 493 | 49185B13-47A9-4B6A-9FF3-7423A9E173EB 494 | modifiers 495 | 0 496 | modifiersubtext 497 | 498 | vitoclose 499 | 500 | 501 | 502 | C316AE82-592A-4D24-95D1-D889B41122DB 503 | 504 | 505 | destinationuid 506 | 4C847DCB-008B-4241-A1C4-35B3895092F7 507 | modifiers 508 | 0 509 | modifiersubtext 510 | 511 | vitoclose 512 | 513 | 514 | 515 | CCE98113-EE5F-4540-B377-3C9A9DA1AC6D 516 | 517 | 518 | destinationuid 519 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 520 | modifiers 521 | 0 522 | modifiersubtext 523 | 524 | vitoclose 525 | 526 | 527 | 528 | D152D8AE-984F-47E0-9BBD-59DDE719EEDC 529 | 530 | 531 | destinationuid 532 | E1E655F3-B8EB-4324-8B9D-25D3AC743045 533 | modifiers 534 | 0 535 | modifiersubtext 536 | 537 | vitoclose 538 | 539 | 540 | 541 | D5001574-5650-42E7-84FC-A51EAA4CE8D1 542 | 543 | 544 | destinationuid 545 | A35A4CC5-F476-4228-8687-881419DA8B60 546 | modifiers 547 | 0 548 | modifiersubtext 549 | 550 | vitoclose 551 | 552 | 553 | 554 | DAFC178B-CF94-4ED2-9E86-4ADA5CB6D961 555 | 556 | 557 | destinationuid 558 | A1994305-42C8-4C6A-BB26-0D822F6B021A 559 | modifiers 560 | 0 561 | modifiersubtext 562 | 563 | vitoclose 564 | 565 | 566 | 567 | DB11D78A-2543-48A3-B195-09CC58045785 568 | 569 | 570 | destinationuid 571 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 572 | modifiers 573 | 0 574 | modifiersubtext 575 | 576 | sourceoutputuid 577 | 30E2F874-FC82-4DF3-9991-38F5337D9CAB 578 | vitoclose 579 | 580 | 581 | 582 | destinationuid 583 | E3B5F19A-B7EC-4984-A3CA-D626DB05ADF5 584 | modifiers 585 | 0 586 | modifiersubtext 587 | 588 | vitoclose 589 | 590 | 591 | 592 | E1E655F3-B8EB-4324-8B9D-25D3AC743045 593 | 594 | 595 | destinationuid 596 | 02B9FF6D-1CCA-424C-BEF2-FF6C2F753585 597 | modifiers 598 | 1048576 599 | modifiersubtext 600 | 601 | vitoclose 602 | 603 | 604 | 605 | destinationuid 606 | 8354F261-D7A0-4848-8312-9EC40100DFA6 607 | modifiers 608 | 0 609 | modifiersubtext 610 | 611 | vitoclose 612 | 613 | 614 | 615 | E6223AE8-EC41-40DA-88AC-8476D6F73ED1 616 | 617 | 618 | destinationuid 619 | 49A9F878-DBE6-4A7F-B6A0-FEFD5D77393A 620 | modifiers 621 | 0 622 | modifiersubtext 623 | 624 | vitoclose 625 | 626 | 627 | 628 | E821ABDF-E08D-4BAE-83F0-4F24F4AB7B8C 629 | 630 | 631 | destinationuid 632 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 633 | modifiers 634 | 0 635 | modifiersubtext 636 | 637 | vitoclose 638 | 639 | 640 | 641 | E9AAB61A-3037-496F-B9A7-1D9166D62D6D 642 | 643 | 644 | destinationuid 645 | BC9416FD-DA8C-4E89-BB77-2300E23FC2AB 646 | modifiers 647 | 0 648 | modifiersubtext 649 | 650 | vitoclose 651 | 652 | 653 | 654 | EEAFA237-F675-43C1-9EB8-A1AE88C34E75 655 | 656 | 657 | destinationuid 658 | CCE98113-EE5F-4540-B377-3C9A9DA1AC6D 659 | modifiers 660 | 0 661 | modifiersubtext 662 | 663 | vitoclose 664 | 665 | 666 | 667 | F8CB74BA-4C13-4621-BC60-E50650C85A9D 668 | 669 | 670 | destinationuid 671 | 616E1777-6C1F-406D-B16B-697EA6E8DDA0 672 | modifiers 673 | 0 674 | modifiersubtext 675 | 676 | vitoclose 677 | 678 | 679 | 680 | F92C44A1-5C7A-4863-9FC1-F0188FF998E3 681 | 682 | 683 | destinationuid 684 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 685 | modifiers 686 | 0 687 | modifiersubtext 688 | 689 | vitoclose 690 | 691 | 692 | 693 | F993108F-7C6F-4AF3-90D3-14A1CBAAEBBE 694 | 695 | 696 | destinationuid 697 | 8F8F1A2F-E9D7-4EF4-B3C9-3BE1B75DEBFB 698 | modifiers 699 | 0 700 | modifiersubtext 701 | 702 | vitoclose 703 | 704 | 705 | 706 | FA2A221A-6386-4B79-950C-7C46E10196EE 707 | 708 | 709 | destinationuid 710 | 89A9AAEB-1F2B-4009-8579-309C620C8B5E 711 | modifiers 712 | 0 713 | modifiersubtext 714 | 715 | vitoclose 716 | 717 | 718 | 719 | destinationuid 720 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 721 | modifiers 722 | 0 723 | modifiersubtext 724 | 725 | sourceoutputuid 726 | 3B42F954-0523-4146-90A3-5E2609506CFA 727 | vitoclose 728 | 729 | 730 | 731 | 732 | createdby 733 | Acidham 734 | description 735 | Manage Markdown Notes 736 | disabled 737 | 738 | name 739 | Markdown Notes 740 | objects 741 | 742 | 743 | config 744 | 745 | autopaste 746 | 747 | clipboardtext 748 | {query} 749 | ignoredynamicplaceholders 750 | 751 | transient 752 | 753 | 754 | type 755 | alfred.workflow.output.clipboard 756 | uid 757 | 02B9FF6D-1CCA-424C-BEF2-FF6C2F753585 758 | version 759 | 3 760 | 761 | 762 | config 763 | 764 | concurrently 765 | 766 | escaping 767 | 68 768 | script 769 | python3 delete_note.py "$1" 770 | scriptargtype 771 | 1 772 | scriptfile 773 | delete_note.py 774 | type 775 | 5 776 | 777 | type 778 | alfred.workflow.action.script 779 | uid 780 | A1994305-42C8-4C6A-BB26-0D822F6B021A 781 | version 782 | 2 783 | 784 | 785 | config 786 | 787 | argument 788 | {var:action2} 789 | passthroughargument 790 | 791 | variables 792 | 793 | 794 | type 795 | alfred.workflow.utility.argument 796 | uid 797 | 7EF41C6D-4E2F-4BED-B733-B3538C647263 798 | version 799 | 1 800 | 801 | 802 | config 803 | 804 | conditions 805 | 806 | 807 | inputstring 808 | {var:action1} 809 | matchcasesensitive 810 | 811 | matchmode 812 | 0 813 | matchstring 814 | delete 815 | outputlabel 816 | delete 817 | uid 818 | 3961D0C5-8015-4EDE-8753-F201EB4B11C1 819 | 820 | 821 | inputstring 822 | {var:action1} 823 | matchcasesensitive 824 | 825 | matchmode 826 | 0 827 | matchstring 828 | urlscheme 829 | outputlabel 830 | urlscheme 831 | uid 832 | 1B17EF3D-7C92-4BD1-BCC3-C88806025AA7 833 | 834 | 835 | inputstring 836 | {var:action1} 837 | matchcasesensitive 838 | 839 | matchmode 840 | 0 841 | matchstring 842 | back 843 | outputlabel 844 | back 845 | uid 846 | 325682B6-E189-423E-A2E9-2D12608FDA53 847 | 848 | 849 | inputstring 850 | {var:action1} 851 | matchcasesensitive 852 | 853 | matchmode 854 | 0 855 | matchstring 856 | link 857 | outputlabel 858 | link 859 | uid 860 | 6D724BD9-BEF7-4D28-8DCC-A594E968CD3E 861 | 862 | 863 | inputstring 864 | {var:action1} 865 | matchcasesensitive 866 | 867 | matchmode 868 | 0 869 | matchstring 870 | viewer 871 | outputlabel 872 | viewer 873 | uid 874 | 03A97A98-84AF-47FA-BD7A-F5BD9090159F 875 | 876 | 877 | elselabel 878 | else 879 | hideelse 880 | 881 | 882 | type 883 | alfred.workflow.utility.conditional 884 | uid 885 | 9CFA7349-15E0-4F12-8E6D-251360FC1EAC 886 | version 887 | 1 888 | 889 | 890 | config 891 | 892 | alfredfiltersresults 893 | 894 | alfredfiltersresultsmatchmode 895 | 0 896 | argumenttreatemptyqueryasnil 897 | 898 | argumenttrimmode 899 | 0 900 | argumenttype 901 | 1 902 | escaping 903 | 102 904 | keyword 905 | mds 906 | queuedelaycustom 907 | 3 908 | queuedelayimmediatelyinitially 909 | 910 | queuedelaymode 911 | 0 912 | queuemode 913 | 1 914 | runningsubtext 915 | Searching... 916 | script 917 | python3 notes_search.py "$1" 918 | scriptargtype 919 | 1 920 | scriptfile 921 | notes_search.py 922 | subtext 923 | Search for {query} 924 | title 925 | Search in MD Notes 926 | type 927 | 5 928 | withspace 929 | 930 | 931 | type 932 | alfred.workflow.input.scriptfilter 933 | uid 934 | 8354F261-D7A0-4848-8312-9EC40100DFA6 935 | version 936 | 3 937 | 938 | 939 | config 940 | 941 | action 942 | 0 943 | argument 944 | 0 945 | focusedappvariable 946 | 947 | focusedappvariablename 948 | 949 | hotkey 950 | 17 951 | hotmod 952 | 1835008 953 | hotstring 954 | T 955 | leftcursor 956 | 957 | modsmode 958 | 0 959 | relatedAppsMode 960 | 0 961 | 962 | type 963 | alfred.workflow.trigger.hotkey 964 | uid 965 | D152D8AE-984F-47E0-9BBD-59DDE719EEDC 966 | version 967 | 2 968 | 969 | 970 | config 971 | 972 | alfredfiltersresults 973 | 974 | alfredfiltersresultsmatchmode 975 | 0 976 | argumenttreatemptyqueryasnil 977 | 978 | argumenttrimmode 979 | 0 980 | argumenttype 981 | 1 982 | escaping 983 | 102 984 | keyword 985 | md# 986 | queuedelaycustom 987 | 3 988 | queuedelayimmediatelyinitially 989 | 990 | queuedelaymode 991 | 0 992 | queuemode 993 | 1 994 | runningsubtext 995 | 996 | script 997 | python3 tag_search.py "$1" 998 | scriptargtype 999 | 1 1000 | scriptfile 1001 | tag_search.py 1002 | subtext 1003 | Markdown Notes Tag Search 1004 | title 1005 | MD Notes Tag Search 1006 | type 1007 | 5 1008 | withspace 1009 | 1010 | 1011 | type 1012 | alfred.workflow.input.scriptfilter 1013 | uid 1014 | E1E655F3-B8EB-4324-8B9D-25D3AC743045 1015 | version 1016 | 3 1017 | 1018 | 1019 | config 1020 | 1021 | alfredfiltersresults 1022 | 1023 | alfredfiltersresultsmatchmode 1024 | 0 1025 | argumenttreatemptyqueryasnil 1026 | 1027 | argumenttrimmode 1028 | 0 1029 | argumenttype 1030 | 1 1031 | escaping 1032 | 0 1033 | queuedelaycustom 1034 | 3 1035 | queuedelayimmediatelyinitially 1036 | 1037 | queuedelaymode 1038 | 0 1039 | queuemode 1040 | 1 1041 | runningsubtext 1042 | 1043 | script 1044 | python3 search_actions.py "$1" 1045 | scriptargtype 1046 | 1 1047 | scriptfile 1048 | search_actions.py 1049 | subtext 1050 | 1051 | title 1052 | 1053 | type 1054 | 5 1055 | withspace 1056 | 1057 | 1058 | type 1059 | alfred.workflow.input.scriptfilter 1060 | uid 1061 | F993108F-7C6F-4AF3-90D3-14A1CBAAEBBE 1062 | version 1063 | 3 1064 | 1065 | 1066 | config 1067 | 1068 | delimiter 1069 | | 1070 | discardemptyarguments 1071 | 1072 | outputas 1073 | 0 1074 | trimarguments 1075 | 1076 | variableprefix 1077 | action 1078 | 1079 | type 1080 | alfred.workflow.utility.split 1081 | uid 1082 | 8F8F1A2F-E9D7-4EF4-B3C9-3BE1B75DEBFB 1083 | version 1084 | 1 1085 | 1086 | 1087 | config 1088 | 1089 | argument 1090 | 1091 | passthroughargument 1092 | 1093 | variables 1094 | 1095 | 1096 | type 1097 | alfred.workflow.utility.argument 1098 | uid 1099 | 294CB549-D1EE-4AC0-BF62-B19E2DAD7162 1100 | version 1101 | 1 1102 | 1103 | 1104 | config 1105 | 1106 | delimiter 1107 | > 1108 | discardemptyarguments 1109 | 1110 | outputas 1111 | 0 1112 | trimarguments 1113 | 1114 | variableprefix 1115 | path_query 1116 | 1117 | type 1118 | alfred.workflow.utility.split 1119 | uid 1120 | 2F777EDC-8764-4AE9-AFC2-65D224BB01DE 1121 | version 1122 | 1 1123 | 1124 | 1125 | config 1126 | 1127 | externaltriggerid 1128 | *urlscheme 1129 | passinputasargument 1130 | 1131 | passvariables 1132 | 1133 | workflowbundleid 1134 | self 1135 | 1136 | type 1137 | alfred.workflow.output.callexternaltrigger 1138 | uid 1139 | A35A4CC5-F476-4228-8687-881419DA8B60 1140 | version 1141 | 1 1142 | 1143 | 1144 | config 1145 | 1146 | argument 1147 | {var:action2} 1148 | passthroughargument 1149 | 1150 | variables 1151 | 1152 | 1153 | type 1154 | alfred.workflow.utility.argument 1155 | uid 1156 | D5001574-5650-42E7-84FC-A51EAA4CE8D1 1157 | version 1158 | 1 1159 | 1160 | 1161 | config 1162 | 1163 | availableviaurlhandler 1164 | 1165 | triggerid 1166 | *snote 1167 | 1168 | type 1169 | alfred.workflow.trigger.external 1170 | uid 1171 | 88BD02F5-4A73-4701-98D0-450B1F338238 1172 | version 1173 | 1 1174 | 1175 | 1176 | config 1177 | 1178 | externaltriggerid 1179 | *snote 1180 | passinputasargument 1181 | 1182 | passvariables 1183 | 1184 | workflowbundleid 1185 | self 1186 | 1187 | type 1188 | alfred.workflow.output.callexternaltrigger 1189 | uid 1190 | 735B5B72-0876-4143-887E-D8191A48154E 1191 | version 1192 | 1 1193 | 1194 | 1195 | config 1196 | 1197 | conditions 1198 | 1199 | 1200 | inputstring 1201 | 1202 | matchcasesensitive 1203 | 1204 | matchmode 1205 | 4 1206 | matchstring 1207 | ^\/ 1208 | outputlabel 1209 | OpenNote 1210 | uid 1211 | 298C21F0-249C-4C74-BFBC-0BE1FF83AB81 1212 | 1213 | 1214 | elselabel 1215 | CreateNote 1216 | hideelse 1217 | 1218 | 1219 | type 1220 | alfred.workflow.utility.conditional 1221 | uid 1222 | 1226368E-7C02-479A-958B-6147F924D9F9 1223 | version 1224 | 1 1225 | 1226 | 1227 | config 1228 | 1229 | argument 1230 | {var:action2} 1231 | passthroughargument 1232 | 1233 | variables 1234 | 1235 | 1236 | type 1237 | alfred.workflow.utility.argument 1238 | uid 1239 | 2FA91C3B-4CED-410B-B2A6-C4BCBC9D6F41 1240 | version 1241 | 1 1242 | 1243 | 1244 | config 1245 | 1246 | acceptsmulti 1247 | 1 1248 | filetypes 1249 | 1250 | net.daringfireball.markdown 1251 | net.ia.markdown 1252 | 1253 | name 1254 | Delete MD Notes 1255 | 1256 | type 1257 | alfred.workflow.trigger.action 1258 | uid 1259 | 454BC87F-9E2E-4C7B-ABFA-2E7709D59244 1260 | version 1261 | 1 1262 | 1263 | 1264 | config 1265 | 1266 | delimiter 1267 | | 1268 | 1269 | type 1270 | alfred.workflow.utility.joinargs 1271 | uid 1272 | DAFC178B-CF94-4ED2-9E86-4ADA5CB6D961 1273 | version 1274 | 1 1275 | 1276 | 1277 | config 1278 | 1279 | alfredfiltersresults 1280 | 1281 | alfredfiltersresultsmatchmode 1282 | 0 1283 | argumenttreatemptyqueryasnil 1284 | 1285 | argumenttrimmode 1286 | 0 1287 | argumenttype 1288 | 0 1289 | escaping 1290 | 102 1291 | keyword 1292 | mdd 1293 | queuedelaycustom 1294 | 3 1295 | queuedelayimmediatelyinitially 1296 | 1297 | queuedelaymode 1298 | 0 1299 | queuemode 1300 | 1 1301 | runningsubtext 1302 | 1303 | script 1304 | python3 md_clean.py "$1" 1305 | scriptargtype 1306 | 1 1307 | scriptfile 1308 | md_clean.py 1309 | subtext 1310 | Enter Tag Name to search for... 1311 | title 1312 | Delete MD notes in batch mode 1313 | type 1314 | 5 1315 | withspace 1316 | 1317 | 1318 | type 1319 | alfred.workflow.input.scriptfilter 1320 | uid 1321 | AE883F79-FCA1-448E-AF9A-D8FF94C93552 1322 | version 1323 | 3 1324 | 1325 | 1326 | config 1327 | 1328 | conditions 1329 | 1330 | 1331 | inputstring 1332 | {query} 1333 | matchcasesensitive 1334 | 1335 | matchmode 1336 | 4 1337 | matchstring 1338 | \/Library\/Caches 1339 | outputlabel 1340 | Summary 1341 | uid 1342 | E4890EEF-C064-437F-84D4-B238D82C5B92 1343 | 1344 | 1345 | elselabel 1346 | Delete 1347 | hideelse 1348 | 1349 | 1350 | type 1351 | alfred.workflow.utility.conditional 1352 | uid 1353 | 8D265164-F63E-461B-B67B-BA20069C5E5F 1354 | version 1355 | 1 1356 | 1357 | 1358 | config 1359 | 1360 | autopaste 1361 | 1362 | clipboardtext 1363 | {query} 1364 | ignoredynamicplaceholders 1365 | 1366 | transient 1367 | 1368 | 1369 | type 1370 | alfred.workflow.output.clipboard 1371 | uid 1372 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 1373 | version 1374 | 3 1375 | 1376 | 1377 | config 1378 | 1379 | lastpathcomponent 1380 | 1381 | onlyshowifquerypopulated 1382 | 1383 | removeextension 1384 | 1385 | text 1386 | {query} 1387 | title 1388 | MD Link copied to clipboard 1389 | 1390 | type 1391 | alfred.workflow.output.notification 1392 | uid 1393 | EAEF0CF6-8AEA-4B7D-8EAB-80FC16EDDAAC 1394 | version 1395 | 1 1396 | 1397 | 1398 | config 1399 | 1400 | externaltriggerid 1401 | *cnote 1402 | passinputasargument 1403 | 1404 | passvariables 1405 | 1406 | workflowbundleid 1407 | self 1408 | 1409 | type 1410 | alfred.workflow.output.callexternaltrigger 1411 | uid 1412 | 93FCC3FA-C642-48CF-BE86-9C5EDAEE07D4 1413 | version 1414 | 1 1415 | 1416 | 1417 | config 1418 | 1419 | argument 1420 | {var:action2} 1421 | passthroughargument 1422 | 1423 | variables 1424 | 1425 | 1426 | type 1427 | alfred.workflow.utility.argument 1428 | uid 1429 | F92C44A1-5C7A-4863-9FC1-F0188FF998E3 1430 | version 1431 | 1 1432 | 1433 | 1434 | config 1435 | 1436 | alfredfiltersresults 1437 | 1438 | alfredfiltersresultsmatchmode 1439 | 0 1440 | argumenttreatemptyqueryasnil 1441 | 1442 | argumenttrimmode 1443 | 0 1444 | argumenttype 1445 | 1 1446 | escaping 1447 | 102 1448 | keyword 1449 | mdt 1450 | queuedelaycustom 1451 | 3 1452 | queuedelayimmediatelyinitially 1453 | 1454 | queuedelaymode 1455 | 0 1456 | queuemode 1457 | 1 1458 | runningsubtext 1459 | 1460 | script 1461 | python3 todo_search.py "$1" 1462 | scriptargtype 1463 | 1 1464 | scriptfile 1465 | todo_search.py 1466 | subtext 1467 | Search Markdown Todos 1468 | title 1469 | Markdown Notes Todos 1470 | type 1471 | 5 1472 | withspace 1473 | 1474 | 1475 | type 1476 | alfred.workflow.input.scriptfilter 1477 | uid 1478 | 661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4 1479 | version 1480 | 3 1481 | 1482 | 1483 | config 1484 | 1485 | acceptsmulti 1486 | 0 1487 | filetypes 1488 | 1489 | net.daringfireball.markdown 1490 | net.ia.markdown 1491 | 1492 | name 1493 | MD Link to Note 1494 | 1495 | type 1496 | alfred.workflow.trigger.action 1497 | uid 1498 | EEAFA237-F675-43C1-9EB8-A1AE88C34E75 1499 | version 1500 | 1 1501 | 1502 | 1503 | config 1504 | 1505 | concurrently 1506 | 1507 | escaping 1508 | 102 1509 | script 1510 | open -a $viewer "$1" 1511 | scriptargtype 1512 | 1 1513 | scriptfile 1514 | 1515 | type 1516 | 5 1517 | 1518 | type 1519 | alfred.workflow.action.script 1520 | uid 1521 | 89A9AAEB-1F2B-4009-8579-309C620C8B5E 1522 | version 1523 | 2 1524 | 1525 | 1526 | config 1527 | 1528 | concurrently 1529 | 1530 | escaping 1531 | 68 1532 | script 1533 | python3 get_md_link.py "$1" 1534 | scriptargtype 1535 | 1 1536 | scriptfile 1537 | get_md_link.py 1538 | type 1539 | 5 1540 | 1541 | type 1542 | alfred.workflow.action.script 1543 | uid 1544 | CCE98113-EE5F-4540-B377-3C9A9DA1AC6D 1545 | version 1546 | 2 1547 | 1548 | 1549 | config 1550 | 1551 | conditions 1552 | 1553 | 1554 | inputstring 1555 | {var:viewer} 1556 | matchcasesensitive 1557 | 1558 | matchmode 1559 | 6 1560 | matchstring 1561 | 1562 | outputlabel 1563 | Editor 1564 | uid 1565 | 3B42F954-0523-4146-90A3-5E2609506CFA 1566 | 1567 | 1568 | elselabel 1569 | Viewer 1570 | hideelse 1571 | 1572 | 1573 | type 1574 | alfred.workflow.utility.conditional 1575 | uid 1576 | FA2A221A-6386-4B79-950C-7C46E10196EE 1577 | version 1578 | 1 1579 | 1580 | 1581 | config 1582 | 1583 | argument 1584 | {var:action2} 1585 | passthroughargument 1586 | 1587 | variables 1588 | 1589 | 1590 | type 1591 | alfred.workflow.utility.argument 1592 | uid 1593 | 73F51F35-8DE7-4E97-A18D-ADA7046EB4A7 1594 | version 1595 | 1 1596 | 1597 | 1598 | config 1599 | 1600 | acceptsmulti 1601 | 0 1602 | filetypes 1603 | 1604 | name 1605 | Upload Asset for MD Notes 1606 | 1607 | type 1608 | alfred.workflow.trigger.action 1609 | uid 1610 | C316AE82-592A-4D24-95D1-D889B41122DB 1611 | version 1612 | 1 1613 | 1614 | 1615 | config 1616 | 1617 | concurrently 1618 | 1619 | escaping 1620 | 0 1621 | script 1622 | python3 asset_upload.py "$1" 1623 | scriptargtype 1624 | 1 1625 | scriptfile 1626 | asset_upload.py 1627 | type 1628 | 5 1629 | 1630 | type 1631 | alfred.workflow.action.script 1632 | uid 1633 | 4C847DCB-008B-4241-A1C4-35B3895092F7 1634 | version 1635 | 2 1636 | 1637 | 1638 | config 1639 | 1640 | concurrently 1641 | 1642 | escaping 1643 | 102 1644 | script 1645 | open -a $editor "$1" 1646 | scriptargtype 1647 | 1 1648 | scriptfile 1649 | 1650 | type 1651 | 5 1652 | 1653 | type 1654 | alfred.workflow.action.script 1655 | uid 1656 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 1657 | version 1658 | 2 1659 | 1660 | 1661 | config 1662 | 1663 | availableviaurlhandler 1664 | 1665 | triggerid 1666 | *urlscheme 1667 | 1668 | type 1669 | alfred.workflow.trigger.external 1670 | uid 1671 | 5739EC91-287F-41C7-8DD5-71C3772036CD 1672 | version 1673 | 1 1674 | 1675 | 1676 | config 1677 | 1678 | concurrently 1679 | 1680 | escaping 1681 | 102 1682 | script 1683 | python3 url_scheme.py "$1" 1684 | scriptargtype 1685 | 1 1686 | scriptfile 1687 | url_scheme.py 1688 | type 1689 | 5 1690 | 1691 | type 1692 | alfred.workflow.action.script 1693 | uid 1694 | E821ABDF-E08D-4BAE-83F0-4F24F4AB7B8C 1695 | version 1696 | 2 1697 | 1698 | 1699 | config 1700 | 1701 | availableviaurlhandler 1702 | 1703 | triggerid 1704 | *cnote 1705 | 1706 | type 1707 | alfred.workflow.trigger.external 1708 | uid 1709 | 64388804-3C22-4CB8-8A61-5E2DC6EBF4FD 1710 | version 1711 | 1 1712 | 1713 | 1714 | config 1715 | 1716 | autopaste 1717 | 1718 | clipboardtext 1719 | {query} 1720 | ignoredynamicplaceholders 1721 | 1722 | transient 1723 | 1724 | 1725 | type 1726 | alfred.workflow.output.clipboard 1727 | uid 1728 | 8BFF5D9F-F5E7-4CC8-AC7D-C3DAA406DA19 1729 | version 1730 | 3 1731 | 1732 | 1733 | config 1734 | 1735 | lastpathcomponent 1736 | 1737 | onlyshowifquerypopulated 1738 | 1739 | removeextension 1740 | 1741 | text 1742 | {query} 1743 | title 1744 | URL copied to Clipboard 1745 | 1746 | type 1747 | alfred.workflow.output.notification 1748 | uid 1749 | 0615CE75-2C53-400F-BD6E-FC6A98EE6F0A 1750 | version 1751 | 1 1752 | 1753 | 1754 | config 1755 | 1756 | alfredfiltersresults 1757 | 1758 | alfredfiltersresultsmatchmode 1759 | 0 1760 | argumenttreatemptyqueryasnil 1761 | 1762 | argumenttrimmode 1763 | 0 1764 | argumenttype 1765 | 1 1766 | escaping 1767 | 102 1768 | keyword 1769 | mdc 1770 | queuedelaycustom 1771 | 3 1772 | queuedelayimmediatelyinitially 1773 | 1774 | queuedelaymode 1775 | 0 1776 | queuemode 1777 | 1 1778 | runningsubtext 1779 | 1780 | script 1781 | python3 template_selector.py "$1" 1782 | scriptargtype 1783 | 1 1784 | scriptfile 1785 | template_selector.py 1786 | subtext 1787 | Templates are MD Notes with #Template tag defined. Press enter with selection... 1788 | title 1789 | Create note based on Template 1790 | type 1791 | 5 1792 | withspace 1793 | 1794 | 1795 | type 1796 | alfred.workflow.input.scriptfilter 1797 | uid 1798 | F8CB74BA-4C13-4621-BC60-E50650C85A9D 1799 | version 1800 | 3 1801 | 1802 | 1803 | config 1804 | 1805 | argumenttype 1806 | 0 1807 | keyword 1808 | mdc 1809 | subtext 1810 | Enter a title for the Note, optionally add tags at the after the title e.g. <title> #tag1 #tag2 1811 | text 1812 | Create New MD Note 1813 | withspace 1814 | 1815 | 1816 | type 1817 | alfred.workflow.input.keyword 1818 | uid 1819 | C277BA58-113D-4741-A306-9A1AAAE9EA61 1820 | version 1821 | 1 1822 | 1823 | 1824 | config 1825 | 1826 | concurrently 1827 | 1828 | escaping 1829 | 0 1830 | script 1831 | python3 create_note.py "$1" 1832 | scriptargtype 1833 | 1 1834 | scriptfile 1835 | create_note.py 1836 | type 1837 | 5 1838 | 1839 | type 1840 | alfred.workflow.action.script 1841 | uid 1842 | 49185B13-47A9-4B6A-9FF3-7423A9E173EB 1843 | version 1844 | 2 1845 | 1846 | 1847 | config 1848 | 1849 | argument 1850 | 1851 | passthroughargument 1852 | 1853 | variables 1854 | 1855 | template_path 1856 | {query} 1857 | 1858 | 1859 | type 1860 | alfred.workflow.utility.argument 1861 | uid 1862 | 616E1777-6C1F-406D-B16B-697EA6E8DDA0 1863 | version 1864 | 1 1865 | 1866 | 1867 | config 1868 | 1869 | browser 1870 | 1871 | skipqueryencode 1872 | 1873 | skipvarencode 1874 | 1875 | spaces 1876 | 1877 | url 1878 | {query} 1879 | 1880 | type 1881 | alfred.workflow.action.openurl 1882 | uid 1883 | 01EBC50C-380F-4FA3-B537-D755B7134966 1884 | version 1885 | 1 1886 | 1887 | 1888 | config 1889 | 1890 | alfredfiltersresults 1891 | 1892 | alfredfiltersresultsmatchmode 1893 | 0 1894 | argumenttreatemptyqueryasnil 1895 | 1896 | argumenttrimmode 1897 | 0 1898 | argumenttype 1899 | 1 1900 | escaping 1901 | 102 1902 | keyword 1903 | mdb 1904 | queuedelaycustom 1905 | 3 1906 | queuedelayimmediatelyinitially 1907 | 1908 | queuedelaymode 1909 | 0 1910 | queuemode 1911 | 1 1912 | runningsubtext 1913 | 1914 | script 1915 | python3 url_search.py "$1" 1916 | scriptargtype 1917 | 1 1918 | scriptfile 1919 | url_search.py 1920 | subtext 1921 | 1922 | title 1923 | Search Bookmarks in MD Notes 1924 | type 1925 | 5 1926 | withspace 1927 | 1928 | 1929 | type 1930 | alfred.workflow.input.scriptfilter 1931 | uid 1932 | 898E9FE0-5165-4530-98E9-9497DDF6DA0D 1933 | version 1934 | 3 1935 | 1936 | 1937 | config 1938 | 1939 | concurrently 1940 | 1941 | escaping 1942 | 102 1943 | script 1944 | python3 html_fetch.py "$1" 1945 | scriptargtype 1946 | 1 1947 | scriptfile 1948 | html_fetch.py 1949 | type 1950 | 5 1951 | 1952 | type 1953 | alfred.workflow.action.script 1954 | uid 1955 | 49A9F878-DBE6-4A7F-B6A0-FEFD5D77393A 1956 | version 1957 | 2 1958 | 1959 | 1960 | config 1961 | 1962 | argumenttype 1963 | 0 1964 | keyword 1965 | mdf 1966 | subtext 1967 | Provide a valid URL beginning with http(s) 1968 | text 1969 | Fetch URL into MD Note 1970 | withspace 1971 | 1972 | 1973 | type 1974 | alfred.workflow.input.keyword 1975 | uid 1976 | E6223AE8-EC41-40DA-88AC-8476D6F73ED1 1977 | version 1978 | 1 1979 | 1980 | 1981 | config 1982 | 1983 | lastpathcomponent 1984 | 1985 | onlyshowifquerypopulated 1986 | 1987 | removeextension 1988 | 1989 | text 1990 | Cannot fetch URL 1991 | title 1992 | ERROR 1993 | 1994 | type 1995 | alfred.workflow.output.notification 1996 | uid 1997 | E3B5F19A-B7EC-4984-A3CA-D626DB05ADF5 1998 | version 1999 | 1 2000 | 2001 | 2002 | config 2003 | 2004 | conditions 2005 | 2006 | 2007 | inputstring 2008 | 2009 | matchcasesensitive 2010 | 2011 | matchmode 2012 | 1 2013 | matchstring 2014 | 2015 | outputlabel 2016 | fetch successful 2017 | uid 2018 | 30E2F874-FC82-4DF3-9991-38F5337D9CAB 2019 | 2020 | 2021 | elselabel 2022 | not successful 2023 | hideelse 2024 | 2025 | 2026 | type 2027 | alfred.workflow.utility.conditional 2028 | uid 2029 | DB11D78A-2543-48A3-B195-09CC58045785 2030 | version 2031 | 1 2032 | 2033 | 2034 | config 2035 | 2036 | argumenttype 2037 | 0 2038 | subtext 2039 | Enter new title optionally with tags e.g. title #tag 2040 | text 2041 | Enter name of the new MD file 2042 | withspace 2043 | 2044 | 2045 | type 2046 | alfred.workflow.input.keyword 2047 | uid 2048 | 18A8C2F0-9E83-42B2-9FFA-04793FDE69AF 2049 | version 2050 | 1 2051 | 2052 | 2053 | config 2054 | 2055 | acceptsmulti 2056 | 1 2057 | filetypes 2058 | 2059 | net.daringfireball.markdown 2060 | 2061 | name 2062 | Create MD Index 2063 | 2064 | type 2065 | alfred.workflow.trigger.action 2066 | uid 2067 | 867A6B07-A86D-464E-9AA2-F4F85D33901E 2068 | version 2069 | 1 2070 | 2071 | 2072 | config 2073 | 2074 | concurrently 2075 | 2076 | escaping 2077 | 102 2078 | script 2079 | python3 create_index.py "$1" 2080 | scriptargtype 2081 | 1 2082 | scriptfile 2083 | create_index.py 2084 | type 2085 | 5 2086 | 2087 | type 2088 | alfred.workflow.action.script 2089 | uid 2090 | BAFF7A0F-AAD1-416B-9279-C500DEA630C3 2091 | version 2092 | 2 2093 | 2094 | 2095 | config 2096 | 2097 | argument 2098 | 2099 | passthroughargument 2100 | 2101 | variables 2102 | 2103 | files 2104 | {query} 2105 | 2106 | 2107 | type 2108 | alfred.workflow.utility.argument 2109 | uid 2110 | BC9416FD-DA8C-4E89-BB77-2300E23FC2AB 2111 | version 2112 | 1 2113 | 2114 | 2115 | config 2116 | 2117 | delimiter 2118 | | 2119 | 2120 | type 2121 | alfred.workflow.utility.joinargs 2122 | uid 2123 | E9AAB61A-3037-496F-B9A7-1D9166D62D6D 2124 | version 2125 | 1 2126 | 2127 | 2128 | readme 2129 | # Alfred Markdown Notes 2130 | 2131 | https://acidham.github.io/alfred-markdown-notes/ 2132 | 2133 | Markdown Notes is a comprehensive note taking tool embedded into Aflred with powerful full text search (supports & and |), tag search and search capabilities for todos ( `- [ ]` or `* [ ]`) . With MD Notes you can quickly create new notes based on custom templates, e.g. meeting notes, bookmarks, project documentation, etc. 2134 | 2135 | If you are interested in the full journey read *[Productivity at your Finger Tips](https://acidham.medium.com/productivity-at-your-finger-tips-d6ef24770e0b)* 2136 | 2137 | MD Notes works with any mardkown editor. 2138 | 2139 | > [Typora](https://typora.io/) is set up in Alfred Workflow as preferred Markdown editor but it is possible to use another MD Editor or Text Editor if required. If you want to use another Editor you to define the Editor in the worklow steps at the end of the WF (right side of the workflow viz in Alfred) 2140 | 2141 | ## Installation 2142 | 2143 | 1. Download [Alfred Markdown Notes](https://github.com/Acidham/alfred-markdown-notes/releases/latest) 2144 | 2. Double click downloaded file to install in Alfred 2145 | 2146 | ## Requirements 2147 | 2148 | 1. [Alfred Powerpack](https://www.alfredapp.com/powerpack/) 2149 | 2. Python 3 2150 | 2151 | ## Configuration 2152 | 2153 | To get MD Notes to work properly you first need to configure the worklfow. Click on `Configure Workflow` in Alfred Workflow Tab. 2154 | 2155 | Variables marked with * are required for running MD Notes properly, the others are optional and can be ignored. 2156 | 2157 | * **Path to Notes:** * The path where MD Notes will be stored. 2158 | The path can be absolute or relative but has to be a user directory! 2159 | Examples: 2160 | 2161 | * `/Users/yourname/Dropbox/Notes` → works 2162 | * `yourname/Dropbox/Notes` → works 2163 | * `/yourname/Dropbox/Notes` → works 2164 | * `/Volumes/usb` → will not work! 2165 | 2166 | * **Editor:** Select preferred Markdown Editor 2167 | 2168 | * **Default Date Format:** Defines date format when creating new notes or when using placeholders in templates: {date} e.g. %d.%m.%Y %H.%M 2169 | 2170 | * **Default Template:** * The file name that will be used as the default Template. Before templates can be used it is required to create the template.md e.g. `Template.md` (see [Working with Templates](#Working%20with%20Templates)) 2171 | **Note**: Enter the file name ONLY without path e.g. `myTemplate.md` 2172 | 2173 | * **Extension:** * The MD files are text files with a specific extension (usually `.txt`or `.md`) any other extension can be defined if required. 2174 | **Note:** The files must be type text files. 2175 | 2176 | * **Search in Tags in YMF only:** * 2177 | 2178 | Tags can be used in the YAML front matter (`Tags: #mytag`) or within the MD note. 2179 | 2180 | 1. When set to `True` tag search only search with YMF. 2181 | 2. When set to `False` tags will be searched the whole MD note. 2182 | 2183 | * **Exact Match:** Defines if the search should match the exact search term (`True`) or the string (`False`) in markdown notes. 2184 | 2185 | **Note:** When exact match is set to `True` it is possible to enhance the search term with wildcards 2186 | 2187 | * **Todo sort order:** Sort order when using Todo Search ( `mdt`). 2188 | `True` newest todos will be shown on top of the search results 2189 | `False` oldest todos will be shown on top of the search results 2190 | 2191 | * **URL scheme:** (OPTIONAL) I figured out that some web app like Todoist is using web interface where, due to OS restrictions, file paths cannot be opened. To work around this URL scheme can be configured to open the note in markdown editor or viewer, e.g. Marked or iA Writer. Add URL Scheme like `x-writer://create?file=` and after `file=` will be enhanced with the MD Note path when executed. 2192 | 2193 | * **Template Tag:** The template tag defines which (`#Tagname`) defines a Template. Once you created a template just add template tag name to the MD Note and it will be recognized when you create a new MD Note from Template (see [Create new MD Notes from Template](#Create%20new%20MD%20Notes%20from%20Template)) 2194 | 2195 | * **Bookmark Tag:** Name of the tag which marks Notes containing URL/Bookmarks. 2196 | 2197 | * **Filename Format:** Standard file format for new MD notes. Default is the title of the notes but in some cases it is useful to add a date format e.g. when using Zettelkasten file format. 2198 | The two placeholders can be used: 2199 | 2200 | * e.g. `{%d-%m-%Y}` or any other strftime format. 2201 | *Be careful when using strftime format. Wrong format result in wrong file names!* 2202 | * `{title}`: The title of the MD Note 2203 | * Example: `{title}-{%d%m%Y}` 2204 | 2205 | ### Optional: QLMarkdown 2206 | 2207 | To use quicklook for Markdown files there is a QLMarkdown plugin available on git: [https://github.com/toland/qlmarkdown](https://github.com/toland/qlmarkdown) 2208 | 2209 | ## Features 2210 | 2211 | ### Full Text Search 2212 | 2213 | Type `mds` keyword into Alfred and get a list of all MD files sorted by last modified date. After `mds` keyword you can type a search term and text will be searched instantly. 2214 | The search runs with exact match and with partial match by using wildcards `*` before or after the search term 2215 | 2216 | #### Syntax 2217 | 2218 | * `Hello Alfred` searches for exact match of the phrase 2219 | * `Hello&Alfred` search for Notes containing the two words somewhere in the text 2220 | * `Hello|Alfred` search for `Hello` or `Alfred` somwhere in the text 2221 | * `Book` match exact word in the text 2222 | * `Book*` machtes `Bookstore` and `Booking` 2223 | * *Tip:* You can type `#Tag` in `mds` to search for Notes with specific Tag in text and using & and | in the same way than with text search. 2224 | 2225 | #### Options 2226 | 2227 | With the Alfred search results from `mds` and `mdt` you can perform additional actions to the note: 2228 | 2229 | * Pressing `Shift`you can quicklook the file. 2230 | *Tip*: To quicklook markdown files formatted you can install [QLMarkdown](https://github.com/toland/qlmarkdown) 2231 | * With Pressing `CMD` you can open the action menu. The following actions are available: 2232 | * **Markdown Link**: Copy markdown link of the note to the clipboard for pasting into another app or markdown file 2233 | * **Delete Note**: Delete the file and all associated assets such as images or other file types. 2234 | * **Marked 2**: Opens the Note in Marked 2 2235 | **Note:** The Markdown Editor can be changed in Alfred Preferences → Workflow 2236 | * **URL Scheme**: Generate MD link for URL Scheme and copy to the clipboard e.g. `[My Notes](x-writer://open?path=/Users/joe/Documents/Notes/doc.md)` 2237 | * It is possible to perform additional actions to one or more Notes by proceeding with File Actions (press `TAB` or `ALT+TAB` on a note or multiple notes): 2238 | * **Delete MD Notes**: Same as *Delete Note* in action menu but it also deletes multiple Notes 2239 | * **MD Link to Note**: Generates relative Link to a markdown document for referencing Notes in other Notes e.g. `[My Notes](mynote.md)` 2240 | * **Create Markdown Index**: Selected Markdown files will be linked into a new Index file e.g. to collect links to all invoices for an insurance 2241 | 2242 | ### Tag Search 2243 | 2244 | Type `md#` to get a list of all tags found in the Notes or search for Tags (see [Options](#Options) as well) 2245 | 2246 | The tag search can also be used to search for already existing Tags to paste it into a markdown note. By pressing `CMD` and `Enter` the tag will be pasted into the frontmost app. 2247 | 2248 | ### Search in Todos 2249 | 2250 | Type `mdt` to get all Todos found in the MD Notes. The list is sorted based on when Notes with the todo was created (older notes first). As well you can search for full-text search in todo and use the modify keys (see [Options](#Options)) 2251 | 2252 | ### MD Bookmarks 2253 | 2254 | Bookmarks/ URLs stored in MD Notes can be opened using MD Notes. If an MD Note contains the bookmark tag (see [Configuration](#Configuration) → Bookmark Tag) and one or more URLs, they can be opened directly from Alfred directly. 2255 | 2256 | Type `mdb` to find Bookmarks found in the MD Notes. The list shows the links found in the MD Notes and corresponding note. 2257 | 2258 | ### Create a MD Note 2259 | 2260 | #### MD Note with Title 2261 | 2262 | Type `mdc` followed by a **title** to create a new MD Note with title. The default Templates will be used (see [Configuration](#Configuration)) 2263 | 2264 | #### Note with Title and Tags 2265 | 2266 | Type `mdc` followed by **title** and **tags** separated by space will create a Note with **title** and **tags**. 2267 | 2268 | #### MD Notes from Template 2269 | 2270 | Type `mdc` and you get a list of all Templates in your folder. After a template was selected the title can be entered as described above. 2271 | 2272 | **Note**: Templates are Notes tagged with `#Template` or whatever you defined as the template tag. 2273 | 2274 | #### YAML Fronter 2275 | 2276 | MD Notes uses YAML Fronter when searching in Tags. Therefore it is required to add YAML Fronter to the top of the notes, with the following format: 2277 | 2278 | ``` 2279 | --- 2280 | Tags: #mytag 2281 | --- 2282 | ``` 2283 | 2284 | ### Delete MD Note(s) 2285 | 2286 | There are various ways of deleting MD Notes. Important is to not use Alfred standard file deletion because using delete feature coming with MD notes workflow ensures that asset dependencies will also be deleted: 2287 | 2288 | 1. **Delete via action menu**: Press `CMD+Enter` when search results will be shown 2289 | 2. **Delete via file action**: This option also allows to collect some notes via Alfred file buffer first before deleting them. 2290 | 3. **Delete via in batch mode**. This method allows to tag specific MD notes for deletion eg. By adding `#delete` tag to the notes and mark them for deletion. Any other tag name can be used. 2291 | To delete MD notes tagged with a specific tag, just execute `mdd` followed by the tag name. The search will show the total number of affected notes with a preview option. 2292 | 2293 | ### Working with Templates 2294 | 2295 | Templates are a great way to quickly create a MD Note based on a Markdown template file. The Template files are created and stored in the same way than normal notes and must contain the Template tag (see [Configuration](#Configuration)) in the YAML Fronter section (see [YAML Fronter](#YAML%20Fonter)). 2296 | 2297 | There are two way to create a file based on a template: 2298 | 2299 | * Via `mdc` → Create Note: A Note with a title will be created based on default template 2300 | * Via `mdc` → `Template`: The Note will be created based on selected template. After the corresponding template was selected, the title can be entered. 2301 | 2302 | #### Using placeholder in Templates 2303 | 2304 | The MD Notes Templates can contain two placeholder values and will be replaced when using Templates with the command `mdc`. 2305 | With `mdc` it is also possible to directly create a note. In this case the default template will be used. The default template can be configured in Alfred Workflow settings (see [Configuration](#Configuration)): 2306 | 2307 | * `{title}`: The title will be used that you entered when creating a new note 2308 | * `{date}`: Today's date will be used when creating a new note 2309 | 2310 | ### Adding a file link to a Markdown Note 2311 | 2312 | Images can be usually added via drag&drop to the markdown editor, esp. with Typora. It is also possible to add other file-types by using `Upload Asset for MD Notes` file action in Alfred. 2313 | 2314 | To add a file to a Markdown Note, search for the File in Alfred and then press `Tab` to enter file actions. Search for `Upload Asset for MD Notes` and execute. The Markdown Link will be copied to the Clipboard. Just paste the MD Link to a Markdown Note. 2315 | 2316 | ### Fetch HTML Pages 2317 | 2318 | MD Notes provides the possibility to fetch pages from an URL and store it in a new note. The note will be created with the containing Page title. 2319 | In case the page cannot be fetched MD Notes create a note and add the URL into the note. 2320 | 2321 | To fetch an URL use `mdf` and enter the target URL. 2322 | 2323 | **Note:** To import HTML content [Pandoc](https://pandoc.org/installing.html) is required. If Pandoc is not installed the note will only contain the URL to the Page. 2324 | 2325 | ### Upload Asset for MD Note (file action) 2326 | 2327 | Depending on the MD Editor used, assets (png, pdf, etc.) can be drag and drop into the MD document. Some editors use absolute or relative paths, and some create an assets' directory automatically and copy the asset into the asset folder. The assets are stored with the MD Notes and when you delete the original asset file, it ensures that the MD note keeps a copy. 2328 | 2329 | In cases where the file will be just linked in MD Editor, the Workflow provides the ability to upload the asset under the `Path to Notes` (see [Options](file:///Users/jjung/Documents/Git/alfred-markdown-notes/README.md#Options)) with the name `_NoteAssets.` The folder will be automatically created. 2330 | uidata 2331 | 2332 | 01EBC50C-380F-4FA3-B537-D755B7134966 2333 | 2334 | xpos 2335 | 1440 2336 | ypos 2337 | 1750 2338 | 2339 | 02B9FF6D-1CCA-424C-BEF2-FF6C2F753585 2340 | 2341 | xpos 2342 | 420 2343 | ypos 2344 | 75 2345 | 2346 | 0615CE75-2C53-400F-BD6E-FC6A98EE6F0A 2347 | 2348 | xpos 2349 | 1705 2350 | ypos 2351 | 1610 2352 | 2353 | 1226368E-7C02-479A-958B-6147F924D9F9 2354 | 2355 | xpos 2356 | 760 2357 | ypos 2358 | 405 2359 | 2360 | 18A8C2F0-9E83-42B2-9FFA-04793FDE69AF 2361 | 2362 | xpos 2363 | 580 2364 | ypos 2365 | 2140 2366 | 2367 | 294CB549-D1EE-4AC0-BF62-B19E2DAD7162 2368 | 2369 | xpos 2370 | 710 2371 | ypos 2372 | 235 2373 | 2374 | 2F777EDC-8764-4AE9-AFC2-65D224BB01DE 2375 | 2376 | xpos 2377 | 650 2378 | ypos 2379 | 235 2380 | 2381 | 2FA91C3B-4CED-410B-B2A6-C4BCBC9D6F41 2382 | 2383 | xpos 2384 | 1295 2385 | ypos 2386 | 405 2387 | 2388 | 454BC87F-9E2E-4C7B-ABFA-2E7709D59244 2389 | 2390 | colorindex 2391 | 6 2392 | note 2393 | File Action to delete notes when multiple files selectes. One file also works 2394 | xpos 2395 | 210 2396 | ypos 2397 | 505 2398 | 2399 | 49185B13-47A9-4B6A-9FF3-7423A9E173EB 2400 | 2401 | colorindex 2402 | 1 2403 | note 2404 | Create a note with template 2405 | xpos 2406 | 615 2407 | ypos 2408 | 1630 2409 | 2410 | 49A9F878-DBE6-4A7F-B6A0-FEFD5D77393A 2411 | 2412 | colorindex 2413 | 1 2414 | note 2415 | HTML Fetch worker 2416 | xpos 2417 | 615 2418 | ypos 2419 | 1915 2420 | 2421 | 4C847DCB-008B-4241-A1C4-35B3895092F7 2422 | 2423 | colorindex 2424 | 1 2425 | note 2426 | Upload the asset to internal target folder and copy md link to clipboard 2427 | xpos 2428 | 420 2429 | ypos 2430 | 1215 2431 | 2432 | 5739EC91-287F-41C7-8DD5-71C3772036CD 2433 | 2434 | xpos 2435 | 215 2436 | ypos 2437 | 1390 2438 | 2439 | 616E1777-6C1F-406D-B16B-697EA6E8DDA0 2440 | 2441 | xpos 2442 | 355 2443 | ypos 2444 | 1660 2445 | 2446 | 64388804-3C22-4CB8-8A61-5E2DC6EBF4FD 2447 | 2448 | xpos 2449 | 215 2450 | ypos 2451 | 1510 2452 | 2453 | 661CD8F0-9481-4FCF-9A96-73A2FF1FCFD4 2454 | 2455 | colorindex 2456 | 7 2457 | note 2458 | List or search for todos 2459 | xpos 2460 | 210 2461 | ypos 2462 | 905 2463 | 2464 | 735B5B72-0876-4143-887E-D8191A48154E 2465 | 2466 | colorindex 2467 | 7 2468 | xpos 2469 | 1840 2470 | ypos 2471 | 370 2472 | 2473 | 73F51F35-8DE7-4E97-A18D-ADA7046EB4A7 2474 | 2475 | xpos 2476 | 1225 2477 | ypos 2478 | 1080 2479 | 2480 | 7EF41C6D-4E2F-4BED-B733-B3538C647263 2481 | 2482 | xpos 2483 | 1295 2484 | ypos 2485 | 145 2486 | 2487 | 8354F261-D7A0-4848-8312-9EC40100DFA6 2488 | 2489 | colorindex 2490 | 7 2491 | note 2492 | Search Note, Delete with CMD 2493 | xpos 2494 | 420 2495 | ypos 2496 | 200 2497 | 2498 | 867A6B07-A86D-464E-9AA2-F4F85D33901E 2499 | 2500 | note 2501 | Create Markdown Index file action 2502 | xpos 2503 | 175 2504 | ypos 2505 | 2140 2506 | 2507 | 88BD02F5-4A73-4701-98D0-450B1F338238 2508 | 2509 | colorindex 2510 | 7 2511 | xpos 2512 | 210 2513 | ypos 2514 | 370 2515 | 2516 | 898E9FE0-5165-4530-98E9-9497DDF6DA0D 2517 | 2518 | xpos 2519 | 425 2520 | ypos 2521 | 1775 2522 | 2523 | 89A9AAEB-1F2B-4009-8579-309C620C8B5E 2524 | 2525 | note 2526 | Open Viewer 2527 | xpos 2528 | 1575 2529 | ypos 2530 | 1065 2531 | 2532 | 8BFF5D9F-F5E7-4CC8-AC7D-C3DAA406DA19 2533 | 2534 | xpos 2535 | 1445 2536 | ypos 2537 | 1610 2538 | 2539 | 8D265164-F63E-461B-B67B-BA20069C5E5F 2540 | 2541 | xpos 2542 | 1295 2543 | ypos 2544 | 705 2545 | 2546 | 8F8F1A2F-E9D7-4EF4-B3C9-3BE1B75DEBFB 2547 | 2548 | xpos 2549 | 940 2550 | ypos 2551 | 235 2552 | 2553 | 9276011F-3CDA-4F4A-A7BB-6A36B761C53A 2554 | 2555 | note 2556 | Copy MD link to clipboard 2557 | xpos 2558 | 1575 2559 | ypos 2560 | 755 2561 | 2562 | 93FCC3FA-C642-48CF-BE86-9C5EDAEE07D4 2563 | 2564 | xpos 2565 | 975 2566 | ypos 2567 | 790 2568 | 2569 | 9CFA7349-15E0-4F12-8E6D-251360FC1EAC 2570 | 2571 | xpos 2572 | 1030 2573 | ypos 2574 | 180 2575 | 2576 | A1994305-42C8-4C6A-BB26-0D822F6B021A 2577 | 2578 | colorindex 2579 | 1 2580 | note 2581 | Deletes a note from Search Note (snote) 2582 | xpos 2583 | 1580 2584 | ypos 2585 | 80 2586 | 2587 | A35A4CC5-F476-4228-8687-881419DA8B60 2588 | 2589 | xpos 2590 | 1580 2591 | ypos 2592 | 240 2593 | 2594 | AE883F79-FCA1-448E-AF9A-D8FF94C93552 2595 | 2596 | colorindex 2597 | 6 2598 | note 2599 | Batch delete specific notes matching tag 2600 | xpos 2601 | 210 2602 | ypos 2603 | 700 2604 | 2605 | BAFF7A0F-AAD1-416B-9279-C500DEA630C3 2606 | 2607 | note 2608 | generates the markdown index file 2609 | xpos 2610 | 760 2611 | ypos 2612 | 2140 2613 | 2614 | BC9416FD-DA8C-4E89-BB77-2300E23FC2AB 2615 | 2616 | xpos 2617 | 470 2618 | ypos 2619 | 2170 2620 | 2621 | C277BA58-113D-4741-A306-9A1AAAE9EA61 2622 | 2623 | colorindex 2624 | 7 2625 | note 2626 | Create note, input title 2627 | xpos 2628 | 420 2629 | ypos 2630 | 1630 2631 | 2632 | C316AE82-592A-4D24-95D1-D889B41122DB 2633 | 2634 | note 2635 | File Action to upload Asset and provide MD Link to it 2636 | xpos 2637 | 215 2638 | ypos 2639 | 1215 2640 | 2641 | CCE98113-EE5F-4540-B377-3C9A9DA1AC6D 2642 | 2643 | colorindex 2644 | 1 2645 | note 2646 | Generates the MD Link 2647 | xpos 2648 | 420 2649 | ypos 2650 | 1065 2651 | 2652 | D152D8AE-984F-47E0-9BBD-59DDE719EEDC 2653 | 2654 | note 2655 | Search in MD Tags 2656 | xpos 2657 | 30 2658 | ypos 2659 | 200 2660 | 2661 | D5001574-5650-42E7-84FC-A51EAA4CE8D1 2662 | 2663 | xpos 2664 | 1295 2665 | ypos 2666 | 275 2667 | 2668 | DAFC178B-CF94-4ED2-9E86-4ADA5CB6D961 2669 | 2670 | xpos 2671 | 780 2672 | ypos 2673 | 535 2674 | 2675 | DB11D78A-2543-48A3-B195-09CC58045785 2676 | 2677 | xpos 2678 | 770 2679 | ypos 2680 | 1935 2681 | 2682 | E1E655F3-B8EB-4324-8B9D-25D3AC743045 2683 | 2684 | colorindex 2685 | 7 2686 | note 2687 | List all tags or search for tags 2688 | xpos 2689 | 210 2690 | ypos 2691 | 200 2692 | 2693 | E3B5F19A-B7EC-4984-A3CA-D626DB05ADF5 2694 | 2695 | xpos 2696 | 1660 2697 | ypos 2698 | 1930 2699 | 2700 | E6223AE8-EC41-40DA-88AC-8476D6F73ED1 2701 | 2702 | colorindex 2703 | 9 2704 | note 2705 | MD Fetch to get HTML into MD Note 2706 | xpos 2707 | 425 2708 | ypos 2709 | 1920 2710 | 2711 | E821ABDF-E08D-4BAE-83F0-4F24F4AB7B8C 2712 | 2713 | colorindex 2714 | 1 2715 | note 2716 | Get Url Scheme MD Link 2717 | xpos 2718 | 420 2719 | ypos 2720 | 1395 2721 | 2722 | E9AAB61A-3037-496F-B9A7-1D9166D62D6D 2723 | 2724 | xpos 2725 | 400 2726 | ypos 2727 | 2170 2728 | 2729 | EAEF0CF6-8AEA-4B7D-8EAB-80FC16EDDAAC 2730 | 2731 | xpos 2732 | 1840 2733 | ypos 2734 | 755 2735 | 2736 | EE67AF7B-BDB3-4BB5-B0DA-62F1D88921B5 2737 | 2738 | note 2739 | Open md file with prefrred editor 2740 | xpos 2741 | 1580 2742 | ypos 2743 | 1315 2744 | 2745 | EEAFA237-F675-43C1-9EB8-A1AE88C34E75 2746 | 2747 | colorindex 2748 | 6 2749 | note 2750 | File Action to get MD Note Link 2751 | xpos 2752 | 215 2753 | ypos 2754 | 1060 2755 | 2756 | F8CB74BA-4C13-4621-BC60-E50650C85A9D 2757 | 2758 | colorindex 2759 | 7 2760 | note 2761 | Template Selector for new Notes 2762 | xpos 2763 | 215 2764 | ypos 2765 | 1630 2766 | 2767 | F92C44A1-5C7A-4863-9FC1-F0188FF998E3 2768 | 2769 | xpos 2770 | 1295 2771 | ypos 2772 | 790 2773 | 2774 | F993108F-7C6F-4AF3-90D3-14A1CBAAEBBE 2775 | 2776 | xpos 2777 | 780 2778 | ypos 2779 | 205 2780 | 2781 | FA2A221A-6386-4B79-950C-7C46E10196EE 2782 | 2783 | xpos 2784 | 1385 2785 | ypos 2786 | 1070 2787 | 2788 | 2789 | userconfigurationconfig 2790 | 2791 | 2792 | config 2793 | 2794 | default 2795 | 2796 | filtermode 2797 | 1 2798 | placeholder 2799 | 2800 | required 2801 | 2802 | 2803 | description 2804 | The path where MD Notes will be stored 2805 | label 2806 | Markdown Notes Folder 2807 | type 2808 | filepicker 2809 | variable 2810 | path_to_notes 2811 | 2812 | 2813 | config 2814 | 2815 | default 2816 | TextEdit.app 2817 | filtermode 2818 | 2 2819 | placeholder 2820 | 2821 | required 2822 | 2823 | 2824 | description 2825 | Path to preferred markdown editor 2826 | label 2827 | MD Editor 2828 | type 2829 | filepicker 2830 | variable 2831 | editor 2832 | 2833 | 2834 | config 2835 | 2836 | default 2837 | 2838 | filtermode 2839 | 2 2840 | placeholder 2841 | 2842 | required 2843 | 2844 | 2845 | description 2846 | Optional markdown viewer otherwise MD Editor will be used 2847 | label 2848 | MD Viewer 2849 | type 2850 | filepicker 2851 | variable 2852 | viewer 2853 | 2854 | 2855 | config 2856 | 2857 | default 2858 | {title} 2859 | placeholder 2860 | 2861 | required 2862 | 2863 | trim 2864 | 2865 | 2866 | description 2867 | Standard file format for new MD notes 2868 | label 2869 | Filename format 2870 | type 2871 | textfield 2872 | variable 2873 | filename_format 2874 | 2875 | 2876 | config 2877 | 2878 | default 2879 | .md 2880 | placeholder 2881 | 2882 | required 2883 | 2884 | trim 2885 | 2886 | 2887 | description 2888 | The MD files are text files with a specific extension (usually `.txt`or `.md`) 2889 | label 2890 | MD Notes extension 2891 | type 2892 | textfield 2893 | variable 2894 | ext 2895 | 2896 | 2897 | config 2898 | 2899 | default 2900 | Template.md 2901 | placeholder 2902 | 2903 | required 2904 | 2905 | trim 2906 | 2907 | 2908 | description 2909 | The file name that will be used as the default Template. Before templates can be used it is required to create the template.md 2910 | label 2911 | Default template file name 2912 | type 2913 | textfield 2914 | variable 2915 | default_template 2916 | 2917 | 2918 | config 2919 | 2920 | default 2921 | url 2922 | placeholder 2923 | 2924 | required 2925 | 2926 | trim 2927 | 2928 | 2929 | description 2930 | The tag that defines a bookmark note 2931 | label 2932 | Bookmark Tag 2933 | type 2934 | textfield 2935 | variable 2936 | bookmark_tag 2937 | 2938 | 2939 | config 2940 | 2941 | default 2942 | #Template 2943 | placeholder 2944 | 2945 | required 2946 | 2947 | trim 2948 | 2949 | 2950 | description 2951 | The template tag defines which (#Tagname) defines a Template 2952 | label 2953 | Template tag 2954 | type 2955 | textfield 2956 | variable 2957 | template_tag 2958 | 2959 | 2960 | config 2961 | 2962 | default 2963 | %d.%m.%Y 2964 | placeholder 2965 | 2966 | required 2967 | 2968 | trim 2969 | 2970 | 2971 | description 2972 | Defines date format when creating new notes or when using placeholders in templates 2973 | label 2974 | Date Format 2975 | type 2976 | textfield 2977 | variable 2978 | default_date_format 2979 | 2980 | 2981 | config 2982 | 2983 | default 2984 | 2985 | required 2986 | 2987 | text 2988 | 2989 | 2990 | description 2991 | Defines if the search should match the exact search term 2992 | label 2993 | Exact Match 2994 | type 2995 | checkbox 2996 | variable 2997 | exact_match 2998 | 2999 | 3000 | config 3001 | 3002 | default 3003 | 3004 | required 3005 | 3006 | text 3007 | 3008 | 3009 | description 3010 | Tags can be used in the YAML front matter (`Tags: #mytag`) or within the MD note 3011 | label 3012 | Search in YAML tags only 3013 | type 3014 | checkbox 3015 | variable 3016 | search_yaml_tags_only 3017 | 3018 | 3019 | config 3020 | 3021 | default 3022 | True 3023 | pairs 3024 | 3025 | 3026 | Newest to oldest 3027 | True 3028 | 3029 | 3030 | Oldest to Newest 3031 | False 3032 | 3033 | 3034 | 3035 | description 3036 | Sort order when using Todo Search 3037 | label 3038 | Todo sort oder 3039 | type 3040 | popupbutton 3041 | variable 3042 | todo_newest_oldest 3043 | 3044 | 3045 | config 3046 | 3047 | default 3048 | x-marked://open/?file= 3049 | placeholder 3050 | 3051 | required 3052 | 3053 | trim 3054 | 3055 | 3056 | description 3057 | URL scheme to open the note in markdown editor or viewer 3058 | label 3059 | URL Scheme 3060 | type 3061 | textfield 3062 | variable 3063 | url_scheme 3064 | 3065 | 3066 | variablesdontexport 3067 | 3068 | version 3069 | 3.3.3 3070 | webaddress 3071 | https://github.com/Acidham/alfred-markdown-notes 3072 | 3073 | 3074 | -------------------------------------------------------------------------------- /src/md_clean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from urllib import parse 6 | 7 | from Alfred3 import Items as Items 8 | from Alfred3 import Keys as K 9 | from Alfred3 import Tools as Tools 10 | from MyNotes import Search 11 | 12 | 13 | def write_summary(md_notes_list, tag): 14 | """ 15 | Write list of md notes with tag into tmp summary.md 16 | 17 | Args: 18 | 19 | md_notes_list (list): List of Notes containg tag name 20 | tag (str): Name of the tag 21 | 22 | Returns: 23 | 24 | str: path to summary.md file 25 | """ 26 | cache_dir = Tools.getCacheDir() 27 | cache_file_path = os.path.join(cache_dir, "summary.md") 28 | if os.path.exists(cache_file_path): 29 | os.remove(cache_file_path) 30 | content_list = [f"### MD Notes with tag: {tag}"] 31 | for el in md_notes_list: 32 | enc_path = parse.quote(el['path']) 33 | content_list.append(f"* [{el['title']}]({enc_path}) `{el['filename']}`") 34 | content = "\n".join(content_list) 35 | with open(cache_file_path, "w") as f: 36 | f.write(content) 37 | return cache_file_path 38 | 39 | 40 | # create MD search object 41 | md_search = Search() 42 | 43 | # Load Env variables 44 | query = Tools.getArgv(1) if len(Tools.getArgv(1)) > 2 else "" 45 | if query is not "" and not query.startswith("#"): 46 | query = f"#{query}" 47 | 48 | 49 | # Get Search config with AND and OR 50 | search_terms, search_type = md_search.get_search_config(query) 51 | 52 | # create WF object 53 | wf = Items() 54 | 55 | # exec search if search terms were entered 56 | if len(search_terms) > 0: 57 | sorted_file_list = md_search.notes_search(search_terms, search_type) 58 | matches = len(sorted_file_list) 59 | file_pathes = [f['path'] for f in sorted_file_list] 60 | arg_string = "|".join(file_pathes) 61 | summary_file = write_summary(sorted_file_list, query) 62 | if matches > 0: 63 | wf.setItem( 64 | title=f"Batch delete {matches} notes with tag {query}", 65 | subtitle=f"{K.ENTER} to delete ALL notes and corresponding Assets. THIS CANNOT BE UNDONE!", 66 | arg=arg_string 67 | ) 68 | wf.setIcon(m_path='icons/delete.png', m_type='image') 69 | wf.addItem() 70 | wf.setItem( 71 | title="Preview affected MD Notes", 72 | subtitle=f"Preview of affected md notes. {K.SHIFT} for Quicklook, {K.ENTER} to open. NOTES ARE NOT DELETED!", 73 | type='file', 74 | arg=summary_file 75 | ) 76 | wf.setIcon(m_path='icons/clipboard.png', m_type='image') 77 | wf.addItem() 78 | else: 79 | wf.setItem( 80 | title=f"MD notes not found with tag: {query}", 81 | subtitle="Try another tag name", 82 | valid=False 83 | ) 84 | wf.addItem() 85 | else: 86 | wf.setItem( 87 | title="Enter a tag name", 88 | subtitle="tag name with or without leading #", 89 | valid=False 90 | ) 91 | wf.addItem() 92 | wf.write() 93 | -------------------------------------------------------------------------------- /src/notes_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from unicodedata import normalize 4 | 5 | from Alfred3 import Items as Items 6 | from Alfred3 import Keys as K 7 | from Alfred3 import Tools as Tools 8 | from MyNotes import Search 9 | 10 | CMD = u'\u2318' 11 | SHIFT = u'\u21E7' 12 | 13 | # log Python version 14 | Tools.logPyVersion() 15 | 16 | # create MD search object 17 | md_search = Search() 18 | 19 | query = normalize('NFC', Tools.getArgv(1)) 20 | 21 | # Get Search config with AND and OR 22 | search_terms, search_type = md_search.get_search_config(query) 23 | 24 | # create WF object 25 | wf = Items() 26 | 27 | # exec search if search terms were entered 28 | if len(search_terms) > 0: 29 | sorted_file_list = md_search.notes_search(search_terms, search_type) 30 | # get full list of file in case no search was entered 31 | else: 32 | sorted_file_list = md_search.getFilesListSorted() 33 | # Write search results into WF object 34 | for f in sorted_file_list: 35 | c_date = Tools.getDateStr(f['ctime']) 36 | m_date = Tools.getDateStr(f['mtime']) 37 | wf.setItem( 38 | title=f['title'], 39 | subtitle=f"Created: {c_date}, Modified: {m_date} ({K.CMD} Actions, {K.SHIFT} Quicklook)", 40 | type='file', 41 | arg=f['path'] 42 | ) 43 | # Mod for CMD - new action menu 44 | wf.addMod( 45 | key="cmd", 46 | arg=f"{f['path']}>{query}", 47 | subtitle="Enter Actions Menu for the Note...", 48 | icon_path="icons/action.png", 49 | icon_type="image" 50 | ) 51 | wf.addItem() 52 | 53 | if len(wf.getItems(response_type="dict")['items']) == 0: 54 | wf.setItem( 55 | title="Nothing found...", 56 | subtitle=f'Do you want to create a new note with title "{query}"?', 57 | arg=query 58 | ) 59 | wf.addItem() 60 | wf.write() 61 | -------------------------------------------------------------------------------- /src/search_actions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from urllib.request import pathname2url 6 | 7 | from Alfred3 import Items, Tools 8 | from MyNotes import Search 9 | 10 | # Get NotePath as path_query env variable 11 | note_path = Tools.getEnv("path_query1") 12 | # Get query used in search markdown notes as path_query env variable 13 | query = Tools.getEnv("path_query2") 14 | md_notes = Search() 15 | # Get NoteTitle for specific note 16 | note_title = md_notes.getNoteTitle(note_path) 17 | file_name = pathname2url(os.path.basename(note_path)) 18 | # If query in notes search was empty subtitle uses following string 19 | back_query = "" if not query else query 20 | 21 | # Get Viewer if available 22 | viewer = Tools.getEnv('viewer') if Tools.getEnv('viewer') else Tools.getEnv('editor') 23 | 24 | # Actions in ScriptFilter menu data 25 | ACTIONS = [ 26 | { 27 | "title": "Back", 28 | "subtitle": f"Back to Search with query: {back_query}", 29 | "arg": f"back|{query}", 30 | "icon": "icons/back.png", 31 | "visible": True 32 | }, 33 | { 34 | "title": "Markdown Link", 35 | "subtitle": f"Copy MD Link for \"{note_title}\" to the Clipboard", 36 | "arg": f"link|[{note_title}]({file_name})", 37 | "icon": "icons/link.png", 38 | "visible": True 39 | }, 40 | { 41 | "title": "Viewer", 42 | "subtitle": "Open Preview in MD Viewer", 43 | "arg": f"viewer|{note_path}", 44 | "icon": "icons/marked.png", 45 | "visible": True 46 | }, 47 | { 48 | "title": "Url Scheme", 49 | "subtitle": "Copy Url Scheme as Markdown Link to Clipboard", 50 | "arg": f"urlscheme|{note_path}", 51 | "icon": "icons/scheme.png", 52 | "visible": Tools.getEnv("url_scheme") 53 | }, 54 | { 55 | "title": "Delete Note", 56 | "subtitle": f'Delete "{note_title}". This action cannot be undone!', 57 | "arg": f"delete|{note_path}>{query}", 58 | "icon": "icons/delete.png", 59 | "visible": True 60 | }, 61 | ] 62 | 63 | # Generate ScriptFilter Output 64 | wf = Items() 65 | for a in ACTIONS: 66 | val = a.get('visible') 67 | if val: 68 | wf.setItem( 69 | title=a.get("title"), 70 | subtitle=a.get("subtitle"), 71 | arg=a.get("arg") 72 | ) 73 | wf.setIcon(m_path=a.get("icon"), m_type="image") 74 | wf.addItem() 75 | wf.write() 76 | -------------------------------------------------------------------------------- /src/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | 4 | from Alfred3 import Items, Plist, Tools 5 | from Alfred3 import Keys as K 6 | 7 | 8 | def get_variables(): 9 | config = Plist() 10 | return config.getConfig() 11 | 12 | 13 | def print_config(q): 14 | variables = get_variables() 15 | for k, v in variables.items(): 16 | if q == str() or q in k: 17 | v_subtitle = '' if v == str() else v 18 | wf.setItem( 19 | title=k, 20 | subtitle=f'Value: {v_subtitle} , \u21E7 for Help.', 21 | arg='selection|%s|%s' % (k, v), 22 | quicklookurl=f"file://{wf_dir}/docs/{k}.md" 23 | ) 24 | icon = 'icons/check.png' if v != str() else 'icons/question.png' 25 | wf.setIcon( 26 | icon, 27 | 'image' 28 | ) 29 | wf.addItem() 30 | 31 | 32 | def get_selection(key, query: str): 33 | variables = get_variables() 34 | if key in variables: 35 | v = variables[key] 36 | isValid = False if query == str() else True 37 | wf.setItem( 38 | title=f'Change {key}: {v}', 39 | subtitle=f'Add new value for "{key}" and press enter. {K.SHIFT} for Help, {K.CMD} to delete value', 40 | arg='set|%s|%s' % (key, query), 41 | valid=isValid, 42 | quicklookurl=f"file://{wf_dir}/docs/{key}.md" 43 | ) 44 | wf.setIcon( 45 | 'icons/edit.png', 46 | 'image' 47 | ) 48 | wf.addMod( 49 | key='cmd', 50 | subtitle='Delete Value', 51 | arg=f'set|{key}|' 52 | ) 53 | wf.addModsToItem() 54 | wf.addItem() 55 | else: 56 | wf.setItem( 57 | title="variable not found", 58 | valid=False 59 | ) 60 | wf.addItem() 61 | 62 | 63 | def write_config(key, value: str): 64 | """ 65 | Writes config item to plist file 66 | 67 | Args: 68 | 69 | key (str): key of key-value pair 70 | value (str): value of key-value pair 71 | """ 72 | config = Plist() 73 | config.setVariable(key, value) 74 | value = '' if value == str() else value 75 | wf.setItem( 76 | title="Proceed?", 77 | subtitle=f'{key} will be changed to: {value}', 78 | arg='' 79 | ) 80 | wf.setIcon( 81 | 'icons/hand.png', 82 | 'image' 83 | ) 84 | wf.addItem() 85 | 86 | 87 | query = Tools.getArgv(1) 88 | action_key_value = Tools.getEnv('action_key_value') 89 | [action, key, value] = action_key_value.split('|') if action_key_value != str() else [str(), str(), str()] 90 | wf_dir = os.getcwd() 91 | query = Tools.getArgv(1) 92 | 93 | wf = Items() 94 | 95 | if action == str(): 96 | print_config(query) 97 | elif action == 'selection': 98 | get_selection(key, query) 99 | else: 100 | write_config(key, value) 101 | wf.write() 102 | -------------------------------------------------------------------------------- /src/tag_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from unicodedata import normalize 5 | 6 | from Alfred3 import Items as Items 7 | from Alfred3 import Keys as K 8 | from Alfred3 import Tools as Tools 9 | from MyNotes import Search 10 | 11 | # create MD search object 12 | md = Search() 13 | 14 | # Get environment variables 15 | ext = md.getNotesExtension() 16 | query = normalize('NFC', Tools.getArgv(1)) # Tag name 17 | 18 | if query is str(): 19 | # Tag Search and sort based on tag name 20 | tag_results = md.tagSearch(query, 'tag', reverse=False) 21 | else: 22 | # Tag Search and sort based on number of Hits 23 | tag_results = md.tagSearch(query, 'count', reverse=True) 24 | 25 | wf = Items() 26 | 27 | if bool(tag_results): 28 | for tag, counter in tag_results.items(): 29 | wf.setItem( 30 | title=f'{tag}', 31 | subtitle=f"{counter} Hit(s), ({K.CMD} to paste tag into frontmost app)", 32 | valid=True, 33 | arg=f'#{tag}' 34 | ) 35 | wf.setIcon('icons/hashtag.png', 'image') 36 | wf.addMod( 37 | key='cmd', 38 | arg=f'#{tag} ', 39 | subtitle='Paste Tag into frontmost app', 40 | icon_path='icons/paste.png', 41 | icon_type='image' 42 | ) 43 | wf.addItem() 44 | else: 45 | wf.setItem( 46 | title="No Tags found!", 47 | subtitle="No Tags matches search term", 48 | valid=False 49 | ) 50 | wf.addItem() 51 | wf.write() 52 | -------------------------------------------------------------------------------- /src/template.md: -------------------------------------------------------------------------------- 1 | --- 2 | Created: {date} 3 | Tags: #Template 4 | --- 5 | 6 | # {title} 7 | 8 | ``` 9 | This is the fallback template! Read the help document on how to build your own template 10 | ``` 11 | -------------------------------------------------------------------------------- /src/template_selector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import MyNotes 5 | from Alfred3 import Items, Tools 6 | 7 | 8 | def get_template_tag() -> str: 9 | tt = Tools.getEnv('template_tag') 10 | if '#' not in tt or tt == str(): 11 | tt = '#Template' 12 | return tt 13 | 14 | 15 | SUFFIX = " (DEFAULT)" 16 | 17 | # create MD search object 18 | my_notes = MyNotes.Search() 19 | 20 | # Load env variables 21 | ext = my_notes.getNotesExtension() 22 | query = Tools.getArgv(1) 23 | default_template = Tools.getEnv('default_template') 24 | template_tag = get_template_tag() 25 | 26 | # Get Files sorted in Notes directory 27 | all_files = my_notes.getFilesListSorted() 28 | template_files = sorted(all_files, key=lambda x: x['filename'] != default_template) 29 | 30 | wf = Items() 31 | for md_file in template_files: 32 | if my_notes.isNoteTagged(md_file['path'], template_tag) and query in md_file['filename']: 33 | suffix = str() 34 | if md_file['filename'] == default_template: 35 | suffix = SUFFIX 36 | wf.setItem( 37 | title=md_file['filename'] + suffix, 38 | subtitle=f"Create new file based on \"{md_file['filename']}\"", 39 | arg=Tools.strJoin(md_file['path']), 40 | type='file' 41 | ) 42 | wf.addItem() 43 | wf.write() 44 | -------------------------------------------------------------------------------- /src/todo_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from urllib.request import pathname2url 5 | 6 | import MyNotes 7 | from Alfred3 import Items as Items 8 | from Alfred3 import Keys as K 9 | from Alfred3 import Tools as Tools 10 | 11 | # create MD search object 12 | md_search = MyNotes.Search() 13 | 14 | # Load environment variables 15 | ext = md_search.getNotesExtension() 16 | query = Tools.getArgv(1) # Search term 17 | 18 | todos = md_search.todoSearch(query) 19 | 20 | wf = Items() 21 | if len(todos) > 0: 22 | for i in todos: 23 | md_path = pathname2url(i['path']) 24 | md_title = i['title'] if i['title'] != str() else Tools.chop(i['filename'], ext) 25 | wf.setItem( 26 | title=i['todo'], 27 | subtitle=f"{K.ARROW_RIGHT} {md_title} (Created: {Tools.getDateStr(i['ctime'])}, Modified: {Tools.getDateStr(i['mtime'])})", 28 | arg=i['path'], 29 | valid=True, 30 | type='file' 31 | ) 32 | wf.setIcon('icons/unchecked.png', 'image') 33 | # Mod for CMD - new action menu 34 | wf.addMod( 35 | key="cmd", 36 | arg=f"{i['path']}>{query}", 37 | subtitle="Actions..", 38 | icon_path="icons/action.png", 39 | icon_type="image" 40 | ) 41 | wf.addItem() 42 | else: 43 | wf.setItem( 44 | title="No todo found!", 45 | subtitle="No todo matches search term", 46 | valid=False 47 | ) 48 | wf.addItem() 49 | wf.write() 50 | -------------------------------------------------------------------------------- /src/url_scheme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | 6 | from Alfred3 import Tools 7 | from MyNotes import Search 8 | 9 | f_path = Tools.getArgv(1) # Path to MD Note 10 | s = Search() 11 | url_scheme_path = s.getUrlScheme(f_path) 12 | filename = os.path.basename(f_path) 13 | md_link = f"[{filename}]({url_scheme_path})" 14 | sys.stdout.write(md_link) 15 | -------------------------------------------------------------------------------- /src/url_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from Alfred3 import Items, Tools 5 | from MyNotes import Search 6 | 7 | query = Tools.getArgv(1) 8 | bmt = Tools.getEnv('bookmark_tag') 9 | bookmark_tag = bmt if bmt.startswith('#') else f'#{bmt}' 10 | 11 | search_terms = f'{bookmark_tag}&{query}' if query else bookmark_tag 12 | 13 | notes = Search() 14 | search_terms, _ = notes.get_search_config(search_terms) 15 | matches = notes.url_search(search_terms) 16 | 17 | alf = Items() 18 | if matches: 19 | for m in matches: 20 | note_title = m.get('title') 21 | note_path = m.get('path') 22 | links = m.get('links') 23 | for link in links: 24 | url_title = link.get('url_title') 25 | url = link.get('url') 26 | subtitle = f'NOTE: {note_title} URL: {url[:30]}...' 27 | alf.setItem( 28 | title=url_title, 29 | subtitle=subtitle, 30 | arg=url, 31 | quicklookurl=url 32 | ) 33 | alf.addMod( 34 | 'cmd', 35 | note_path, 36 | 'Open MD Note', 37 | icon_path='icons/markdown.png', 38 | icon_type='image' 39 | ) 40 | alf.addMod( 41 | 'alt', 42 | url, 43 | "Copy URL to Clipboard", 44 | icon_path='icons/clipboard.png', 45 | icon_type='image' 46 | ) 47 | alf.addItem() 48 | else: 49 | alf.setItem( 50 | title='No Bookmarks found...', 51 | subtitle='try again', 52 | valid=False 53 | ) 54 | alf.addItem() 55 | alf.write() 56 | --------------------------------------------------------------------------------