├── magic-date-screenshot.png ├── .gitignore ├── screenshots └── 2023-03-18 new settings screen.png ├── version-bump.mjs ├── manifest.json ├── tsconfig.json ├── rollup.config.js ├── package.json ├── LICENSE ├── styles.css ├── .github └── workflows │ └── release.yml ├── README.md └── src └── main.ts /magic-date-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplGy/obsidian-open-file-by-magic-date/HEAD/magic-date-screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | main.js 11 | *.js.map 12 | data.json -------------------------------------------------------------------------------- /screenshots/2023-03-18 new settings screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplGy/obsidian-open-file-by-magic-date/HEAD/screenshots/2023-03-18 new settings screen.png -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // manifest.json - bump to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | manifest.version = targetVersion; 8 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 9 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-open-file-by-magic-date", 3 | "name": "Open File by Magic Date", 4 | "version": "0.1.2", 5 | "description": "Define a Moment.js date pattern that specifies the file that is most important to you (eg: your daily/weekly/monthly note). Will create the file if it doesn't exist.", 6 | "author": "simplgy", 7 | "authorUrl": "https://github.com/simplgy", 8 | "isDesktopOnly": false 9 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es5", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | 5 | export default { 6 | input: 'src/main.ts', 7 | output: { 8 | dir: '.', 9 | sourcemap: 'inline', 10 | format: 'cjs', 11 | exports: 'default' 12 | }, 13 | external: ['obsidian'], 14 | plugins: [ 15 | typescript(), 16 | nodeResolve({ browser: true }), 17 | commonjs(), 18 | ] 19 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-open-file-by-magic-date", 3 | "version": "0.1.2", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js", 9 | "version": "node version-bump.mjs && git add . && git commit -m 'bump' && git tag -a $npm_package_version -m \"$npm_package_version\" && git push origin $npm_package_version" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@rollup/plugin-commonjs": "^15.1.0", 16 | "@rollup/plugin-node-resolve": "^9.0.0", 17 | "@rollup/plugin-typescript": "^6.0.0", 18 | "@types/node": "^14.14.2", 19 | "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master", 20 | "rollup": "^2.32.1", 21 | "tslib": "^2.0.3", 22 | "typescript": "^4.0.3" 23 | }, 24 | "dependencies": { 25 | "@popperjs/core": "^2.9.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Eric Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Settings window */ 4 | 5 | .modal .open-file-by-magic-date input { 6 | width: 100%; 7 | } 8 | 9 | .modal .open-file-by-magic-date .setting-item { 10 | padding-bottom: 4px; 11 | } 12 | 13 | .modal .open-file-by-magic-date .info-tips { 14 | margin-top: 0; 15 | } 16 | 17 | .open-file-by-magic-date .margin-top { 18 | margin-top: 20px; 19 | } 20 | 21 | .flex-kids-y { 22 | display: flex; 23 | flex-direction: column; 24 | } 25 | 26 | .flex-start { 27 | align-items: flex-start; 28 | } 29 | 30 | .modal .open-file-by-magic-date .add-btn { 31 | margin-left: 7rem; /* align with inputs */ 32 | } 33 | 34 | .modal .open-file-by-magic-date .setting-item { 35 | align-items: flex-start; 36 | border: none; 37 | } 38 | 39 | .modal .open-file-by-magic-date .add-row .setting-item-control { 40 | justify-content: flex-start; 41 | } 42 | 43 | .modal .open-file-by-magic-date .add-row { 44 | border-top: 1px solid var(--background-modifier-border); 45 | margin-top: 10px; 46 | } 47 | 48 | .modal .open-file-by-magic-date .setting-item-info { 49 | width: 7rem; 50 | flex-grow: 0; 51 | margin-top: 6px; /* align with input box */ 52 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | PLUGIN_NAME: obsidian-open-file-by-magic-date 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: "14.x" 21 | 22 | - name: Build 23 | id: build 24 | run: | 25 | npm install 26 | npm run build 27 | mkdir ${{ env.PLUGIN_NAME }} 28 | cp main.js manifest.json ${{ env.PLUGIN_NAME }} 29 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 30 | ls 31 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 32 | 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | VERSION: ${{ github.ref }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload-zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload main.js 57 | id: upload-main 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./main.js 64 | asset_name: main.js 65 | asset_content_type: text/javascript 66 | 67 | - name: Upload manifest.json 68 | id: upload-manifest 69 | uses: actions/upload-release-asset@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} 74 | asset_path: ./manifest.json 75 | asset_name: manifest.json 76 | asset_content_type: application/json 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian - Open File by Magic Date 2 | 3 | Plugin for [Obsidian](https://obsidian.md) 4 | 5 | ## Summary 6 | 7 | A quick way to open files that match a date pattern. 8 | 9 | If you're trying to open a "weekly note" file or something similar, you're in the right place. This plugin has some flexibility that I couldn't find elsewhere. 10 | 11 | My use case: Open the note for "most recent monday". But, you can use it for any datestamped filename. 12 | 13 |  14 | 15 | ## Use Cases 16 | 17 | - [x] Open a file by any date pattern you can think of -- eg: `{YYYY-MM-DD} foo.md` 18 | - [x] Open files with a single keystroke 19 | - [x] Support anchoring on arbitrary days of the week, like "most recent monday". eg: `weekly notes/{mon: YYYY-MM-DD} week.md` 20 | - [x] Support multiple files. eg: `{MMMM} monthly note` and `{YYYY} yearly note` 21 | - [ ] Support specifying a search query, then open the first file that matches the query 22 | 23 | ## Installing 24 | 25 | 1. Open settings -> Third party plugin -> Disable Safe mode 26 | 1. Click "Browse community plugins" -> Search for "Magic File Hotkey" 27 | 1. Install it, then click "enable" 28 | 29 | ## Technical Details 30 | 31 | The key piece of code is here, where the input file pattern is parsed by `moment.js` either against today's date or a different one: 32 | 33 | ```js 34 | // send anything in curlies "{mon:...}" to moment.format for the preceeding monday 35 | // eg: `Weekly Notes/{mon:YYYY-MM-DD} week.md` 36 | str = str.replace(/{mon:(.*)}/g, (match, captured) => priorMonday.format(captured)); 37 | 38 | // send anything in curlies "{...}" to moment.format 39 | // eg: `Daily Notes/{YYYY-MM-DD}.md` 40 | str = str.replace(/{(.*)}/g, (_match, captured) => now.format(captured)); 41 | ``` 42 | 43 | ## Developing 44 | 45 | ### Building 46 | 47 | ``` 48 | # npm install 49 | npm run dev 50 | ``` 51 | 52 | (for auto refreshing) install `git clone https://github.com/pjeby/hot-reload.git` and turn it on 53 | 54 | ### TODOs 55 | 56 | - [x] Support multiple files, different hotkeys for each 57 | - [ ] when a filespec is removed, remove the associated hotkey command -- might not be possible 58 | - [x] Fix: the hotkey does not rename correctly when you edit the path (requires Obsidian restart) 59 | - [x] Fix: the hotkey does not stick around correctly when you edit the path (requires choosing a hotkey again after restart) 60 | - [x] Detect when the file exists, to help people check their syntax easily. ([example implementation](https://github.com/SilentVoid13/Templater/commit/e4273b706465df012648b8a0163018f4925b5808) of file.exists from the templater plugin) 61 | 62 | 63 | ### Releasing 64 | 65 | 1. Update the version in `package.json` (only) 66 | 2. `npm run version` 67 | 3. `git push` 68 | 69 | > This will trigger `.github/workflows/release.yml`. 70 | > 71 | > verify the workflow is running [here](https://github.com/SimplGy/obsidian-open-file-by-magic-date/actions). 72 | > Verify [releases here](https://github.com/SimplGy/obsidian-open-file-by-magic-date/releases) 73 | 74 | 4. (you're done) simply doing a github release and running release.yml will make the new version of the plugin available on the Obsidian marketplace. Nice! 75 | 76 | ## Thanks and credit 77 | 78 | Originally forked and learned from [Hotkeys for specific files](https://github.com/Vinzent03/obsidian-hotkeys-for-specific-files). Thank you, [Vinzent03](https://github.com/Vinzent03). 79 | 80 | ## Similar Plugins 81 | 82 | * [Homepage](https://github.com/mirnovov/obsidian-homepage) - open a specific note on startup 83 | * [Hotkeys for starred files](https://github.com/Vinzent03/obsidian-shortcuts-for-starred-files) 84 | * [Hotkeys for specific files](https://github.com/Vinzent03/obsidian-hotkeys-for-specific-files) -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { App, MarkdownView, Plugin, PluginSettingTab, Setting, TFile, moment } from 'obsidian'; 2 | 3 | 4 | 5 | // How many files are allowed to be configured? 6 | const FILE_LIMIT = 10; 7 | 8 | // iso weekday spec 9 | const DAYS = { 10 | mon: 1, 11 | tue: 2, 12 | wed: 3, 13 | thu: 4, 14 | fri: 5, 15 | sat: 6, 16 | sun: 7, 17 | } 18 | 19 | const ICONS = [ 20 | '🔮', 21 | '💎', 22 | '🍏', 23 | '🌼', 24 | '🍎', 25 | '💜', 26 | '🌀', 27 | '🐉', 28 | '⭐', 29 | '🚗', 30 | ] 31 | 32 | interface MagicFileHotkeySettings { 33 | files: string[]; 34 | useExistingPane: boolean; 35 | } 36 | 37 | const DEFAULT_SETTINGS: MagicFileHotkeySettings = { 38 | files: [ 39 | 'journal/{YYYY-MM-DD}.md' 40 | ], 41 | useExistingPane: true, 42 | }; 43 | 44 | // ---------------------------------------------------- Plugin Definition 45 | export default class MagicFileHotkeyPlugin extends Plugin { 46 | 47 | settings: MagicFileHotkeySettings; 48 | 49 | async onload() { 50 | await this.loadSettings(); 51 | console.log('loading ' + this.manifest.name); 52 | this.addSettingTab(new SettingsTab(this.app, this)); 53 | this.resetCommands(); 54 | } 55 | 56 | onunload() { 57 | console.log('unloading ' + this.manifest.name); 58 | } 59 | 60 | async loadSettings() { 61 | // this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 62 | this.settings = {...DEFAULT_SETTINGS, ...await this.loadData()}; 63 | } 64 | 65 | // note: this is called ~every keystroke, so be aware 66 | async saveSettings() { 67 | await this.saveData(this.settings); 68 | // update the commands, which is what hotkeys are set against 69 | this.resetCommands(); 70 | } 71 | 72 | resetCommands() { 73 | for (let i = 0; i < FILE_LIMIT; i++) { 74 | const id = `open-file-${i}`; // should not change over time. app name automatically added to prefix 75 | const fileNameSpec = (this.settings.files[i] || '').trim(); 76 | // const fileNameSpecNoExt = fileNameSpec.substring(0, fileNameSpec.lastIndexOf(".")) || fileNameSpec; 77 | const egName = lockInDate(fileNameSpec); 78 | 79 | // repeatedly calling with the same ID appears to be effectively an "update" operation 80 | if (fileNameSpec) { 81 | this.addCommand({ 82 | id, 83 | name: `${i+1} ${ICONS[i]} '${egName}'`, 84 | callback: () => { 85 | const fileName = lockInDate(fileNameSpec); 86 | this.openFile(fileName); 87 | }, 88 | // doesn't prevent from showing as an available hotkey: 89 | // checkCallback: (checking) => { 90 | // if (!fileNameSpec) return false; // not available 91 | // return true; 92 | // } 93 | }); 94 | } 95 | // TODO: remove commands 96 | } 97 | } 98 | 99 | // Desired behavior: focus the tab if it's already open. Open a new tab if it's not. 100 | // This would be simple, except for one thing: 101 | // the file you want to open might ALREADY be open in another tab. 102 | // That's the reason for "iterateAllLeaves" 103 | openFile(fileName: string) { 104 | 105 | // See if there's a tab open with this file in it: 106 | let found = false; 107 | this.app.workspace.iterateAllLeaves(leaf => { 108 | const file: TFile = (leaf.view as any).file; 109 | if (file?.path === fileName) { 110 | this.app.workspace.revealLeaf(leaf); 111 | if (leaf.view instanceof MarkdownView) { 112 | leaf.view.editor.focus(); 113 | } 114 | found = true; 115 | 116 | // console.log('FOUND A LEAF!', leaf); 117 | return; // don't keep looking 118 | } 119 | }); 120 | 121 | // Case: there isn't already a tab open with this file 122 | if (!found) { 123 | /* 124 | docs: 125 | https://marcus.se.net/obsidian-plugin-docs/reference/typescript/classes/Workspace#openlinktext 126 | openLinkText( 127 | linktext: string, 128 | sourcePath: string, 129 | newLeaf?: PaneType | boolean, // PaneType = 'tab' | 'split' | 'window'; // 2023-01-28: out of date docs. "Argument of type 'string' is not assignable to parameter of type 'boolean'" 130 | openViewState?: OpenViewState // no idea. https://marcus.se.net/obsidian-plugin-docs/reference/typescript/interfaces/OpenViewState 131 | ) 132 | */ 133 | this.app.workspace.openLinkText(fileName, "", true); 134 | } 135 | } 136 | } 137 | 138 | 139 | 140 | // ---------------------------------------------------- Settings Tab 141 | class SettingsTab extends PluginSettingTab { 142 | 143 | plugin: MagicFileHotkeyPlugin; 144 | 145 | constructor(app: App, plugin: MagicFileHotkeyPlugin) { 146 | super(app, plugin); 147 | this.plugin = plugin; 148 | } 149 | 150 | display() { 151 | // limit number of files allowed 152 | this.plugin.settings.files = this.plugin.settings.files.slice(0, FILE_LIMIT); 153 | this.plugin.saveSettings(); 154 | 155 | // remove empty entries 156 | // this.plugin.settings.files = this.plugin.settings.files.filter(file => file != null && file != ""); 157 | 158 | let { containerEl } = this; 159 | containerEl.empty(); 160 | const className = String(this.plugin.manifest.name).toLowerCase().replace(/\s/g, '-'); 161 | containerEl.addClass(className); 162 | containerEl.createEl("h2", { text: this.plugin.manifest.name }); 163 | 164 | const fileCount = this.plugin.settings.files.length || 1; // at least one, always. 165 | 166 | for (let i = 0; i < fileCount; i++) { 167 | this.renderSettingsRow(i); 168 | } 169 | 170 | if (this.plugin.settings.files.length < FILE_LIMIT) { 171 | new Setting(this.containerEl).addButton(cb => { 172 | cb 173 | // .setButtonText('Add another file') // can't have both icon and text? 174 | .setTooltip('Add another file') 175 | .setIcon('plus') 176 | .onClick(onClickAdd.bind(this)) 177 | ; 178 | }).setClass('add-row'); 179 | } 180 | 181 | function onClickAdd() { 182 | this.plugin.settings.files.push(''); // new empty file spec 183 | this.plugin.saveSettings(); 184 | this.display(); 185 | } 186 | 187 | containerEl.createEl("h2", { text: 'Tips', cls: 'margin-top' }); 188 | // add an explanation, but with more room 189 | const descEl = containerEl.createEl('div', { cls: 'setting-item-description info-tips' }); // small text 190 | descEl.innerHTML = ` 191 |
Use curly brackets to add date formats. eg: "{YYYY-MM-DD}".
192 |Any syntax from the moment.js library will work.
193 |Additionally, accepts a special format to indicate "prior monday". eg: "{mon:YYYY-MM-DD}"
194 |Include the '.md' extension in your filename if you use that.
195 | `; 196 | } 197 | 198 | renderSettingsRow(idx: number) { 199 | const curVal = this.plugin.settings.files[idx]; 200 | 201 | const setting = new Setting(this.containerEl).setName(`${ICONS[idx]} file:`); 202 | setting.controlEl.addClass('flex-kids-y'); 203 | setting.controlEl.addClass('flex-start'); 204 | setting.addText(cb => { 205 | cb 206 | .setPlaceholder("dir/{YYYY-MM-DD} file.md") 207 | .setValue(curVal) 208 | .onChange(onChange.bind(this)); 209 | }); 210 | 211 | // add an element to print out the computed path 212 | const outputEl = setting.controlEl.createEl('div', { 213 | text: lockInDate(curVal), 214 | cls: 'setting-item-description', // small, muted 215 | }); 216 | 217 | function onChange(value) { 218 | renderValidation(value, outputEl); 219 | this.onFileSettingChanged(idx, value); 220 | } 221 | 222 | // init the validation state 223 | // TODO: better way to get value of a setting? 224 | const curVal = setting.components[0].getValue(); 225 | renderValidation(curVal, outputEl); 226 | } 227 | 228 | // whenever the user types and changes the file spec setting 229 | onFileSettingChanged(idx, value) { 230 | this.plugin.settings.files[idx] = value; 231 | 232 | // remove any empty files and save: 233 | this.plugin.settings.files = this.plugin.settings.files.map(str => (str || '').trim()).filter(Boolean); 234 | this.plugin.saveSettings(); 235 | } 236 | 237 | 238 | } 239 | 240 | // For a given value, 241 | function renderValidation(value, outputEl) { 242 | if (!value) return outputEl.innerText = ''; 243 | 244 | // Tell user how/if we parsed it 245 | const parsedName = lockInDate(value); 246 | outputEl.innerText = `"${parsedName}"`; 247 | 248 | // Nothing Parsed, so we aren't using the date syntax 249 | if (parsedName === value) { 250 | outputEl.style.color = 'inherit'; 251 | 252 | // Parser changed something, so date syntax is active 253 | } else { 254 | // colored purple if date syntax is active 255 | outputEl.style.color = 'var(--text-accent)'; 256 | } 257 | 258 | // Checkmark if it also matches an existing file 259 | // this is a little funny, I think because Obsidian can match filenames with and without directories 260 | if(exists(parsedName)) { 261 | outputEl.innerText = `"${parsedName}" ✅`; 262 | } 263 | } 264 | 265 | // convert the input format YYYY to the current date 266 | function lockInDate(inputString: string): string { 267 | const now = moment(); 268 | 269 | let str = inputString; 270 | 271 | // If there's a weekday prefix, send that to the preceding, matching day 272 | // send anything in curlies "{mon:...}" to moment.format for the preceeding monday 273 | // eg: `Weekly Notes/{mon:YYYY-MM-DD} week.md` 274 | str = str.replace(/{mon:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.mon).format(captured)); 275 | str = str.replace(/{tue:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.tue).format(captured)); 276 | str = str.replace(/{wed:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.wed).format(captured)); 277 | str = str.replace(/{thu:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.thu).format(captured)); 278 | str = str.replace(/{fri:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.fri).format(captured)); 279 | str = str.replace(/{sat:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.sat).format(captured)); 280 | str = str.replace(/{sun:(.*)}/g, (_match, captured) => getPreviousWeekday(DAYS.sun).format(captured)); 281 | 282 | // send anything in curlies "{...}" to moment.format 283 | // eg: `Daily Notes/{YYYY-MM-DD}.md` 284 | // replace the entire match with a moment formatted version of the capture group 285 | str = str.replace(/{(.*)}/g, (_match, captured) => now.format(captured)); 286 | 287 | return str; 288 | } 289 | 290 | // Get the date of the previous day of the week you'd like. (eg: the most recent Monday) 291 | // isoWeekday: 1 for Monday, 7 for Sunday. 292 | function getPreviousWeekday(day: number) { 293 | const t = moment(); 294 | let guess = t.isoWeekday() 295 | let i = 0; 296 | 297 | while (day !== guess && i <= 7) { 298 | t.subtract(1, 'days'); 299 | guess = t.isoWeekday(); 300 | i++; // infinite loop blocker 301 | } 302 | return t; 303 | } 304 | 305 | // Check if a file exists. Depends on `app` global. 306 | function exists(filename: string) { 307 | const ref = app.metadataCache.getFirstLinkpathDest(filename, ""); 308 | return ref != null; 309 | } --------------------------------------------------------------------------------