mirror of
https://github.com/halogamer33/Obsidian-Fuzzy-Note-Creator.git
synced 2026-07-22 10:00:30 +00:00
feat: add note templating functionality
This commit is contained in:
parent
fca4592745
commit
4159abc212
8 changed files with 771 additions and 357 deletions
126
package-lock.json
generated
126
package-lock.json
generated
|
|
@ -19,16 +19,6 @@
|
|||
"typescript": "4.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
|
||||
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
||||
|
|
@ -37,9 +27,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.1.tgz",
|
||||
"integrity": "sha512-wLw0t3R9AwOSQThdZ5Onw8QQtem5asE7+bPlnzc57eubPqiuJKIzwjMZ+C42vQett+iva+J8VgFV4RYWDBh5FA==",
|
||||
"version": "6.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.30.0.tgz",
|
||||
"integrity": "sha512-96Nmn8OeLh6aONQprIeYk8hGVnEuYpWuxKSkdsODOx9hWPxyuyZGvmvxV/JmLsp+CubMO1PsLaN5TNNgrl0UrQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -417,9 +407,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@eslint-community/regexpp": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
|
||||
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
|
||||
"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
|
@ -464,6 +454,7 @@
|
|||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
||||
"deprecated": "Use @eslint/config-array instead",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -493,6 +484,7 @@
|
|||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
|
||||
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
|
||||
"deprecated": "Use @eslint/object-schema instead",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
|
|
@ -553,9 +545,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.18.94",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.94.tgz",
|
||||
"integrity": "sha512-X8q3DoKq8t/QhA0Rk/9wJUajxtXRDiCK+cVaONKLxpsjPhu+xX6uZuEj4UKGLQ4p0obTdFxa0cP/BMvf9mOYZA==",
|
||||
"version": "16.18.104",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.104.tgz",
|
||||
"integrity": "sha512-OF3keVCbfPlkzxnnDBUZJn1RiCJzKeadjiW0xTEb0G1SUJ5gDVb3qnzZr2T4uIFvsbKJbXy1v2DN7e2zaEY7jQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
|
|
@ -759,9 +751,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
|
|
@ -859,12 +851,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
|
@ -952,9 +944,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
|
|
@ -1204,9 +1196,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
||||
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1338,9 +1330,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
|
|
@ -1405,6 +1397,7 @@
|
|||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1528,6 +1521,7 @@
|
|||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1670,18 +1664,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
|
@ -1692,12 +1674,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1740,9 +1722,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/obsidian": {
|
||||
"version": "1.5.7-1",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.5.7-1.tgz",
|
||||
"integrity": "sha512-T5ZRuQ1FnfXqEoakTTHVDYvzUEEoT8zSPnQCW31PVgYwG4D4tZCQfKHN2hTz1ifnCe8upvwa6mBTAP2WUA5Vng==",
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.6.6.tgz",
|
||||
"integrity": "sha512-GZHzeOiwmw/wBjB5JwrsxAZBLqxGQmqtEKSvJJvT0LtTcqeOFnV8jv0ZK5kO7hBb44WxJc+LdS7mZgLXbb+qXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/codemirror": "5.60.8",
|
||||
|
|
@ -1764,18 +1746,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aashutoshrathi/word-wrap": "^1.2.3",
|
||||
"deep-is": "^0.1.3",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"levn": "^0.4.1",
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "^0.4.0"
|
||||
"type-check": "^0.4.0",
|
||||
"word-wrap": "^1.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
|
|
@ -1953,6 +1935,7 @@
|
|||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1989,13 +1972,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -2199,6 +2179,16 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
|
@ -2206,12 +2196,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FolderSelectionModal } from './main';
|
||||
import { FolderSelectionModal } from './folder-selection-modal';
|
||||
|
||||
export function AddCommands() {
|
||||
this.addCommand({
|
||||
|
|
|
|||
71
source/folder-selection-modal.ts
Normal file
71
source/folder-selection-modal.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { App, FuzzySuggestModal, normalizePath, Instruction, TFolder } from 'obsidian';
|
||||
import { FuzzyNoteCreatorSettings } from './settingsTab';
|
||||
import { NoteCreationModal } from './note-creation-modal';
|
||||
|
||||
// Modals are elements in the UI that require interaction from the user
|
||||
export class FolderSelectionModal extends FuzzySuggestModal<string> {
|
||||
settings: FuzzyNoteCreatorSettings;
|
||||
leafMode: string;
|
||||
|
||||
constructor(app: App, leafMode: string, settings: FuzzyNoteCreatorSettings) {
|
||||
super(app)
|
||||
this.settings = settings;
|
||||
this.leafMode = leafMode;
|
||||
|
||||
this.setPlaceholder('Path to the folder...');
|
||||
|
||||
if (settings.showInstructions) {
|
||||
const instructions: Instruction[] = [
|
||||
{command: '↵', purpose: 'to select folder'},
|
||||
{command: 'esc', purpose: 'to dismiss'},
|
||||
{command: '↑↓', purpose: 'to navigate'},
|
||||
{command: '⭾ tab', purpose: 'to autocomplete folder'},
|
||||
];
|
||||
|
||||
this.setInstructions(instructions);
|
||||
}
|
||||
|
||||
// Autocomplete selected folder with tab.
|
||||
this.inputEl.addEventListener('keydown', async (event: KeyboardEvent) => {
|
||||
if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
|
||||
const suggestions = this.resultContainerEl.children;
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
if (!suggestions[i].classList.contains('is-selected')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.inputEl.value = suggestions[i].getText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ── End of constructor ──────────────────────────────────────────────
|
||||
|
||||
// Get the folders in the vault
|
||||
getItems(): string[] {
|
||||
let dirs: string[] = [];
|
||||
|
||||
const abstractFiles = this.app.vault.getAllLoadedFiles();
|
||||
for (let i = 0; i < abstractFiles.length; i++) {
|
||||
if (abstractFiles[i] instanceof TFolder) {
|
||||
dirs.push(abstractFiles[i].path);
|
||||
}
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
getItemText(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
onChooseItem(path: string) {
|
||||
// normalizePath is an obsidian function (I still do some work to normalize the path down the line)
|
||||
const normalizedPath = normalizePath(path);
|
||||
new NoteCreationModal(this.app, normalizedPath, this.leafMode, this.settings).open();
|
||||
}
|
||||
}
|
||||
260
source/main.ts
260
source/main.ts
|
|
@ -1,7 +1,6 @@
|
|||
import { Plugin, Notice, App, FuzzySuggestModal, SuggestModal, normalizePath, moment, Instruction, Platform, TFolder } from 'obsidian';
|
||||
import { Plugin } from 'obsidian';
|
||||
import { FuzzyNoteCreatorSettingTab, FuzzyNoteCreatorSettings, DEFAULT_SETTINGS } from './settingsTab';
|
||||
import { AddCommands } from './commands';
|
||||
import { OpenNote } from './open-note';
|
||||
|
||||
export default class FuzzyNoteCreatorPlugin extends Plugin {
|
||||
settings: FuzzyNoteCreatorSettings;
|
||||
|
|
@ -13,263 +12,10 @@ export default class FuzzyNoteCreatorPlugin extends Plugin {
|
|||
async onload() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
|
||||
// Arguments: (app, plugin)
|
||||
this.addSettingTab(new FuzzyNoteCreatorSettingTab(this.app, this));
|
||||
|
||||
// Bind `this` context and then call the function
|
||||
AddCommands.bind(this)();
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderSelectionModal extends FuzzySuggestModal<string> {
|
||||
settings: FuzzyNoteCreatorSettings;
|
||||
leafMode: string;
|
||||
|
||||
constructor(app: App, leafMode: string, settings: FuzzyNoteCreatorSettings) {
|
||||
super(app)
|
||||
this.settings = settings;
|
||||
this.leafMode = leafMode;
|
||||
|
||||
this.setPlaceholder('Path to the folder...');
|
||||
|
||||
if (settings.showInstructions) {
|
||||
const instructions: Instruction[] = [
|
||||
{command: '↵', purpose: 'to select folder'},
|
||||
{command: 'esc', purpose: 'to dismiss'},
|
||||
{command: '↑↓', purpose: 'to navigate'},
|
||||
{command: '⭾ tab', purpose: 'to autocomplete folder'},
|
||||
];
|
||||
|
||||
this.setInstructions(instructions);
|
||||
}
|
||||
|
||||
this.inputEl.addEventListener('keydown', async (event: KeyboardEvent) => {
|
||||
if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
|
||||
const suggestions = this.resultContainerEl.children;
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
if (!suggestions[i].classList.contains('is-selected')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.inputEl.value = suggestions[i].getText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getItems(): string[] {
|
||||
let dirs: string[] = [];
|
||||
|
||||
const abstractFiles = this.app.vault.getAllLoadedFiles();
|
||||
for (let i = 0; i < abstractFiles.length; i++) {
|
||||
if (abstractFiles[i] instanceof TFolder) {
|
||||
dirs.push(abstractFiles[i].path);
|
||||
}
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
getItemText(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
onChooseItem(path: string) {
|
||||
const normalizedPath = normalizePath(path);
|
||||
new NoteTitleModal(this.app, normalizedPath, this.leafMode, this.settings).open();
|
||||
}
|
||||
}
|
||||
|
||||
export class NoteTitleModal extends SuggestModal<string> {
|
||||
settings: FuzzyNoteCreatorSettings;
|
||||
path: string;
|
||||
leafMode: string;
|
||||
|
||||
constructor(app: App, path: string, leafMode: string, settings: FuzzyNoteCreatorSettings) {
|
||||
super(app)
|
||||
this.settings = settings
|
||||
this.path = path;
|
||||
this.leafMode = leafMode;
|
||||
|
||||
if (settings.showInstructions) {
|
||||
const instructions: Instruction[] = [
|
||||
{command: '↵', purpose: 'to create note'},
|
||||
{command: 'esc', purpose: 'to dismiss'},
|
||||
// {command: '/', purpose: 'to create parent folder of the note'},
|
||||
];
|
||||
|
||||
this.setInstructions(instructions);
|
||||
}
|
||||
this.emptyStateText = 'No templates found.';
|
||||
this.setPlaceholder('Title of the note...');
|
||||
|
||||
if (!settings.useNoteTitleTemplates) {
|
||||
let previousModalJustClosed = new BooleanWrapper(true);
|
||||
this.inputEl.addEventListener('keyup', event => {
|
||||
this.handleEnterKey(event, previousModalJustClosed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.useNoteTitleTemplates) {
|
||||
let previousModalJustClosed = new BooleanWrapper(true);
|
||||
this.inputEl.addEventListener('keyup', event => {
|
||||
this.noTemplatesNoteCreation(event, previousModalJustClosed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async noTemplatesNoteCreation(event: KeyboardEvent | MouseEvent, previousModalJustClosed: BooleanWrapper) {
|
||||
const isKeyboardEvent = (event: KeyboardEvent | MouseEvent): event is KeyboardEvent => "key" in event;
|
||||
|
||||
if(isKeyboardEvent(event)) {
|
||||
const {key} = event;
|
||||
if (key !== 'Enter') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (previousModalJustClosed.value) {
|
||||
previousModalJustClosed.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getSuggestions(this.inputEl.value).length === 0) {
|
||||
this.handleEnterKey(event, previousModalJustClosed)
|
||||
}
|
||||
}
|
||||
|
||||
getSuggestions(query: string): string[] {
|
||||
if (!this.settings.useNoteTitleTemplates) {
|
||||
this.resultContainerEl.hide();
|
||||
return [];
|
||||
}
|
||||
if (this.settings.noteTitleTemplates === '' || this.settings.noteTitleTemplates === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const templates = this.settings.noteTitleTemplates.split('\n');
|
||||
const trimmedTemplates = templates.map(template => template.trim())
|
||||
return trimmedTemplates.filter(template => {
|
||||
return template.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
renderSuggestion(template: string, el: HTMLElement) {
|
||||
el.createEl('div', {text: template});
|
||||
}
|
||||
|
||||
onChooseSuggestion(noteName: string, evt: MouseEvent | KeyboardEvent) {
|
||||
const formatedNoteName = moment().format(noteName)
|
||||
|
||||
this.handleEnterKey(evt, new BooleanWrapper(false), formatedNoteName)
|
||||
}
|
||||
|
||||
async handleEnterKey(event: KeyboardEvent | MouseEvent, previousModalJustClosed: BooleanWrapper, noteName?: string) {
|
||||
const isKeyboardEvent = (event: KeyboardEvent | MouseEvent): event is KeyboardEvent => "key" in event;
|
||||
|
||||
if(isKeyboardEvent(event)) {
|
||||
const {key} = event;
|
||||
if (key !== 'Enter') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (previousModalJustClosed.value) {
|
||||
previousModalJustClosed.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName === undefined) {
|
||||
noteName = this.inputEl.value;
|
||||
}
|
||||
|
||||
noteName = noteName.trim();
|
||||
|
||||
if (noteName.length == 0 && !this.settings.allowUntitledNotes) {
|
||||
new Notice('Add a title to the note' , 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.length == 0 && this.settings.allowUntitledNotes) {
|
||||
noteName = (this.settings.untitledNoteName.length == 0) ? DEFAULT_SETTINGS.untitledNoteName! : this.settings.untitledNoteName!;
|
||||
}
|
||||
|
||||
const windowsCompatibility: boolean = (
|
||||
Platform.isWin || this.settings.windowsNoteTitleCompatibility
|
||||
);
|
||||
|
||||
if (windowsCompatibility && noteName.match(/(<|>|:|"|\||\?|\*)/) !== null) {
|
||||
new Notice(`The note title must not include any of this characters: < > : " ? | *`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.match(/^[\\\/\s]+$/)) {
|
||||
new Notice(`The note title must not be only '\\' or '/'`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.match(/(\/|\\)/) !== null) {
|
||||
let noteNameWithDirs = normalizePath(noteName).split('/').map(link => link.trim()).filter(link => link.length !== 0);
|
||||
let parentDirs = `${this.path}/`;
|
||||
for (let i = 0; i < noteNameWithDirs.length - 1; i++) {
|
||||
noteNameWithDirs[i] += '/';
|
||||
parentDirs += noteNameWithDirs[i];
|
||||
}
|
||||
parentDirs = parentDirs.slice(0, -1);
|
||||
|
||||
if (this.app.vault.getFolderByPath(parentDirs) === null) {
|
||||
this.app.vault.createFolder(parentDirs);
|
||||
}
|
||||
noteName = noteNameWithDirs.join('');
|
||||
}
|
||||
|
||||
let noteExtension = this.settings.defaultNoteExtension;
|
||||
if (noteExtension.length == 0) {
|
||||
noteExtension = DEFAULT_SETTINGS.defaultNoteExtension!;
|
||||
}
|
||||
|
||||
if (windowsCompatibility && noteExtension.match(/(<|>|:|"|\\|\||\?|\/|\*|\/)/) !== null) {
|
||||
new Notice(`The note extension must not include any of this characters: < > : " \ ? | * /`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteExtension.match(/(\/)/) !== null) {
|
||||
new Notice(`The note extension must not include the character: /`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
const notePath = (this.path.length == 1) ? `${noteName}${noteExtension}` : `${this.path}/${noteName}${noteExtension}`;
|
||||
|
||||
const normalizedNotePath= normalizePath(notePath);
|
||||
|
||||
const fileAlreadyExists = (this.app.vault.getFileByPath(normalizedNotePath) !== null) ? true : false;
|
||||
|
||||
if (fileAlreadyExists) {
|
||||
new Notice(`That note already exists`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
const newNote = await this.app.vault.create(`${normalizedNotePath}`, '');
|
||||
|
||||
if (newNote == null) {
|
||||
new Notice('Error opening the file, report issue to GitHub', 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newNote.extension !== 'md') {
|
||||
new Notice(`Created note ${noteName}${noteExtension}, opening it on the system's default application if there is one`, 4000);
|
||||
}
|
||||
|
||||
OpenNote.bind(this)(newNote);
|
||||
}
|
||||
}
|
||||
|
||||
class BooleanWrapper {
|
||||
value: boolean;
|
||||
constructor(value: boolean) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
401
source/note-creation-modal.ts
Normal file
401
source/note-creation-modal.ts
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
import { Notice, App, SuggestModal, normalizePath, moment, Instruction, Platform } from 'obsidian';
|
||||
import { FuzzyNoteCreatorSettings, DEFAULT_SETTINGS } from './settingsTab';
|
||||
import { OpenNote } from './open-note';
|
||||
|
||||
class BooleanWrapper {
|
||||
value: boolean;
|
||||
constructor(value: boolean) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
type NoteType = "TitleTemplate" | "NoteTemplate";
|
||||
|
||||
type Suggestion = {
|
||||
text: string;
|
||||
type: NoteType;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
type NoteTemplate = {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class NoteCreationModal extends SuggestModal<Suggestion> {
|
||||
settings: FuzzyNoteCreatorSettings;
|
||||
path: string;
|
||||
leafMode: string;
|
||||
noteTemplate: NoteTemplate;
|
||||
|
||||
constructor(app: App, path: string, leafMode: string, settings: FuzzyNoteCreatorSettings, noteTemplate?: NoteTemplate) {
|
||||
super(app)
|
||||
this.settings = settings
|
||||
this.path = path;
|
||||
this.leafMode = leafMode;
|
||||
|
||||
this.emptyStateText = 'No templates found.';
|
||||
this.setPlaceholder('Title of the note...');
|
||||
|
||||
if (noteTemplate !== undefined) {
|
||||
this.noteTemplate = noteTemplate;
|
||||
}
|
||||
|
||||
if (settings.showInstructions) {
|
||||
const instructions: Instruction[] = [
|
||||
{command: '↵', purpose: 'to create note'},
|
||||
{command: 'esc', purpose: 'to dismiss'},
|
||||
// {command: '/', purpose: 'to create parent folder of the note'},
|
||||
];
|
||||
this.setInstructions(instructions);
|
||||
}
|
||||
|
||||
let previousModalJustClosed = new BooleanWrapper(true);
|
||||
if (noteTemplate !== undefined) {
|
||||
this.inputEl.addEventListener('keyup', event => {
|
||||
this.enterKeyHandler(event, previousModalJustClosed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.useNoteTitleTemplates) {
|
||||
this.inputEl.addEventListener('keyup', event => {
|
||||
this.enterKeyHandler(event, previousModalJustClosed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.useNoteTitleTemplates) {
|
||||
this.inputEl.addEventListener('keyup', event => {
|
||||
this.usingTemplatesHandler(event, previousModalJustClosed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async usingTemplatesHandler(event: KeyboardEvent | MouseEvent, previousModalJustClosed: BooleanWrapper) {
|
||||
const isKeyboardEvent = (event: KeyboardEvent | MouseEvent): event is KeyboardEvent => "key" in event;
|
||||
|
||||
if(isKeyboardEvent(event)) {
|
||||
const {key} = event;
|
||||
if (key !== 'Enter') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (previousModalJustClosed.value) {
|
||||
previousModalJustClosed.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* This only triggers if the user pressed 'enter' when no suggestion was displayed,
|
||||
* aka: doesn't want to use any template and wants to create a blank note. Otherwise
|
||||
* the program goes to the function 'OnChooseSuggestion' */
|
||||
if (this.getSuggestions(this.inputEl.value).length === 0) {
|
||||
this.enterKeyHandler(event, previousModalJustClosed)
|
||||
}
|
||||
}
|
||||
|
||||
getSuggestions(query: string): Suggestion[] {
|
||||
let suggestions: Suggestion[] = [];
|
||||
|
||||
if ((!this.settings.useNoteTitleTemplates && !this.settings.useNoteTemplates) || this.noteTemplate !== undefined) {
|
||||
this.resultContainerEl.hide();
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.settings.useNoteTitleTemplates) {
|
||||
const noteTitleTemplates = this.settings.noteTitleTemplates.split('\n');
|
||||
const trimmedTemplates = noteTitleTemplates.map(template => template.trim())
|
||||
const filteredTemplates = trimmedTemplates.filter(template => {
|
||||
return template.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
|
||||
const areThereTemplates = filteredTemplates[0] !== "";
|
||||
if (areThereTemplates) {
|
||||
for (let i = 0; i < filteredTemplates.length; i++) {
|
||||
const currentTemplate = filteredTemplates[i];
|
||||
const suggestion: Suggestion = {
|
||||
text: currentTemplate,
|
||||
type: "TitleTemplate",
|
||||
query: query.trim(),
|
||||
}
|
||||
suggestions.push(suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.settings.useNoteTemplates) {
|
||||
const allNotes = this.app.vault.getFiles();
|
||||
const templatesFolder = this.settings.noteTemplatesFolder;
|
||||
let noteTemplatesFilePaths: string[] = [];
|
||||
|
||||
for (let i = 0; i < allNotes.length; i++) {
|
||||
const currentNote = allNotes[i];
|
||||
const notePath = currentNote.path;
|
||||
|
||||
if (notePath.startsWith(templatesFolder) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (query.trim() === "") {
|
||||
noteTemplatesFilePaths.push(notePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
type queryWrapper = {
|
||||
query: string;
|
||||
found: boolean;
|
||||
}
|
||||
|
||||
const queryWrappers: queryWrapper[] = query.trim().split(" ").map((query) => {return {query: query, found: false}});
|
||||
const pathElements = notePath.split(" ");
|
||||
// console.log(`\n\n\n-------------------\nNEW CYCLE`);
|
||||
// console.log(`Working with the note:\n\n${notePath}\n`);
|
||||
for (let p = 0; p < pathElements.length; p++) {
|
||||
let headIndex: number = 0;
|
||||
// console.log(`NEW CYCLE`);
|
||||
for (let q = 0; q < queryWrappers.length; q++) {
|
||||
// console.log(`current path element:`, pathElements[p]);
|
||||
// console.log(`current query element:`, queryWrappers[q].query);
|
||||
if (queryWrappers[q].found === true) {
|
||||
// console.log("Already found, continuing");
|
||||
// console.log(`\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexOfQuery = pathElements[p].toLowerCase().indexOf(queryWrappers[q].query.toLowerCase(), headIndex);
|
||||
if (indexOfQuery === -1) {
|
||||
// console.log(`'${queryWrappers[q].query}' not found on '${pathElements[p].slice(headIndex)}', breaking.`);
|
||||
// console.log(`\n`);
|
||||
break;
|
||||
}
|
||||
// console.log(`'${queryWrappers[q].query}' found on '${pathElements[p].slice(headIndex)}'`);
|
||||
|
||||
headIndex = indexOfQuery + queryWrappers[q].query.length;
|
||||
queryWrappers[q].found = true;
|
||||
// console.log(`current wrapper:`, queryWrappers[q]);
|
||||
// console.log(`\n`);
|
||||
}
|
||||
|
||||
let allFound = true;
|
||||
for (let t = 0; t < queryWrappers.length; t++) {
|
||||
if (queryWrappers[t].found === false) {allFound = false}
|
||||
}
|
||||
|
||||
if (allFound === true) {
|
||||
noteTemplatesFilePaths.push(notePath);
|
||||
// console.log(`All components have been found, breaking.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < noteTemplatesFilePaths.length; i++) {
|
||||
const currentTemplate = noteTemplatesFilePaths[i];
|
||||
const suggestion: Suggestion = {
|
||||
text: currentTemplate,
|
||||
type: "NoteTemplate",
|
||||
query: query.trim(),
|
||||
}
|
||||
suggestions.push(suggestion);
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
renderSuggestion(suggestion: Suggestion, el: HTMLElement) {
|
||||
if (suggestion.query === "") {
|
||||
el.createEl("div", {text: suggestion.text});
|
||||
}
|
||||
|
||||
function escapeRegExp(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
if (suggestion.query !== undefined && suggestion.query !== "") {
|
||||
const queryElements = suggestion.query.split(" ").filter((suggestion) => {return suggestion !== "";});
|
||||
|
||||
let startIndex = 0;
|
||||
const parentDiv = el.createEl("div");
|
||||
for (let i = 0; i < queryElements.length; i++) {
|
||||
const regex = new RegExp(escapeRegExp(String.raw`${queryElements[i]}`), "gi");
|
||||
let match;
|
||||
match = regex.exec(suggestion.text.slice(startIndex))
|
||||
if (match !== null) {
|
||||
parentDiv.createEl("span", {text: suggestion.text.slice(startIndex, match.index + startIndex)});
|
||||
parentDiv.createEl("span", {text: suggestion.text.slice(match.index + startIndex, queryElements[i].length + startIndex + match.index), cls: "suggestion-highlight"});
|
||||
startIndex += queryElements[i].length + match.index;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex !== suggestion.text.length) {
|
||||
parentDiv.createEl("span", {text: suggestion.text.slice(startIndex)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChooseSuggestion(suggestion: Suggestion, evt: MouseEvent | KeyboardEvent) {
|
||||
if (suggestion.type === "TitleTemplate") {
|
||||
const formatedNoteTitle = moment().format(suggestion.text)
|
||||
this.enterKeyHandler(evt, new BooleanWrapper(false), formatedNoteTitle)
|
||||
}
|
||||
|
||||
if (suggestion.type === "NoteTemplate") {
|
||||
const template: NoteTemplate = {path: suggestion.text};
|
||||
new NoteCreationModal(this.app, this.path, this.leafMode, this.settings, template).open();
|
||||
}
|
||||
}
|
||||
|
||||
async enterKeyHandler(event: KeyboardEvent | MouseEvent, previousModalJustClosed: BooleanWrapper, noteName?: string) {
|
||||
const isKeyboardEvent = (event: KeyboardEvent | MouseEvent): event is KeyboardEvent => "key" in event;
|
||||
|
||||
if(isKeyboardEvent(event)) {
|
||||
const {key} = event;
|
||||
if (key !== 'Enter') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (previousModalJustClosed.value) {
|
||||
previousModalJustClosed.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName === undefined) {
|
||||
noteName = this.inputEl.value;
|
||||
}
|
||||
|
||||
noteName = noteName.trim();
|
||||
|
||||
if (noteName.length == 0 && !this.settings.allowUntitledNotes) {
|
||||
new Notice('Add a title to the note' , 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.length == 0 && this.settings.allowUntitledNotes) {
|
||||
noteName = (this.settings.untitledNoteName.length == 0) ? DEFAULT_SETTINGS.untitledNoteName! : this.settings.untitledNoteName!;
|
||||
}
|
||||
|
||||
const windowsCompatibility: boolean = (
|
||||
Platform.isWin || this.settings.windowsNoteTitleCompatibility
|
||||
);
|
||||
|
||||
if (windowsCompatibility && noteName.match(/(<|>|:|"|\||\?|\*)/) !== null) {
|
||||
new Notice(`The note title must not include any of this characters: < > : " ? | *`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.match(/^[\\\/\s]+$/)) {
|
||||
new Notice(`The note title must not be only '\\' or '/'`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteName.match(/(\/|\\)/) !== null) {
|
||||
let noteNameWithDirs = normalizePath(noteName).split('/').map(link => link.trim()).filter(link => link.length !== 0);
|
||||
let parentDirs = `${this.path}/`;
|
||||
for (let i = 0; i < noteNameWithDirs.length - 1; i++) {
|
||||
noteNameWithDirs[i] += '/';
|
||||
parentDirs += noteNameWithDirs[i];
|
||||
}
|
||||
parentDirs = parentDirs.slice(0, -1);
|
||||
|
||||
if (this.app.vault.getFolderByPath(parentDirs) === null) {
|
||||
this.app.vault.createFolder(parentDirs);
|
||||
}
|
||||
noteName = noteNameWithDirs.join('');
|
||||
}
|
||||
|
||||
let noteExtension = this.settings.defaultNoteExtension;
|
||||
if (noteExtension.length == 0) {
|
||||
noteExtension = DEFAULT_SETTINGS.defaultNoteExtension!;
|
||||
}
|
||||
|
||||
if (windowsCompatibility && noteExtension.match(/(<|>|:|"|\\|\||\?|\/|\*|\/)/) !== null) {
|
||||
new Notice(`The note extension must not include any of this characters: < > : " \ ? | * /`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteExtension.match(/(\/)/) !== null) {
|
||||
new Notice(`The note extension must not include the character: /`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
const notePath = (this.path.length == 1) ? `${noteName}${noteExtension}` : `${this.path}/${noteName}${noteExtension}`;
|
||||
|
||||
const normalizedNotePath= normalizePath(notePath);
|
||||
|
||||
let fileAlreadyExists = false;
|
||||
const loadedFiles = this.app.vault.getFiles();
|
||||
for (let i = 0; i < loadedFiles.length; i++) {
|
||||
if (loadedFiles[i].path.toLowerCase() === normalizedNotePath.toLowerCase()) {
|
||||
fileAlreadyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileAlreadyExists) {
|
||||
new Notice(`That note already exists`, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
let noteContents = "";
|
||||
if (this.noteTemplate !== undefined) {
|
||||
const noteTemplatePath = this.noteTemplate.path;
|
||||
const templateFile = this.app.vault.getFileByPath(noteTemplatePath);
|
||||
|
||||
if (templateFile !== null) {
|
||||
noteContents = await this.app.vault.read(templateFile);
|
||||
|
||||
const timeFormat = this.settings.timeFormat;
|
||||
const dateFormat = this.settings.dateFormat;
|
||||
|
||||
const validTimeFormat = (timeFormat === undefined || timeFormat === "") ? "HH:mm" : timeFormat;
|
||||
const validDateFormat = (dateFormat === undefined || dateFormat === "") ? "YYYY-MM-DD" : dateFormat;
|
||||
|
||||
const formatedTime = moment().format(validTimeFormat);
|
||||
const formatedDate = moment().format(validDateFormat);
|
||||
|
||||
noteContents = noteContents.replace(/{{time}}/g, formatedTime);
|
||||
noteContents = noteContents.replace(/{{date}}/g, formatedDate);
|
||||
|
||||
const overrideDateRegex = new RegExp(String.raw`{{date:(.+)}}`, "g");
|
||||
const overrideTimeRegex = new RegExp(String.raw`{{time:(.+)}}`, "g");
|
||||
|
||||
const matchesDate = noteContents.matchAll(overrideDateRegex);
|
||||
const matchesTime = noteContents.matchAll(overrideTimeRegex);
|
||||
|
||||
if (matchesDate !== null) {
|
||||
const matchesArray = [...matchesDate];
|
||||
for (let i = 0; i < matchesArray.length; i++) {
|
||||
const override = matchesArray[i][0];
|
||||
const format = matchesArray[i][1];
|
||||
|
||||
noteContents = noteContents.replace(override, moment().format(format));
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesTime !== null) {
|
||||
const matchesArray = [...matchesTime];
|
||||
for (let i = 0; i < matchesArray.length; i++) {
|
||||
const override = matchesArray[i][0];
|
||||
const format = matchesArray[i][1];
|
||||
|
||||
noteContents = noteContents.replace(override, moment().format(format));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newNote = await this.app.vault.create(`${normalizedNotePath}`, noteContents);
|
||||
|
||||
if (newNote == null) {
|
||||
new Notice('Error opening the file, report issue to GitHub', 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newNote.extension !== 'md') {
|
||||
new Notice(`Created note ${noteName}${noteExtension}, opening it on the system's default application if there is one`, 4000);
|
||||
}
|
||||
|
||||
OpenNote.bind(this)(newNote);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import FuzzyNoteCreatorPlugin from './main';
|
||||
import { App, PluginSettingTab, Setting, Platform, sanitizeHTMLToDom } from 'obsidian';
|
||||
import { App, PluginSettingTab, Setting, Platform, moment } from 'obsidian';
|
||||
|
||||
export interface FuzzyNoteCreatorSettings {
|
||||
showInstructions: boolean;
|
||||
|
|
@ -10,7 +10,9 @@ export interface FuzzyNoteCreatorSettings {
|
|||
useNoteTitleTemplates: boolean;
|
||||
noteTitleTemplates: string;
|
||||
useNoteTemplates: boolean;
|
||||
noteTemplates: string;
|
||||
noteTemplatesFolder: string;
|
||||
dateFormat: string;
|
||||
timeFormat: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: Partial<FuzzyNoteCreatorSettings> = {
|
||||
|
|
@ -25,10 +27,12 @@ export const DEFAULT_SETTINGS: Partial<FuzzyNoteCreatorSettings> = {
|
|||
|
||||
export class FuzzyNoteCreatorSettingTab extends PluginSettingTab {
|
||||
plugin: FuzzyNoteCreatorPlugin
|
||||
app: App
|
||||
|
||||
constructor(app: App, plugin: FuzzyNoteCreatorPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
|
|
@ -109,6 +113,181 @@ export class FuzzyNoteCreatorSettingTab extends PluginSettingTab {
|
|||
})
|
||||
});
|
||||
|
||||
// ╭─────────────────────────────────────────────────────────╮
|
||||
// │ Note Templates │
|
||||
// ╰─────────────────────────────────────────────────────────╯
|
||||
|
||||
containerEl.createEl('h6', {text: 'Note templates'});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use note templates')
|
||||
.setDesc('Whether to use templates that define the contents of the new note.')
|
||||
.addToggle((slider) => {
|
||||
slider
|
||||
.setValue(this.plugin.settings.useNoteTemplates)
|
||||
.onChange(async (value: boolean) => {
|
||||
this.plugin.settings.useNoteTemplates = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Template Folder Location ────────────────────────────────────────
|
||||
|
||||
const folderLocationSetting = new Setting(containerEl)
|
||||
.setName("Template folder location")
|
||||
.setDesc("Files in this folder will be available as templates.");
|
||||
|
||||
const suggestionDiv = folderLocationSetting.settingEl.querySelector(".setting-item-control")!.createEl("div", { cls: "suggestion" });
|
||||
const folderInput = suggestionDiv.createEl("input", { attr: { id: "textbox-folder", type: "text" }});
|
||||
const templateFolderPath = this.plugin.settings.noteTemplatesFolder;
|
||||
const verticalTabContent = document.getElementsByClassName("vertical-tab-content-container")[0];
|
||||
const fuzzyListenerAttr = verticalTabContent.getAttr("fuzzy-creator-listener");
|
||||
|
||||
folderInput.placeholder = "Template Folder"
|
||||
|
||||
if (templateFolderPath === undefined || templateFolderPath === "") {
|
||||
folderInput.value = "";
|
||||
} else {
|
||||
folderInput.value = templateFolderPath;
|
||||
}
|
||||
|
||||
if (fuzzyListenerAttr == null) {
|
||||
verticalTabContent.addEventListener("click", clickHandler);
|
||||
verticalTabContent.setAttr("fuzzy-creator-listener", "true");
|
||||
}
|
||||
|
||||
folderInput.addEventListener("input", () => createSuggestions(folderInput, this.plugin, this.app));
|
||||
folderInput.addEventListener("focus", () => createSuggestions(folderInput, this.plugin, this.app));
|
||||
|
||||
// ── Functions & Callbacks ───────────────────────────────────────────
|
||||
|
||||
function closeAllDropdowns() {
|
||||
const dropdownMenu = document.getElementById("textbox-folder-dropdown-menu");
|
||||
if (dropdownMenu == null) {
|
||||
return;
|
||||
}
|
||||
dropdownMenu.detach();
|
||||
}
|
||||
|
||||
function createSuggestions(folderInput: HTMLInputElement, plugin: FuzzyNoteCreatorPlugin, app: App) {
|
||||
let dirs: string[] = [];
|
||||
|
||||
const obsidianFolders = app.vault.getAllFolders();
|
||||
for (let i = 0; i < obsidianFolders.length; i++) {
|
||||
dirs.push(obsidianFolders[i].path);
|
||||
}
|
||||
|
||||
closeAllDropdowns()
|
||||
|
||||
const value = folderInput.value;
|
||||
const newDiv = createEl("div", { cls: "dropdown-menu", attr: { id: `${folderInput.id}-dropdown-menu` }});
|
||||
if (folderInput.parentNode !== null) {
|
||||
folderInput.parentNode.appendChild(newDiv)
|
||||
}
|
||||
|
||||
const lengthOfSuggestions = (dirs.length < 5) ? dirs.length : 5;
|
||||
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
if (dirs[i].toLowerCase().contains(value.toLowerCase())) {
|
||||
const listElement = createEl("div", { text: `${dirs[i]}`, cls: "dropdown-item" });
|
||||
listElement.addEventListener("click", function() {
|
||||
if (listElement.textContent !== null) {
|
||||
folderInput.value = listElement.textContent;
|
||||
|
||||
plugin.settings.noteTemplatesFolder = folderInput.value;
|
||||
plugin.saveSettings();
|
||||
}
|
||||
closeAllDropdowns();
|
||||
});
|
||||
newDiv.appendChild(listElement);
|
||||
if (newDiv.childNodes.length === lengthOfSuggestions) {break;}
|
||||
}
|
||||
}
|
||||
if (newDiv.childNodes.length === 0) {newDiv.hide();}
|
||||
}
|
||||
|
||||
function clickHandler(event: UIEvent) {
|
||||
const dropdown = document.getElementById("textbox-folder-dropdown-menu");
|
||||
const textbox = document.getElementById("textbox-folder");
|
||||
let clickedOnChild: boolean = false;
|
||||
|
||||
if (dropdown === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < dropdown.childNodes.length; i++) {
|
||||
if (dropdown.childNodes[i] === event.target) {
|
||||
clickedOnChild = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clickedOnChild && event.target !== textbox) {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
const userDate = (this.plugin.settings.dateFormat !== undefined && this.plugin.settings.dateFormat !== "") ? moment().format(this.plugin.settings.dateFormat) : moment().format('YYYY-MM-DD');
|
||||
const descriptionDivDate = createEl("div", {text: `Inside your template you can put '{{date}}' which when the note is created its gonna be replaced with the value that you define on this setting. With your current settings {{date}} will be replaced with `});
|
||||
descriptionDivDate.createEl("b", {text: userDate, cls: "u-pop", attr: {id: "userDate"}});
|
||||
descriptionDivDate.createEl("span", {text: "."});
|
||||
descriptionDivDate.createEl("br");
|
||||
descriptionDivDate.createEl("br");
|
||||
descriptionDivDate.createEl("span", {text: "You can also use {{date:YYYY-MM-DD}} to override the format."});
|
||||
|
||||
const descriptionDate = new DocumentFragment();
|
||||
descriptionDate.append(descriptionDivDate)
|
||||
|
||||
const dateFormatSetting = new Setting(containerEl)
|
||||
dateFormatSetting
|
||||
.setName('Date format')
|
||||
.setDesc(descriptionDate)
|
||||
.addMomentFormat((momentComponent) => {
|
||||
momentComponent
|
||||
.setValue(this.plugin.settings.dateFormat)
|
||||
.setDefaultFormat('YYYY-MM-DD')
|
||||
.onChange(async (value) => {
|
||||
const trimmedValue = value.trim();
|
||||
this.plugin.settings.dateFormat = trimmedValue;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
const userDate = (trimmedValue !== "") ? moment().format(trimmedValue) : moment().format("YYYY-MM-DD");
|
||||
document.getElementById("userDate")!.setText(userDate);
|
||||
});
|
||||
});
|
||||
|
||||
const userTime = (this.plugin.settings.timeFormat !== undefined && this.plugin.settings.timeFormat !== "") ? moment().format(this.plugin.settings.timeFormat) : moment().format('HH:mm');
|
||||
const descriptionDivTime = createEl("div", {text: `Inside your template you can put '{{time}}' which when the note is created its gonna be replaced with the value that you define on this setting. With your current settings {{time}} will be replaced with `});
|
||||
descriptionDivTime.createEl("b", {text: userTime, cls: "u-pop", attr: {id: "userTime"}});
|
||||
descriptionDivTime.createEl("span", {text: "."});
|
||||
descriptionDivTime.createEl("br");
|
||||
descriptionDivTime.createEl("br");
|
||||
descriptionDivTime.createEl("span", {text: "You can also use {{time:YYYY-MM-DD}} to override the format."});
|
||||
|
||||
const descriptionTime = new DocumentFragment();
|
||||
descriptionTime.append(descriptionDivTime)
|
||||
|
||||
const timeFormatSetting = new Setting(containerEl)
|
||||
timeFormatSetting
|
||||
.setName('Time format')
|
||||
.setDesc(descriptionTime)
|
||||
.addMomentFormat((momentComponent) => {
|
||||
momentComponent
|
||||
.setValue(this.plugin.settings.timeFormat)
|
||||
.setDefaultFormat('HH:mm')
|
||||
.onChange(async (value) => {
|
||||
const trimmedValue = value.trim();
|
||||
this.plugin.settings.timeFormat = trimmedValue;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
const userTime = (trimmedValue !== "") ? moment().format(trimmedValue) : moment().format("HH:mm");
|
||||
document.getElementById("userTime")!.setText(userTime);
|
||||
});
|
||||
});
|
||||
|
||||
// ╭─────────────────────────────────────────────────────────╮
|
||||
// │ Note Title Templates │
|
||||
// ╰─────────────────────────────────────────────────────────╯
|
||||
|
||||
containerEl.createEl('h6', {text: 'Note title templates'});
|
||||
|
||||
new Setting(containerEl)
|
||||
|
|
@ -123,12 +302,18 @@ export class FuzzyNoteCreatorSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
const exampleDate = moment().format('YYYY-MM-DD');
|
||||
|
||||
const templatesFragment = new DocumentFragment();
|
||||
const templatesDescription = containerEl.createEl('div', {text: 'If you have note title templates turned on, you will see them when you are inputing your note title, dates displayed like so: YYYY-MM-DD will be replaced for the date.'});
|
||||
const templatesLink = containerEl.createEl('a', {text: 'Reference for formatting', href: 'https://momentjs.com/docs/#/displaying/format/'});
|
||||
const templatesDescription = containerEl.createEl('div', {text: 'If you have note title templates turned on, you will see them when you are inputing your note title. For example, when you create a note with the template: '});
|
||||
templatesDescription.createEl('b', {text: 'YYYY-MM-DD', cls: 'u-pop'});
|
||||
templatesDescription.createEl('span', {text: ' it will be replaced for '});
|
||||
templatesDescription.createEl('b', {text: `${exampleDate}`, cls: 'u-pop'});
|
||||
templatesDescription.createEl('span', {text: ', this according to the formatting that you can see '});
|
||||
templatesDescription.createEl('a', {text: 'here', href: 'https://momentjs.com/docs/#/displaying/format/'});
|
||||
templatesDescription.createEl('span', {text: '.'});
|
||||
|
||||
templatesFragment.append(templatesDescription);
|
||||
templatesFragment.append(templatesLink);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Note title templates')
|
||||
|
|
@ -142,29 +327,9 @@ export class FuzzyNoteCreatorSettingTab extends PluginSettingTab {
|
|||
|
||||
this.plugin.settings.noteTitleTemplates = trimmedValue;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
|
||||
const noteTemplatesFragment = new DocumentFragment();
|
||||
const noteTemplatesDescription = containerEl.createEl('div', {text: 'Works like the templates native Obsidian plugin.'});
|
||||
const noteTemplatesLink = containerEl.createEl('a', {text: 'Reference for formatting', href: 'https://momentjs.com/docs/#/displaying/format/'});
|
||||
|
||||
noteTemplatesFragment.append(noteTemplatesDescription);
|
||||
noteTemplatesFragment.append(noteTemplatesLink);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Note title noteTemplates')
|
||||
.setDesc(noteTemplatesFragment)
|
||||
.addTextArea((text) => {
|
||||
text
|
||||
.setPlaceholder('YYYY-MM-DD')
|
||||
.setValue(this.plugin.settings.noteTitlenoteTemplates)
|
||||
.onChange(async (value) => {
|
||||
const trimmedValue = value.trim();
|
||||
|
||||
this.plugin.settings.noteTitlenoteTemplates = trimmedValue;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
text.inputEl.setCssStyles({resize: "none"})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
0
source/template.ts
Normal file
0
source/template.ts
Normal file
47
styles.css
Normal file
47
styles.css
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
.suggestion {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.input-search {
|
||||
/* border: 1px solid #FFFFFF; */
|
||||
position: relative;
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
/*position the autocomplete items to be the same width as the container:*/
|
||||
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
padding: var(--size-2-3);
|
||||
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.dropdown-menu div {
|
||||
border-radius: var(--radius-s);
|
||||
padding: var(--size-2-3) var(--size-4-3);
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
|
||||
.dropdown-menu div:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.autocomplete-active {
|
||||
/*when navigating through the items using the arrow keys:*/
|
||||
/* background-color: DodgerBlue !important; */
|
||||
/* color: #ffffff; */
|
||||
}
|
||||
Loading…
Reference in a new issue