From 69a67bbde47d7f392e37addf09b4daa4aee06bdf Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Wed, 28 Dec 2022 18:59:49 +0100 Subject: [PATCH 1/7] Replace folder templates with file regex --- .gitignore | 115 ++++++++++---------- src/core/Templater.ts | 23 ++-- src/settings/Settings.ts | 225 +++++++++++++++++++-------------------- 3 files changed, 178 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index e7dc1d72..a21b995d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,57 +1,58 @@ -# Editors - -## Intellij -*.iml -.idea - -## VSCode -.vscode/ - -# npm -node_modules - -# build -main.js -main.test.js -*.js.map - -# test -test/ -tests_jest/ - -# Obsidian -data.json -.hotreload -resources/ - -# Other -RESOURCES.md - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +# Editors + +## Intellij +*.iml +.idea + +## VSCode +.vscode/ + +# npm +node_modules +package-lock.json + +# build +main.js +main.test.js +*.js.map + +# test +test/ +tests_jest/ + +# Obsidian +data.json +.hotreload +resources/ + +# Other +RESOURCES.md + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/src/core/Templater.ts b/src/core/Templater.ts index 34e8881b..55b65e7d 100644 --- a/src/core/Templater.ts +++ b/src/core/Templater.ts @@ -364,18 +364,15 @@ export class Templater { } } - get_new_file_template_for_folder(folder: TFolder): string | undefined { - do { - const match = this.plugin.settings.folder_templates.find( - (e) => e.folder == folder.path - ); - - if (match && match.template) { - return match.template; - } + get_new_file_template_for_file(file: TFile): string | undefined { + const match = this.plugin.settings.file_templates.find((e) => { + const eRegex = new RegExp(e.regex); + return eRegex.test(file.path); + }); - folder = folder.parent; - } while (folder); + if (match && match.template) { + return match.template; + } } static async on_file_creation( @@ -401,10 +398,10 @@ export class Templater { if ( file.stat.size == 0 && - templater.plugin.settings.enable_folder_templates + templater.plugin.settings.enable_file_templates ) { const folder_template_match = - templater.get_new_file_template_for_folder(file.parent); + templater.get_new_file_template_for_file(file); if (!folder_template_match) { return; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 826d1e86..844f421b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -8,7 +8,7 @@ import { log_error } from "utils/Log"; import { HEART, PAYPAL } from "utils/Constants"; export interface FolderTemplate { - folder: string; + regex: string; template: string; } @@ -21,8 +21,8 @@ export const DEFAULT_SETTINGS: Settings = { enable_system_commands: false, shell_path: "", user_scripts_folder: "", - enable_folder_templates: true, - folder_templates: [{ folder: "", template: "" }], + enable_file_templates: true, + file_templates: [{ regex: "", template: "" }], syntax_highlighting: true, syntax_highlighting_mobile: false, enabled_templates_hotkeys: [""], @@ -39,8 +39,8 @@ export interface Settings { enable_system_commands: boolean; shell_path: string; user_scripts_folder: string; - enable_folder_templates: boolean; - folder_templates: Array; + enable_file_templates: boolean; + file_templates: Array; syntax_highlighting: boolean; syntax_highlighting_mobile: boolean; enabled_templates_hotkeys: Array; @@ -121,8 +121,8 @@ export class TemplaterSettingTab extends PluginSettingTab { const mobileDesc = document.createDocumentFragment(); mobileDesc.append( "Adds syntax highlighting for Templater commands in edit mode on " + - "mobile. Use with caution: this may break live preview on mobile " + - "platforms." + "mobile. Use with caution: this may break live preview on mobile " + + "platforms." ); new Setting(this.containerEl) @@ -368,36 +368,45 @@ export class TemplaterSettingTab extends PluginSettingTab { } add_folder_templates_setting(): void { - this.containerEl.createEl("h2", { text: "Folder Templates" }); + this.containerEl.createEl("h2", { text: "File Template regex" }); const descHeading = document.createDocumentFragment(); descHeading.append( - "Folder Templates are triggered when a new ", + "File Template Regexes are triggered when a new ", descHeading.createEl("strong", { text: "empty " }), - "file is created in a given folder.", + "file is created that matches one of them.", descHeading.createEl("br"), "Templater will fill the empty file with the specified template.", descHeading.createEl("br"), - "The deepest match is used. A global default template would be defined on the root ", - descHeading.createEl("code", { text: "/" }), - "." + "The first match from the top is used.", + descHeading.createEl("br"), + 'Use "', + descHeading.createEl("code", { text: ".*" }), + '" as a final catch-all, if you need it.', + descHeading.createEl("br"), + "Use ", + descHeading.createEl("a", { + href: "https://regex101.com/", + text: "Regex101", + }), + " to test your regexes." ); new Setting(this.containerEl).setDesc(descHeading); const descUseNewFileTemplate = document.createDocumentFragment(); descUseNewFileTemplate.append( - "When enabled Templater will make use of the folder templates defined below." + "When enabled Templater will make use of the file template regexes defined below." ); new Setting(this.containerEl) - .setName("Enable Folder Templates") + .setName("Enable File Template Regex") .setDesc(descUseNewFileTemplate) .addToggle((toggle) => { toggle - .setValue(this.plugin.settings.enable_folder_templates) + .setValue(this.plugin.settings.enable_file_templates) .onChange((use_new_file_templates) => { - this.plugin.settings.enable_folder_templates = + this.plugin.settings.enable_file_templates = use_new_file_templates; this.plugin.save_settings(); // Force refresh @@ -405,21 +414,21 @@ export class TemplaterSettingTab extends PluginSettingTab { }); }); - if (!this.plugin.settings.enable_folder_templates) { + if (!this.plugin.settings.enable_file_templates) { return; } new Setting(this.containerEl) .setName("Add New") - .setDesc("Add new folder template") + .setDesc("Add new file template regex") .addButton((button: ButtonComponent) => { button - .setTooltip("Add additional folder template") + .setTooltip("Add additional file template regex") .setButtonText("+") .setCta() .onClick(() => { - this.plugin.settings.folder_templates.push({ - folder: "", + this.plugin.settings.file_templates.push({ + regex: "", template: "", }); this.plugin.save_settings(); @@ -427,94 +436,76 @@ export class TemplaterSettingTab extends PluginSettingTab { }); }); - this.plugin.settings.folder_templates.forEach( - (folder_template, index) => { - const s = new Setting(this.containerEl) - .addSearch((cb) => { - new FolderSuggest(cb.inputEl); - cb.setPlaceholder("Folder") - .setValue(folder_template.folder) - .onChange((new_folder) => { - if ( - new_folder && - this.plugin.settings.folder_templates.some( - (e) => e.folder == new_folder - ) - ) { - log_error( - new TemplaterError( - "This folder already has a template associated with it" - ) - ); - return; - } - - this.plugin.settings.folder_templates[ - index - ].folder = new_folder; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addSearch((cb) => { - new FileSuggest( - cb.inputEl, - this.plugin, - FileSuggestMode.TemplateFiles - ); - cb.setPlaceholder("Template") - .setValue(folder_template.template) - .onChange((new_template) => { - this.plugin.settings.folder_templates[ - index - ].template = new_template; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addExtraButton((cb) => { - cb.setIcon("up-chevron-glyph") - .setTooltip("Move up") - .onClick(() => { - arraymove( - this.plugin.settings.folder_templates, - index, - index - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("down-chevron-glyph") - .setTooltip("Move down") - .onClick(() => { - arraymove( - this.plugin.settings.folder_templates, - index, - index + 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - this.plugin.settings.folder_templates.splice( - index, - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }); - s.infoEl.remove(); - } - ); + this.plugin.settings.file_templates.forEach((file_template, index) => { + const s = new Setting(this.containerEl) + .addText((cb) => { + cb.setPlaceholder("File Regex") + .setValue(file_template.regex) + .onChange((new_regex) => { + this.plugin.settings.file_templates[index].regex = + new_regex; + this.plugin.save_settings(); + }); + // @ts-ignore + cb.inputEl.addClass("templater_search"); + }) + .addSearch((cb) => { + new FileSuggest( + cb.inputEl, + this.plugin, + FileSuggestMode.TemplateFiles + ); + cb.setPlaceholder("Template") + .setValue(file_template.template) + .onChange((new_template) => { + this.plugin.settings.file_templates[ + index + ].template = new_template; + this.plugin.save_settings(); + }); + // @ts-ignore + cb.containerEl.addClass("templater_search"); + }) + .addExtraButton((cb) => { + cb.setIcon("up-chevron-glyph") + .setTooltip("Move up") + .onClick(() => { + arraymove( + this.plugin.settings.file_templates, + index, + index - 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }) + .addExtraButton((cb) => { + cb.setIcon("down-chevron-glyph") + .setTooltip("Move down") + .onClick(() => { + arraymove( + this.plugin.settings.file_templates, + index, + index + 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }) + .addExtraButton((cb) => { + cb.setIcon("cross") + .setTooltip("Delete") + .onClick(() => { + this.plugin.settings.file_templates.splice( + index, + 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }); + s.infoEl.remove(); + }); } add_startup_templates_setting(): void { @@ -849,21 +840,25 @@ export class TemplaterSettingTab extends PluginSettingTab { .setName("Donate") .setDesc( "If you like this Plugin, consider donating to support continued development." - ) - + ); const a1 = document.createElement("a"); a1.setAttribute("href", "https://github.com/sponsors/silentvoid13"); a1.addClass("templater_donating"); const img1 = document.createElement("img"); - img1.src = "https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86"; + img1.src = + "https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86"; a1.appendChild(img1); const a2 = document.createElement("a"); - a2.setAttribute("href", "https://www.paypal.com/donate?hosted_button_id=U2SRGAFYXT32Q"); + a2.setAttribute( + "href", + "https://www.paypal.com/donate?hosted_button_id=U2SRGAFYXT32Q" + ); a2.addClass("templater_donating"); const img2 = document.createElement("img"); - img2.src = "https://img.shields.io/badge/paypal-silentvoid13-yellow?style=social&logo=paypal"; + img2.src = + "https://img.shields.io/badge/paypal-silentvoid13-yellow?style=social&logo=paypal"; a2.appendChild(img2); s.settingEl.appendChild(a1); From e5d464e00fa7783436ed3194e94e70e416a50fc3 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Thu, 29 Dec 2022 09:33:11 +0100 Subject: [PATCH 2/7] Re-add Folder Templates option --- .gitignore | 3 +- .vscode/settings.json | 3 + src/core/Templater.ts | 39 ++++++++- src/settings/Settings.ts | 175 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index a21b995d..9c91ad60 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ .idea ## VSCode -.vscode/ +.vscode/* +!.vscode/settings.json # npm node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..db5aa504 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", +} diff --git a/src/core/Templater.ts b/src/core/Templater.ts index 55b65e7d..4eca4517 100644 --- a/src/core/Templater.ts +++ b/src/core/Templater.ts @@ -364,6 +364,20 @@ export class Templater { } } + get_new_file_template_for_folder(folder: TFolder): string | undefined { + do { + const match = this.plugin.settings.folder_templates.find( + (e) => e.folder == folder.path + ); + + if (match && match.template) { + return match.template; + } + + folder = folder.parent; + } while (folder); + } + get_new_file_template_for_file(file: TFile): string | undefined { const match = this.plugin.settings.file_templates.find((e) => { const eRegex = new RegExp(e.regex); @@ -395,13 +409,12 @@ export class Templater { // Currently, I have to wait for the daily note plugin to add the file content before replacing // Not a problem with Calendar however since it creates the file with the existing content await delay(300); - if ( file.stat.size == 0 && - templater.plugin.settings.enable_file_templates + templater.plugin.settings.enable_folder_templates ) { const folder_template_match = - templater.get_new_file_template_for_file(file); + templater.get_new_file_template_for_folder(file.parent); if (!folder_template_match) { return; } @@ -416,6 +429,26 @@ export class Templater { return; } await templater.write_template_to_file(template_file, file); + } else if ( + file.stat.size == 0 && + templater.plugin.settings.enable_file_templates + ) { + const file_template_match = + templater.get_new_file_template_for_file(file); + if (!file_template_match) { + return; + } + const template_file: TFile = await errorWrapper( + async (): Promise => { + return resolve_tfile(file_template_match); + }, + `Couldn't find template ${file_template_match}` + ); + // errorWrapper failed + if (template_file == null) { + return; + } + await templater.write_template_to_file(template_file, file); } else { if (file.stat.size <= 100000) { //https://github.com/SilentVoid13/Templater/issues/873 diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 844f421b..64dcbc93 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -8,6 +8,11 @@ import { log_error } from "utils/Log"; import { HEART, PAYPAL } from "utils/Constants"; export interface FolderTemplate { + folder: string; + template: string; +} + +export interface FileTemplate { regex: string; template: string; } @@ -21,7 +26,9 @@ export const DEFAULT_SETTINGS: Settings = { enable_system_commands: false, shell_path: "", user_scripts_folder: "", - enable_file_templates: true, + enable_folder_templates: true, + folder_templates: [{ folder: "", template: "" }], + enable_file_templates: false, file_templates: [{ regex: "", template: "" }], syntax_highlighting: true, syntax_highlighting_mobile: false, @@ -39,8 +46,10 @@ export interface Settings { enable_system_commands: boolean; shell_path: string; user_scripts_folder: string; + enable_folder_templates: boolean; + folder_templates: Array; enable_file_templates: boolean; - file_templates: Array; + file_templates: Array; syntax_highlighting: boolean; syntax_highlighting_mobile: boolean; enabled_templates_hotkeys: Array; @@ -66,6 +75,7 @@ export class TemplaterSettingTab extends PluginSettingTab { this.add_templates_hotkeys_setting(); if (this.plugin.settings.trigger_on_file_creation) { this.add_folder_templates_setting(); + this.add_file_templates_setting(); } this.add_startup_templates_setting(); this.add_user_script_functions_setting(); @@ -368,7 +378,160 @@ export class TemplaterSettingTab extends PluginSettingTab { } add_folder_templates_setting(): void { - this.containerEl.createEl("h2", { text: "File Template regex" }); + this.containerEl.createEl("h2", { text: "Folder Templates" }); + + const descHeading = document.createDocumentFragment(); + descHeading.append( + "Folder Templates are triggered when a new ", + descHeading.createEl("strong", { text: "empty " }), + "file is created in a given folder.", + descHeading.createEl("br"), + "Templater will fill the empty file with the specified template.", + descHeading.createEl("br"), + "The deepest match is used. A global default template would be defined on the root ", + descHeading.createEl("code", { text: "/" }), + "." + ); + + new Setting(this.containerEl).setDesc(descHeading); + + const descUseNewFileTemplate = document.createDocumentFragment(); + descUseNewFileTemplate.append( + "When enabled Templater will make use of the folder templates defined below. This option is mutually exclusive with File Template Regex below, so enabling one will disable the other." + ); + + new Setting(this.containerEl) + .setName("Enable Folder Templates") + .setDesc(descUseNewFileTemplate) + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.enable_folder_templates) + .onChange((use_new_folder_templates) => { + this.plugin.settings.enable_folder_templates = + use_new_folder_templates; + if (use_new_folder_templates) { + this.plugin.settings.enable_file_templates = false; + } + this.plugin.save_settings(); + // Force refresh + this.display(); + }); + }); + + if (!this.plugin.settings.enable_folder_templates) { + return; + } + + new Setting(this.containerEl) + .setName("Add New") + .setDesc("Add new folder template") + .addButton((button: ButtonComponent) => { + button + .setTooltip("Add additional folder template") + .setButtonText("+") + .setCta() + .onClick(() => { + this.plugin.settings.folder_templates.push({ + folder: "", + template: "", + }); + this.plugin.save_settings(); + this.display(); + }); + }); + + this.plugin.settings.folder_templates.forEach( + (folder_template, index) => { + const s = new Setting(this.containerEl) + .addSearch((cb) => { + new FolderSuggest(cb.inputEl); + cb.setPlaceholder("Folder") + .setValue(folder_template.folder) + .onChange((new_folder) => { + if ( + new_folder && + this.plugin.settings.folder_templates.some( + (e) => e.folder == new_folder + ) + ) { + log_error( + new TemplaterError( + "This folder already has a template associated with it" + ) + ); + return; + } + + this.plugin.settings.folder_templates[ + index + ].folder = new_folder; + this.plugin.save_settings(); + }); + // @ts-ignore + cb.containerEl.addClass("templater_search"); + }) + .addSearch((cb) => { + new FileSuggest( + cb.inputEl, + this.plugin, + FileSuggestMode.TemplateFiles + ); + cb.setPlaceholder("Template") + .setValue(folder_template.template) + .onChange((new_template) => { + this.plugin.settings.folder_templates[ + index + ].template = new_template; + this.plugin.save_settings(); + }); + // @ts-ignore + cb.containerEl.addClass("templater_search"); + }) + .addExtraButton((cb) => { + cb.setIcon("up-chevron-glyph") + .setTooltip("Move up") + .onClick(() => { + arraymove( + this.plugin.settings.folder_templates, + index, + index - 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }) + .addExtraButton((cb) => { + cb.setIcon("down-chevron-glyph") + .setTooltip("Move down") + .onClick(() => { + arraymove( + this.plugin.settings.folder_templates, + index, + index + 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }) + .addExtraButton((cb) => { + cb.setIcon("cross") + .setTooltip("Delete") + .onClick(() => { + this.plugin.settings.folder_templates.splice( + index, + 1 + ); + this.plugin.save_settings(); + this.display(); + }); + }); + s.infoEl.remove(); + } + ); + } + + add_file_templates_setting(): void { + this.containerEl.createEl("h2", { text: "File Template Regex" }); const descHeading = document.createDocumentFragment(); descHeading.append( @@ -396,7 +559,7 @@ export class TemplaterSettingTab extends PluginSettingTab { const descUseNewFileTemplate = document.createDocumentFragment(); descUseNewFileTemplate.append( - "When enabled Templater will make use of the file template regexes defined below." + "When enabled Templater will make use of the file template regexes defined below. This option is mutually exclusive with Folder Templates above, so enabling one will disable the other." ); new Setting(this.containerEl) @@ -408,6 +571,10 @@ export class TemplaterSettingTab extends PluginSettingTab { .onChange((use_new_file_templates) => { this.plugin.settings.enable_file_templates = use_new_file_templates; + if (use_new_file_templates) { + this.plugin.settings.enable_folder_templates = + false; + } this.plugin.save_settings(); // Force refresh this.display(); From 7a5ab057f017f8392158c9c7800508036b1efeb9 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Thu, 29 Dec 2022 10:44:37 +0100 Subject: [PATCH 3/7] Tweak wording in settings window --- src/settings/Settings.ts | 60 +++++++++++++++------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 64dcbc93..7fdb9a8e 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -27,9 +27,9 @@ export const DEFAULT_SETTINGS: Settings = { shell_path: "", user_scripts_folder: "", enable_folder_templates: true, - folder_templates: [{ folder: "", template: "" }], + folder_templates: [{ folder: "/", template: "" }], enable_file_templates: false, - file_templates: [{ regex: "", template: "" }], + file_templates: [{ regex: ".*", template: "" }], syntax_highlighting: true, syntax_highlighting_mobile: false, enabled_templates_hotkeys: [""], @@ -193,9 +193,12 @@ export class TemplaterSettingTab extends PluginSettingTab { add_trigger_on_new_file_creation_setting(): void { const desc = document.createDocumentFragment(); desc.append( - "Templater will listen for the new file creation event, and replace every command it finds in the new file's content.", + "Templater will listen for the new file creation event, and, if it matches a rule you've set, replace every command it finds in the new file's content. ", + "This makes Templater compatible with other plugins like the Daily note core plugin, Calendar plugin, Review plugin, Note refactor plugin, etc. ", + desc.createEl("br"), + desc.createEl("br"), + "Make sure to set up rules under either Folder Templates or File Regex Template below.", desc.createEl("br"), - "This makes Templater compatible with other plugins like the Daily note core plugin, Calendar plugin, Review plugin, Note refactor plugin, ...", desc.createEl("br"), desc.createEl("b", { text: "Warning: ", @@ -383,21 +386,15 @@ export class TemplaterSettingTab extends PluginSettingTab { const descHeading = document.createDocumentFragment(); descHeading.append( "Folder Templates are triggered when a new ", - descHeading.createEl("strong", { text: "empty " }), - "file is created in a given folder.", - descHeading.createEl("br"), - "Templater will fill the empty file with the specified template.", - descHeading.createEl("br"), - "The deepest match is used. A global default template would be defined on the root ", - descHeading.createEl("code", { text: "/" }), - "." + descHeading.createEl("strong", { text: "empty" }), + " file is created in a given folder. Templater will fill the empty file with the specified template." ); new Setting(this.containerEl).setDesc(descHeading); const descUseNewFileTemplate = document.createDocumentFragment(); descUseNewFileTemplate.append( - "When enabled Templater will make use of the folder templates defined below. This option is mutually exclusive with File Template Regex below, so enabling one will disable the other." + "When enabled, Templater will make use of the folder templates defined below. This option is mutually exclusive with File Regex Templates below, so enabling one will disable the other." ); new Setting(this.containerEl) @@ -424,7 +421,9 @@ export class TemplaterSettingTab extends PluginSettingTab { new Setting(this.containerEl) .setName("Add New") - .setDesc("Add new folder template") + .setDesc( + "Add new folder template. The deepest match is used, so the order of the rules here is irrelevant. A global default template would be defined on root (`/`)." + ) .addButton((button: ButtonComponent) => { button .setTooltip("Add additional folder template") @@ -531,39 +530,24 @@ export class TemplaterSettingTab extends PluginSettingTab { } add_file_templates_setting(): void { - this.containerEl.createEl("h2", { text: "File Template Regex" }); + this.containerEl.createEl("h2", { text: "File Regex Templates" }); const descHeading = document.createDocumentFragment(); descHeading.append( - "File Template Regexes are triggered when a new ", - descHeading.createEl("strong", { text: "empty " }), - "file is created that matches one of them.", - descHeading.createEl("br"), - "Templater will fill the empty file with the specified template.", - descHeading.createEl("br"), - "The first match from the top is used.", - descHeading.createEl("br"), - 'Use "', - descHeading.createEl("code", { text: ".*" }), - '" as a final catch-all, if you need it.', - descHeading.createEl("br"), - "Use ", - descHeading.createEl("a", { - href: "https://regex101.com/", - text: "Regex101", - }), - " to test your regexes." + "File Regex Templates are triggered when a new ", + descHeading.createEl("strong", { text: "empty" }), + " file is created that matches one of them. Templater will fill the empty file with the specified template." ); new Setting(this.containerEl).setDesc(descHeading); const descUseNewFileTemplate = document.createDocumentFragment(); descUseNewFileTemplate.append( - "When enabled Templater will make use of the file template regexes defined below. This option is mutually exclusive with Folder Templates above, so enabling one will disable the other." + "When enabled, Templater will make use of the file regex templates defined below. This option is mutually exclusive with Folder Templates above, so enabling one will disable the other." ); new Setting(this.containerEl) - .setName("Enable File Template Regex") + .setName("Enable File Regex Templates") .setDesc(descUseNewFileTemplate) .addToggle((toggle) => { toggle @@ -587,10 +571,12 @@ export class TemplaterSettingTab extends PluginSettingTab { new Setting(this.containerEl) .setName("Add New") - .setDesc("Add new file template regex") + .setDesc( + "Add new file regex. The first match from the top is used, so the order of the rules is important. Use `.*` as a final catch-all, if you need it." + ) .addButton((button: ButtonComponent) => { button - .setTooltip("Add additional file template regex") + .setTooltip("Add additional file regex") .setButtonText("+") .setCta() .onClick(() => { From ef462cb6899259a9ed75a1e6b4abcbb155bee4c9 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Thu, 29 Dec 2022 11:27:47 +0100 Subject: [PATCH 4/7] Update settings docs --- docs/src/settings.md | 57 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/docs/src/settings.md b/docs/src/settings.md index 8730a983..3870fa4d 100644 --- a/docs/src/settings.md +++ b/docs/src/settings.md @@ -1,27 +1,58 @@ # Settings -You can set a `Template folder location` to tell [Templater](https://github.com/SilentVoid13/Templater) to only check this folder for templates. +## General Settings -You can set a timeout for your system commands with the `Timeout` option. A system command that takes longer than what you defined will be canceled and considered as a failure. +- `Template folder location`: Files in this folder will be available as templates. +- `Syntax Highlighting on Desktop` adds syntax highlighting for [Templater](https://github.com/SilentVoid13/Templater) commands in edit mode. +- `Syntax Highlighting on Mobile` adds syntax highlighting for [Templater](https://github.com/SilentVoid13/Templater) commands in edit mode on mobile. Use with caution: this may break live preview on mobile platforms." +- `Automatic jump to cursor` automatically triggers `tp.file.cursor` after inserting a template. You can also set a hotkey to manually trigger `tp.file.cursor`. +- `Trigger Templater on new file creation`: [Templater](https://github.com/SilentVoid13/Templater) will listen for the new file creation event, and, if it matches a rule you've set, replace every command it finds in the new file's content. This makes [Templater](https://github.com/SilentVoid13/Templater) compatible with other plugins like the Daily note core plugin, Calendar plugin, Review plugin, Note refactor plugin, etc. + - Make sure to set up rules under either Folder Templates or File Regex Template below. + - **Warning:** This can be dangerous if you create new files with unknown / unsafe content on creation. Make sure that every new file's content is safe on creation." +- `Show icon in sidebar`: Show [Templater](https://github.com/SilentVoid13/Templater) icon in sidebar ribbon, allowing you to quickly use templates anywhere. -You can set [Templater](https://github.com/SilentVoid13/Templater) to be triggered on new file creation. It will listen for the new file creation event and replace every command it finds in the new file's content. +## Template Hotkeys -This makes Templater compatible with other plugins like the Daily note core plugin, Calendar plugin, Review plugin, Note refactor plugin, ... +Template Hotkeys allows you to bind a template to a hotkey. -## Security Warning +## Folder Templates -It can be dangerous if you create new files with unknown / unsafe content on creation. Make sure that every new file's content is safe on creation. +**Note**: This setting is hidden by default. To view it first enable the `Trigger Template on new file creation` setting. And since it's mutually exclusive with File Regex Templates, enabling one will disable the other. -## Folder Templates +You can specify a template that will automatically be used on a selected folder and children using the `Folder Templates` functionality. The deepest match will be used, so the order of the rules is irrelevant. + +Add a rule for "`/`" if you need a catch-all. + +## File Regex Templates + +**Note**: This setting is hidden by default. To view it first enable the `Trigger Template on new file creation` setting. And since it's mutually exclusive with Folder Templates, enabling one will disable the other. + +You can specify regex declarations that a new file's path will be tested against. If a regex matches, the associated template will automatically be used. Rules are tested top-to-bottom, and the first match will be used. + +End with a rule for "`.*`" if you need a catch-all. + +Use a tool like [Regex101](https://regex101.com/) to verify your regexes. + +## Startup Templates + +Startup Templates are templates that will get executed once when Templater starts. + +These templates won't output anything. + +This can be useful to set up templates adding hooks to obsidian events for example. + +## User Script Functions + +All JavaScript files in this folder will be loaded as CommonJS modules, to import custom [user functions](./user-functions/overview.md). -You can specify a template that will automatically be used on a selected folder and children using the `Folder Templates` functionality. +The folder needs to be accessible from the vault. -**Note**: This setting is hidden by default. To view it first enable the `Trigger Template on new file creation` setting. +Check the [documentation](./user-functions/script-user-functions.md) for more information. -## System Commands +## User System Command Functions -You can enable system commands. This allows you to create [user functions](./user-functions/overview.md) linked to system commands. +Allows you to create [user functions](./user-functions/overview.md) linked to system commands. -### Arbitrary system commands +Check the [documentation](./user-functions/system-user-functions.md) for more information. -It can be dangerous to execute arbitrary system commands from untrusted sources. Only run system commands that you understand, from trusted sources. +**Warning:** It can be dangerous to execute arbitrary system commands from untrusted sources. Only run system commands that you understand, from trusted sources. From b1c16d3415275eff4e76b76a28890d34b9fe05d2 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Fri, 14 Jul 2023 09:42:06 +0200 Subject: [PATCH 5/7] Remove VSCode settings from PR --- .gitignore | 1 - .vscode/settings.json | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 9c91ad60..631c668b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ ## VSCode .vscode/* -!.vscode/settings.json # npm node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index db5aa504..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.defaultFormatter": "esbenp.prettier-vscode", -} From 56e8dd970cc1bf82f76f2da13104682ffe91959e Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Fri, 14 Jul 2023 09:42:06 +0200 Subject: [PATCH 6/7] Remove VSCode settings from PR --- .gitignore | 3 +-- .vscode/settings.json | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 9c91ad60..a21b995d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ .idea ## VSCode -.vscode/* -!.vscode/settings.json +.vscode/ # npm node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index db5aa504..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.defaultFormatter": "esbenp.prettier-vscode", -} From 4ba5ee9552487efc5ba54de82bd0d2b5d56d31e2 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Thu, 26 Sep 2024 10:01:38 +0200 Subject: [PATCH 7/7] Remove unrelated files --- .gitignore | 115 ++-- src/core/Templater.ts.orig | 563 ------------------- src/settings/Settings.ts.orig | 980 ---------------------------------- 3 files changed, 57 insertions(+), 1601 deletions(-) delete mode 100644 src/core/Templater.ts.orig delete mode 100644 src/settings/Settings.ts.orig diff --git a/.gitignore b/.gitignore index a21b995d..e7dc1d72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,58 +1,57 @@ -# Editors - -## Intellij -*.iml -.idea - -## VSCode -.vscode/ - -# npm -node_modules -package-lock.json - -# build -main.js -main.test.js -*.js.map - -# test -test/ -tests_jest/ - -# Obsidian -data.json -.hotreload -resources/ - -# Other -RESOURCES.md - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +# Editors + +## Intellij +*.iml +.idea + +## VSCode +.vscode/ + +# npm +node_modules + +# build +main.js +main.test.js +*.js.map + +# test +test/ +tests_jest/ + +# Obsidian +data.json +.hotreload +resources/ + +# Other +RESOURCES.md + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/src/core/Templater.ts.orig b/src/core/Templater.ts.orig deleted file mode 100644 index dca31afb..00000000 --- a/src/core/Templater.ts.orig +++ /dev/null @@ -1,563 +0,0 @@ -import { - MarkdownPostProcessorContext, - MarkdownView, - normalizePath, - TAbstractFile, - TFile, - TFolder, -} from "obsidian"; -import { - delay, - generate_dynamic_command_regex, - get_active_file, - get_folder_path_from_file_path, - resolve_tfile, -} from "utils/Utils"; -import TemplaterPlugin from "main"; -import { - FunctionsGenerator, - FunctionsMode, -} from "./functions/FunctionsGenerator"; -import { errorWrapper, errorWrapperSync, TemplaterError } from "utils/Error"; -import { Parser } from "./parser/Parser"; -import { log_error } from "utils/Log"; - -export enum RunMode { - CreateNewFromTemplate, - AppendActiveFile, - OverwriteFile, - OverwriteActiveFile, - DynamicProcessor, - StartupTemplate, -} - -export type RunningConfig = { - template_file: TFile | undefined; - target_file: TFile; - run_mode: RunMode; - active_file?: TFile | null; -}; - -export class Templater { - public parser: Parser; - public functions_generator: FunctionsGenerator; - public current_functions_object: Record; - public files_with_pending_templates: Set; - - constructor(private plugin: TemplaterPlugin) { - this.functions_generator = new FunctionsGenerator(this.plugin); - this.parser = new Parser(); - } - - async setup(): Promise { - this.files_with_pending_templates = new Set(); - await this.parser.init(); - await this.functions_generator.init(); - this.plugin.registerMarkdownPostProcessor((el, ctx) => - this.process_dynamic_templates(el, ctx) - ); - } - - create_running_config( - template_file: TFile | undefined, - target_file: TFile, - run_mode: RunMode - ): RunningConfig { - const active_file = get_active_file(app); - - return { - template_file: template_file, - target_file: target_file, - run_mode: run_mode, - active_file: active_file, - }; - } - - async read_and_parse_template(config: RunningConfig): Promise { - const template_content = await app.vault.read( - config.template_file as TFile - ); - return this.parse_template(config, template_content); - } - - async parse_template( - config: RunningConfig, - template_content: string - ): Promise { - const functions_object = await this.functions_generator.generate_object( - config, - FunctionsMode.USER_INTERNAL - ); - this.current_functions_object = functions_object; - const content = await this.parser.parse_commands( - template_content, - functions_object - ); - return content; - } - - private start_templater_task(path: string) { - this.files_with_pending_templates.add(path); - } - - private async end_templater_task(path: string) { - this.files_with_pending_templates.delete(path); - if (this.files_with_pending_templates.size === 0) { - app.workspace.trigger("templater:all-templates-executed"); - await this.functions_generator.teardown(); - } - } - - async create_new_note_from_template( - template: TFile | string, - folder?: TFolder | string, - filename?: string, - open_new_note = true - ): Promise { - // TODO: Maybe there is an obsidian API function for that - if (!folder) { - const new_file_location = app.vault.getConfig("newFileLocation"); - switch (new_file_location) { - case "current": { - const active_file = get_active_file(app); - if (active_file) { - folder = active_file.parent; - } - break; - } - case "folder": - folder = app.fileManager.getNewFileParent(""); - break; - case "root": - folder = app.vault.getRoot(); - break; - default: - break; - } - } - - const extension = - template instanceof TFile ? template.extension || "md" : "md"; - const created_note = await errorWrapper(async () => { - const folderPath = folder instanceof TFolder ? folder.path : folder; - const path = app.vault.getAvailablePath( - normalizePath(`${folderPath ?? ""}/${filename || "Untitled"}`), - extension - ); - const folder_path = get_folder_path_from_file_path(path); - if ( - folder_path && - !app.vault.getAbstractFileByPathInsensitive(folder_path) - ) { - await app.vault.createFolder(folder_path); - } - return app.vault.create(path, ""); - }, `Couldn't create ${extension} file.`); - - if (created_note == null) { - return; - } - - const { path } = created_note; - this.start_templater_task(path); - let running_config: RunningConfig; - let output_content: string; - if (template instanceof TFile) { - running_config = this.create_running_config( - template, - created_note, - RunMode.CreateNewFromTemplate - ); - output_content = await errorWrapper( - async () => this.read_and_parse_template(running_config), - "Template parsing error, aborting." - ); - } else { - running_config = this.create_running_config( - undefined, - created_note, - RunMode.CreateNewFromTemplate - ); - output_content = await errorWrapper( - async () => this.parse_template(running_config, template), - "Template parsing error, aborting." - ); - } - - if (output_content == null) { - await app.vault.delete(created_note); - await this.end_templater_task(path); - return; - } - - await app.vault.modify(created_note, output_content); - - app.workspace.trigger("templater:new-note-from-template", { - file: created_note, - content: output_content, - }); - - if (open_new_note) { - const active_leaf = app.workspace.getLeaf(false); - if (!active_leaf) { - log_error(new TemplaterError("No active leaf")); - return; - } - await active_leaf.openFile(created_note, { - state: { mode: "source" }, - }); - - await this.plugin.editor_handler.jump_to_next_cursor_location( - created_note, - true - ); - - active_leaf.setEphemeralState({ - rename: "all", - }); - } - - await this.end_templater_task(path); - return created_note; - } - - async append_template_to_active_file(template_file: TFile): Promise { - const active_view = app.workspace.getActiveViewOfType(MarkdownView); - const active_editor = app.workspace.activeEditor; - if (!active_editor || !active_editor.file || !active_editor.editor) { - log_error( - new TemplaterError("No active editor, can't append templates.") - ); - return; - } - const { path } = active_editor.file; - this.start_templater_task(path); - const running_config = this.create_running_config( - template_file, - active_editor.file, - RunMode.AppendActiveFile - ); - const output_content = await errorWrapper( - async () => this.read_and_parse_template(running_config), - "Template parsing error, aborting." - ); - // errorWrapper failed - if (output_content == null) { - await this.end_templater_task(path); - return; - } - - const editor = active_editor.editor; - const doc = editor.getDoc(); - const oldSelections = doc.listSelections(); - doc.replaceSelection(output_content); - // Refresh editor to ensure properties widget shows after inserting template in blank file - if (active_editor.file) { - await app.vault.append(active_editor.file, ""); - } - app.workspace.trigger("templater:template-appended", { - view: active_view, - editor: active_editor, - content: output_content, - oldSelections, - newSelections: doc.listSelections(), - }); - - await this.plugin.editor_handler.jump_to_next_cursor_location( - active_editor.file, - true - ); - await this.end_templater_task(path); - } - - async write_template_to_file( - template_file: TFile, - file: TFile - ): Promise { - const { path } = file; - this.start_templater_task(path); - const active_editor = app.workspace.activeEditor; - const active_file = get_active_file(app); - const running_config = this.create_running_config( - template_file, - file, - RunMode.OverwriteFile - ); - const output_content = await errorWrapper( - async () => this.read_and_parse_template(running_config), - "Template parsing error, aborting." - ); - // errorWrapper failed - if (output_content == null) { - await this.end_templater_task(path); - return; - } - await app.vault.modify(file, output_content); - // Set cursor to first line of editor (below properties) - // https://github.com/SilentVoid13/Templater/issues/1231 - if ( - active_file?.path === file.path && - active_editor && - active_editor.editor - ) { - const editor = active_editor.editor; - editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 0 }); - } - app.workspace.trigger("templater:new-note-from-template", { - file, - content: output_content, - }); - await this.plugin.editor_handler.jump_to_next_cursor_location( - file, - true - ); - await this.end_templater_task(path); - } - - overwrite_active_file_commands(): void { - const active_editor = app.workspace.activeEditor; - if (!active_editor || !active_editor.file) { - log_error( - new TemplaterError( - "Active editor is null, can't overwrite content" - ) - ); - return; - } - this.overwrite_file_commands(active_editor.file, true); - } - - async overwrite_file_commands( - file: TFile, - active_file = false - ): Promise { - const { path } = file; - this.start_templater_task(path); - const running_config = this.create_running_config( - file, - file, - active_file ? RunMode.OverwriteActiveFile : RunMode.OverwriteFile - ); - const output_content = await errorWrapper( - async () => this.read_and_parse_template(running_config), - "Template parsing error, aborting." - ); - // errorWrapper failed - if (output_content == null) { - await this.end_templater_task(path); - return; - } - await app.vault.modify(file, output_content); - app.workspace.trigger("templater:overwrite-file", { - file, - content: output_content, - }); - await this.plugin.editor_handler.jump_to_next_cursor_location( - file, - true - ); - await this.end_templater_task(path); - } - - async process_dynamic_templates( - el: HTMLElement, - ctx: MarkdownPostProcessorContext - ): Promise { - const dynamic_command_regex = generate_dynamic_command_regex(); - - const walker = document.createNodeIterator(el, NodeFilter.SHOW_TEXT); - let node; - let pass = false; - let functions_object: Record; - while ((node = walker.nextNode())) { - let content = node.nodeValue; - if (content !== null) { - let match = dynamic_command_regex.exec(content); - if (match !== null) { - const file = app.metadataCache.getFirstLinkpathDest( - "", - ctx.sourcePath - ); - if (!file || !(file instanceof TFile)) { - return; - } - if (!pass) { - pass = true; - const config = this.create_running_config( - file, - file, - RunMode.DynamicProcessor - ); - functions_object = - await this.functions_generator.generate_object( - config, - FunctionsMode.USER_INTERNAL - ); - this.current_functions_object = functions_object; - } - } - - while (match != null) { - // Not the most efficient way to exclude the '+' from the command but I couldn't find something better - const complete_command = match[1] + match[2]; - const command_output: string = await errorWrapper( - async () => { - return await this.parser.parse_commands( - complete_command, - functions_object - ); - }, - `Command Parsing error in dynamic command '${complete_command}'` - ); - if (command_output == null) { - return; - } - const start = - dynamic_command_regex.lastIndex - match[0].length; - const end = dynamic_command_regex.lastIndex; - content = - content.substring(0, start) + - command_output + - content.substring(end); - - dynamic_command_regex.lastIndex += - command_output.length - match[0].length; - match = dynamic_command_regex.exec(content); - } - node.nodeValue = content; - } - } - } - - get_new_file_template_for_folder(folder: TFolder): string | undefined { - do { - const match = this.plugin.settings.folder_templates.find( - (e) => e.folder == folder.path - ); - - if (match && match.template) { - return match.template; - } - - folder = folder.parent; - } while (folder); - } - - get_new_file_template_for_file(file: TFile): string | undefined { - const match = this.plugin.settings.file_templates.find((e) => { - const eRegex = new RegExp(e.regex); - return eRegex.test(file.path); - }); - - if (match && match.template) { - return match.template; - } - } - - static async on_file_creation( - templater: Templater, - file: TAbstractFile - ): Promise { - if (!(file instanceof TFile) || file.extension !== "md") { - return; - } - - // Avoids template replacement when syncing template files - const template_folder = normalizePath( - templater.plugin.settings.templates_folder - ); - if (file.path.includes(template_folder) && template_folder !== "/") { - return; - } - - // TODO: find a better way to do this - // Currently, I have to wait for the note extractor plugin to add the file content before replacing - await delay(300); -<<<<<<< HEAD -======= - - // Avoids template replacement when creating file from template without content before delay - if (templater.files_with_pending_templates.has(file.path)) { - return; - } - ->>>>>>> b7b03cf41b9b3353dc57930a4d3bbbf1a53b1caf - if ( - file.stat.size == 0 && - templater.plugin.settings.enable_folder_templates - ) { - const folder_template_match = - templater.get_new_file_template_for_folder(file.parent); - if (!folder_template_match) { - return; - } - const template_file: TFile = await errorWrapper( - async (): Promise => { - return resolve_tfile(folder_template_match); - }, - `Couldn't find template ${folder_template_match}` - ); - // errorWrapper failed - if (template_file == null) { - return; - } - await templater.write_template_to_file(template_file, file); - } else if ( - file.stat.size == 0 && - templater.plugin.settings.enable_file_templates - ) { - const file_template_match = - templater.get_new_file_template_for_file(file); - if (!file_template_match) { - return; - } - const template_file: TFile = await errorWrapper( - async (): Promise => { - return resolve_tfile(file_template_match); - }, - `Couldn't find template ${file_template_match}` - ); - // errorWrapper failed - if (template_file == null) { - return; - } - await templater.write_template_to_file(template_file, file); - } else { - if (file.stat.size <= 100000) { - //https://github.com/SilentVoid13/Templater/issues/873 - await templater.overwrite_file_commands(file); - } else { - console.log( - `Templater skipped parsing ${file.path} because file size exceeds 10000` - ); - } - } - } - - async execute_startup_scripts(): Promise { - for (const template of this.plugin.settings.startup_templates) { - if (!template) { - continue; - } - const file = errorWrapperSync( - () => resolve_tfile(template), - `Couldn't find startup template "${template}"` - ); - if (!file) { - continue; - } - const { path } = file; - this.start_templater_task(path); - const running_config = this.create_running_config( - file, - file, - RunMode.StartupTemplate - ); - await errorWrapper( - async () => this.read_and_parse_template(running_config), - `Startup Template parsing error, aborting.` - ); - await this.end_templater_task(path); - } - } -} diff --git a/src/settings/Settings.ts.orig b/src/settings/Settings.ts.orig deleted file mode 100644 index 8b15b3ab..00000000 --- a/src/settings/Settings.ts.orig +++ /dev/null @@ -1,980 +0,0 @@ -import { ButtonComponent, PluginSettingTab, Setting } from "obsidian"; -import { errorWrapperSync, TemplaterError } from "utils/Error"; -import { FolderSuggest } from "./suggesters/FolderSuggester"; -import { FileSuggest, FileSuggestMode } from "./suggesters/FileSuggester"; -import TemplaterPlugin from "main"; -import { arraymove, get_tfiles_from_folder } from "utils/Utils"; -import { log_error } from "utils/Log"; -import { HEART, PAYPAL } from "utils/Constants"; - -export interface FolderTemplate { - folder: string; - template: string; -} - -export interface FileTemplate { - regex: string; - template: string; -} - -export const DEFAULT_SETTINGS: Settings = { - command_timeout: 5, - templates_folder: "", - templates_pairs: [["", ""]], - trigger_on_file_creation: false, - auto_jump_to_cursor: false, - enable_system_commands: false, - shell_path: "", - user_scripts_folder: "", - enable_folder_templates: true, - folder_templates: [{ folder: "/", template: "" }], - enable_file_templates: false, - file_templates: [{ regex: ".*", template: "" }], - syntax_highlighting: true, - syntax_highlighting_mobile: false, - enabled_templates_hotkeys: [""], - startup_templates: [""] -}; - -export interface Settings { - command_timeout: number; - templates_folder: string; - templates_pairs: Array<[string, string]>; - trigger_on_file_creation: boolean; - auto_jump_to_cursor: boolean; - enable_system_commands: boolean; - shell_path: string; - user_scripts_folder: string; - enable_folder_templates: boolean; - folder_templates: Array; - enable_file_templates: boolean; - file_templates: Array; - syntax_highlighting: boolean; - syntax_highlighting_mobile: boolean; - enabled_templates_hotkeys: Array; - startup_templates: Array; -} - -export class TemplaterSettingTab extends PluginSettingTab { - constructor(private plugin: TemplaterPlugin) { - super(app, plugin); - } - - display(): void { - this.containerEl.empty(); - - this.add_template_folder_setting(); - this.add_internal_functions_setting(); - this.add_syntax_highlighting_settings(); - this.add_auto_jump_to_cursor(); - this.add_trigger_on_new_file_creation_setting(); - this.add_templates_hotkeys_setting(); - if (this.plugin.settings.trigger_on_file_creation) { - this.add_folder_templates_setting(); - this.add_file_templates_setting(); - } - this.add_startup_templates_setting(); - this.add_user_script_functions_setting(); - this.add_user_system_command_functions_setting(); - this.add_donating_setting(); - } - - add_template_folder_setting(): void { - new Setting(this.containerEl) - .setName("Template folder location") - .setDesc("Files in this folder will be available as templates.") - .addSearch((cb) => { - new FolderSuggest(cb.inputEl); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.templates_folder) - .onChange((new_folder) => { - this.plugin.settings.templates_folder = new_folder; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }); - } - - add_internal_functions_setting(): void { - const desc = document.createDocumentFragment(); - desc.append( - "Templater provides multiples predefined variables / functions that you can use.", - desc.createEl("br"), - "Check the ", - desc.createEl("a", { - href: "https://silentvoid13.github.io/Templater/", - text: "documentation", - }), - " to get a list of all the available internal variables / functions." - ); - - new Setting(this.containerEl) - .setName("Internal variables and functions") - .setDesc(desc); - } - - add_syntax_highlighting_settings(): void { - const desktopDesc = document.createDocumentFragment(); - desktopDesc.append( - "Adds syntax highlighting for Templater commands in edit mode." - ); - - const mobileDesc = document.createDocumentFragment(); - mobileDesc.append( - "Adds syntax highlighting for Templater commands in edit mode on " + - "mobile. Use with caution: this may break live preview on mobile " + - "platforms." - ); - - new Setting(this.containerEl) - .setName("Syntax highlighting on desktop") - .setDesc(desktopDesc) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.syntax_highlighting) - .onChange((syntax_highlighting) => { - this.plugin.settings.syntax_highlighting = - syntax_highlighting; - this.plugin.save_settings(); - this.plugin.event_handler.update_syntax_highlighting(); - }); - }); - - new Setting(this.containerEl) - .setName("Syntax highlighting on mobile") - .setDesc(mobileDesc) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.syntax_highlighting_mobile) - .onChange((syntax_highlighting_mobile) => { - this.plugin.settings.syntax_highlighting_mobile = - syntax_highlighting_mobile; - this.plugin.save_settings(); - this.plugin.event_handler.update_syntax_highlighting(); - }); - }); - } - - add_auto_jump_to_cursor(): void { - const desc = document.createDocumentFragment(); - desc.append( - "Automatically triggers ", - desc.createEl("code", { text: "tp.file.cursor" }), - " after inserting a template.", - desc.createEl("br"), - "You can also set a hotkey to manually trigger ", - desc.createEl("code", { text: "tp.file.cursor" }), - "." - ); - - new Setting(this.containerEl) - .setName("Automatic jump to cursor") - .setDesc(desc) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.auto_jump_to_cursor) - .onChange((auto_jump_to_cursor) => { - this.plugin.settings.auto_jump_to_cursor = - auto_jump_to_cursor; - this.plugin.save_settings(); - }); - }); - } - - add_trigger_on_new_file_creation_setting(): void { - const desc = document.createDocumentFragment(); - desc.append( - "Templater will listen for the new file creation event, and, if it matches a rule you've set, replace every command it finds in the new file's content. ", - "This makes Templater compatible with other plugins like the Daily note core plugin, Calendar plugin, Review plugin, Note refactor plugin, etc. ", - desc.createEl("br"), - desc.createEl("br"), - "Make sure to set up rules under either Folder Templates or File Regex Template below.", - desc.createEl("br"), - desc.createEl("br"), - desc.createEl("b", { - text: "Warning: ", - }), - "This can be dangerous if you create new files with unknown / unsafe content on creation. Make sure that every new file's content is safe on creation." - ); - - new Setting(this.containerEl) - .setName("Trigger Templater on new file creation") - .setDesc(desc) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.trigger_on_file_creation) - .onChange((trigger_on_file_creation) => { - this.plugin.settings.trigger_on_file_creation = - trigger_on_file_creation; - this.plugin.save_settings(); - this.plugin.event_handler.update_trigger_file_on_creation(); - // Force refresh - this.display(); - }); - }); - } - - add_templates_hotkeys_setting(): void { - new Setting(this.containerEl).setName("Template hotkeys").setHeading(); - - const desc = document.createDocumentFragment(); - desc.append( - "Template hotkeys allows you to bind a template to a hotkey." - ); - - new Setting(this.containerEl).setDesc(desc); - - this.plugin.settings.enabled_templates_hotkeys.forEach( - (template, index) => { - const s = new Setting(this.containerEl) - .addSearch((cb) => { - new FileSuggest( - cb.inputEl, - this.plugin, - FileSuggestMode.TemplateFiles - ); - cb.setPlaceholder("Example: folder1/template_file") - .setValue(template) - .onChange((new_template) => { - if ( - new_template && - this.plugin.settings.enabled_templates_hotkeys.contains( - new_template - ) - ) { - log_error( - new TemplaterError( - "This template is already bound to a hotkey" - ) - ); - return; - } - this.plugin.command_handler.add_template_hotkey( - this.plugin.settings - .enabled_templates_hotkeys[index], - new_template - ); - this.plugin.settings.enabled_templates_hotkeys[ - index - ] = new_template; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addExtraButton((cb) => { - cb.setIcon("any-key") - .setTooltip("Configure Hotkey") - .onClick(() => { - // TODO: Replace with future "official" way to do this - // @ts-ignore - app.setting.openTabById("hotkeys"); - // @ts-ignore - const tab = app.setting.activeTab; - tab.searchInputEl.value = "Templater: Insert"; - tab.updateHotkeyVisibility(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("up-chevron-glyph") - .setTooltip("Move up") - .onClick(() => { - arraymove( - this.plugin.settings - .enabled_templates_hotkeys, - index, - index - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("down-chevron-glyph") - .setTooltip("Move down") - .onClick(() => { - arraymove( - this.plugin.settings - .enabled_templates_hotkeys, - index, - index + 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - this.plugin.command_handler.remove_template_hotkey( - this.plugin.settings - .enabled_templates_hotkeys[index] - ); - this.plugin.settings.enabled_templates_hotkeys.splice( - index, - 1 - ); - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - s.infoEl.remove(); - } - ); - - new Setting(this.containerEl).addButton((cb) => { - cb.setButtonText("Add new hotkey for template") - .setCta() - .onClick(() => { - this.plugin.settings.enabled_templates_hotkeys.push(""); - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - } - - add_folder_templates_setting(): void { - this.containerEl.createEl("h2", { text: "Folder templates" }); - new Setting(this.containerEl).setName("Folder templates").setHeading(); - - const descHeading = document.createDocumentFragment(); - descHeading.append( - "Folder Templates are triggered when a new ", - descHeading.createEl("strong", { text: "empty" }), - " file is created in a given folder. Templater will fill the empty file with the specified template." - ); - - new Setting(this.containerEl).setDesc(descHeading); - - const descUseNewFileTemplate = document.createDocumentFragment(); - descUseNewFileTemplate.append( - "When enabled, Templater will make use of the folder templates defined below. This option is mutually exclusive with File Regex Templates below, so enabling one will disable the other." - ); - - new Setting(this.containerEl) - .setName("Enable folder templates") - .setDesc(descUseNewFileTemplate) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.enable_folder_templates) - .onChange((use_new_folder_templates) => { - this.plugin.settings.enable_folder_templates = - use_new_folder_templates; - if (use_new_folder_templates) { - this.plugin.settings.enable_file_templates = false; - } - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - - if (!this.plugin.settings.enable_folder_templates) { - return; - } - - new Setting(this.containerEl) -<<<<<<< HEAD - .setName("Add New") - .setDesc( - "Add new folder template. The deepest match is used, so the order of the rules here is irrelevant. A global default template would be defined on root (`/`)." - ) -======= - .setName("Add new") - .setDesc("Add new folder template") ->>>>>>> b7b03cf41b9b3353dc57930a4d3bbbf1a53b1caf - .addButton((button: ButtonComponent) => { - button - .setTooltip("Add additional folder template") - .setButtonText("+") - .setCta() - .onClick(() => { - this.plugin.settings.folder_templates.push({ - folder: "", - template: "", - }); - this.plugin.save_settings(); - this.display(); - }); - }); - - this.plugin.settings.folder_templates.forEach( - (folder_template, index) => { - const s = new Setting(this.containerEl) - .addSearch((cb) => { - new FolderSuggest(cb.inputEl); - cb.setPlaceholder("Folder") - .setValue(folder_template.folder) - .onChange((new_folder) => { - if ( - new_folder && - this.plugin.settings.folder_templates.some( - (e) => e.folder == new_folder - ) - ) { - log_error( - new TemplaterError( - "This folder already has a template associated with it" - ) - ); - return; - } - - this.plugin.settings.folder_templates[ - index - ].folder = new_folder; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addSearch((cb) => { - new FileSuggest( - cb.inputEl, - this.plugin, - FileSuggestMode.TemplateFiles - ); - cb.setPlaceholder("Template") - .setValue(folder_template.template) - .onChange((new_template) => { - this.plugin.settings.folder_templates[ - index - ].template = new_template; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addExtraButton((cb) => { - cb.setIcon("up-chevron-glyph") - .setTooltip("Move up") - .onClick(() => { - arraymove( - this.plugin.settings.folder_templates, - index, - index - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("down-chevron-glyph") - .setTooltip("Move down") - .onClick(() => { - arraymove( - this.plugin.settings.folder_templates, - index, - index + 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - this.plugin.settings.folder_templates.splice( - index, - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }); - s.infoEl.remove(); - } - ); - } - - add_file_templates_setting(): void { - this.containerEl.createEl("h2", { text: "File Regex Templates" }); - - const descHeading = document.createDocumentFragment(); - descHeading.append( - "File Regex Templates are triggered when a new ", - descHeading.createEl("strong", { text: "empty" }), - " file is created that matches one of them. Templater will fill the empty file with the specified template." - ); - - new Setting(this.containerEl).setDesc(descHeading); - - const descUseNewFileTemplate = document.createDocumentFragment(); - descUseNewFileTemplate.append( - "When enabled, Templater will make use of the file regex templates defined below. This option is mutually exclusive with Folder Templates above, so enabling one will disable the other." - ); - - new Setting(this.containerEl) - .setName("Enable File Regex Templates") - .setDesc(descUseNewFileTemplate) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.enable_file_templates) - .onChange((use_new_file_templates) => { - this.plugin.settings.enable_file_templates = - use_new_file_templates; - if (use_new_file_templates) { - this.plugin.settings.enable_folder_templates = - false; - } - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - - if (!this.plugin.settings.enable_file_templates) { - return; - } - - new Setting(this.containerEl) - .setName("Add New") - .setDesc( - "Add new file regex. The first match from the top is used, so the order of the rules is important. Use `.*` as a final catch-all, if you need it." - ) - .addButton((button: ButtonComponent) => { - button - .setTooltip("Add additional file regex") - .setButtonText("+") - .setCta() - .onClick(() => { - this.plugin.settings.file_templates.push({ - regex: "", - template: "", - }); - this.plugin.save_settings(); - this.display(); - }); - }); - - this.plugin.settings.file_templates.forEach((file_template, index) => { - const s = new Setting(this.containerEl) - .addText((cb) => { - cb.setPlaceholder("File Regex") - .setValue(file_template.regex) - .onChange((new_regex) => { - this.plugin.settings.file_templates[index].regex = - new_regex; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.inputEl.addClass("templater_search"); - }) - .addSearch((cb) => { - new FileSuggest( - cb.inputEl, - this.plugin, - FileSuggestMode.TemplateFiles - ); - cb.setPlaceholder("Template") - .setValue(file_template.template) - .onChange((new_template) => { - this.plugin.settings.file_templates[ - index - ].template = new_template; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addExtraButton((cb) => { - cb.setIcon("up-chevron-glyph") - .setTooltip("Move up") - .onClick(() => { - arraymove( - this.plugin.settings.file_templates, - index, - index - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("down-chevron-glyph") - .setTooltip("Move down") - .onClick(() => { - arraymove( - this.plugin.settings.file_templates, - index, - index + 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }) - .addExtraButton((cb) => { - cb.setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - this.plugin.settings.file_templates.splice( - index, - 1 - ); - this.plugin.save_settings(); - this.display(); - }); - }); - s.infoEl.remove(); - }); - } - - add_startup_templates_setting(): void { - new Setting(this.containerEl).setName("Startup templates").setHeading(); - - const desc = document.createDocumentFragment(); - desc.append( - "Startup templates are templates that will get executed once when Templater starts.", - desc.createEl("br"), - "These templates won't output anything.", - desc.createEl("br"), - "This can be useful to set up templates adding hooks to Obsidian events for example." - ); - - new Setting(this.containerEl).setDesc(desc); - - this.plugin.settings.startup_templates.forEach((template, index) => { - const s = new Setting(this.containerEl) - .addSearch((cb) => { - new FileSuggest( - cb.inputEl, - this.plugin, - FileSuggestMode.TemplateFiles - ); - cb.setPlaceholder("Example: folder1/template_file") - .setValue(template) - .onChange((new_template) => { - if ( - new_template && - this.plugin.settings.startup_templates.contains( - new_template - ) - ) { - log_error( - new TemplaterError( - "This startup template already exist" - ) - ); - return; - } - this.plugin.settings.startup_templates[index] = - new_template; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }) - .addExtraButton((cb) => { - cb.setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - this.plugin.settings.startup_templates.splice( - index, - 1 - ); - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - s.infoEl.remove(); - }); - - new Setting(this.containerEl).addButton((cb) => { - cb.setButtonText("Add new startup template") - .setCta() - .onClick(() => { - this.plugin.settings.startup_templates.push(""); - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - } - - add_user_script_functions_setting(): void { - new Setting(this.containerEl).setName("User script functions").setHeading(); - - let desc = document.createDocumentFragment(); - desc.append( - "All JavaScript files in this folder will be loaded as CommonJS modules, to import custom user functions.", - desc.createEl("br"), - "The folder needs to be accessible from the vault.", - desc.createEl("br"), - "Check the ", - desc.createEl("a", { - href: "https://silentvoid13.github.io/Templater/", - text: "documentation", - }), - " for more information." - ); - - new Setting(this.containerEl) - .setName("Script files folder location") - .setDesc(desc) - .addSearch((cb) => { - new FolderSuggest(cb.inputEl); - cb.setPlaceholder("Example: folder1/folder2") - .setValue(this.plugin.settings.user_scripts_folder) - .onChange((new_folder) => { - this.plugin.settings.user_scripts_folder = new_folder; - this.plugin.save_settings(); - }); - // @ts-ignore - cb.containerEl.addClass("templater_search"); - }); - - desc = document.createDocumentFragment(); - let name: string; - if (!this.plugin.settings.user_scripts_folder) { - name = "No user scripts folder set"; - } else { - const files = errorWrapperSync( - () => - get_tfiles_from_folder( - this.plugin.settings.user_scripts_folder - ), - `User scripts folder doesn't exist` - ); - if (!files || files.length === 0) { - name = "No user scripts detected"; - } else { - let count = 0; - for (const file of files) { - if (file.extension === "js") { - count++; - desc.append( - desc.createEl("li", { - text: `tp.user.${file.basename}`, - }) - ); - } - } - name = `Detected ${count} User Script(s)`; - } - } - - new Setting(this.containerEl) - .setName(name) - .setDesc(desc) - .addExtraButton((extra) => { - extra - .setIcon("sync") - .setTooltip("Refresh") - .onClick(() => { - // Force refresh - this.display(); - }); - }); - } - - add_user_system_command_functions_setting(): void { - let desc = document.createDocumentFragment(); - desc.append( - "Allows you to create user functions linked to system commands.", - desc.createEl("br"), - desc.createEl("b", { - text: "Warning: ", - }), - "It can be dangerous to execute arbitrary system commands from untrusted sources. Only run system commands that you understand, from trusted sources." - ); - new Setting(this.containerEl).setName("User system command functions").setHeading(); - - new Setting(this.containerEl) - .setName("Enable user system command functions") - .setDesc(desc) - .addToggle((toggle) => { - toggle - .setValue(this.plugin.settings.enable_system_commands) - .onChange((enable_system_commands) => { - this.plugin.settings.enable_system_commands = - enable_system_commands; - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - }); - - if (this.plugin.settings.enable_system_commands) { - new Setting(this.containerEl) - .setName("Timeout") - .setDesc("Maximum timeout in seconds for a system command.") - .addText((text) => { - text.setPlaceholder("Timeout") - .setValue( - this.plugin.settings.command_timeout.toString() - ) - .onChange((new_value) => { - const new_timeout = Number(new_value); - if (isNaN(new_timeout)) { - log_error( - new TemplaterError( - "Timeout must be a number" - ) - ); - return; - } - this.plugin.settings.command_timeout = new_timeout; - this.plugin.save_settings(); - }); - }); - - desc = document.createDocumentFragment(); - desc.append( - "Full path to the shell binary to execute the command with.", - desc.createEl("br"), - "This setting is optional and will default to the system's default shell if not specified.", - desc.createEl("br"), - "You can use forward slashes ('/') as path separators on all platforms if in doubt." - ); - new Setting(this.containerEl) - .setName("Shell binary location") - .setDesc(desc) - .addText((text) => { - text.setPlaceholder("Example: /bin/bash, ...") - .setValue(this.plugin.settings.shell_path) - .onChange((shell_path) => { - this.plugin.settings.shell_path = shell_path; - this.plugin.save_settings(); - }); - }); - - let i = 1; - this.plugin.settings.templates_pairs.forEach((template_pair) => { - const div = this.containerEl.createEl("div"); - div.addClass("templater_div"); - - const title = this.containerEl.createEl("h4", { - text: "User function n°" + i, - }); - title.addClass("templater_title"); - - const setting = new Setting(this.containerEl) - .addExtraButton((extra) => { - extra - .setIcon("cross") - .setTooltip("Delete") - .onClick(() => { - const index = - this.plugin.settings.templates_pairs.indexOf( - template_pair - ); - if (index > -1) { - this.plugin.settings.templates_pairs.splice( - index, - 1 - ); - this.plugin.save_settings(); - // Force refresh - this.display(); - } - }); - }) - .addText((text) => { - const t = text - .setPlaceholder("Function name") - .setValue(template_pair[0]) - .onChange((new_value) => { - const index = - this.plugin.settings.templates_pairs.indexOf( - template_pair - ); - if (index > -1) { - this.plugin.settings.templates_pairs[ - index - ][0] = new_value; - this.plugin.save_settings(); - } - }); - t.inputEl.addClass("templater_template"); - - return t; - }) - .addTextArea((text) => { - const t = text - .setPlaceholder("System command") - .setValue(template_pair[1]) - .onChange((new_cmd) => { - const index = - this.plugin.settings.templates_pairs.indexOf( - template_pair - ); - if (index > -1) { - this.plugin.settings.templates_pairs[ - index - ][1] = new_cmd; - this.plugin.save_settings(); - } - }); - - t.inputEl.setAttr("rows", 2); - t.inputEl.addClass("templater_cmd"); - - return t; - }); - - setting.infoEl.remove(); - - div.appendChild(title); - div.appendChild(this.containerEl.lastChild as Node); - - i += 1; - }); - - const div = this.containerEl.createEl("div"); - div.addClass("templater_div2"); - - const setting = new Setting(this.containerEl).addButton( - (button) => { - button - .setButtonText("Add new user function") - .setCta() - .onClick(() => { - this.plugin.settings.templates_pairs.push(["", ""]); - this.plugin.save_settings(); - // Force refresh - this.display(); - }); - } - ); - setting.infoEl.remove(); - - div.appendChild(this.containerEl.lastChild as Node); - } - } - - add_donating_setting(): void { - const s = new Setting(this.containerEl) - .setName("Donate") - .setDesc( - "If you like this Plugin, consider donating to support continued development." - ); - - const a1 = document.createElement("a"); - a1.setAttribute("href", "https://github.com/sponsors/silentvoid13"); - a1.addClass("templater_donating"); - const img1 = document.createElement("img"); - img1.src = - "https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86"; - a1.appendChild(img1); - - const a2 = document.createElement("a"); - a2.setAttribute( - "href", - "https://www.paypal.com/donate?hosted_button_id=U2SRGAFYXT32Q" - ); - a2.addClass("templater_donating"); - const img2 = document.createElement("img"); - img2.src = - "https://img.shields.io/badge/paypal-silentvoid13-yellow?style=social&logo=paypal"; - a2.appendChild(img2); - - s.settingEl.appendChild(a1); - s.settingEl.appendChild(a2); - } -}