├── .npmrc ├── .prettierignore ├── versions.json ├── .prettierrc ├── .editorconfig ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── package.json ├── src ├── settings │ ├── folderSuggester.ts │ └── suggest.ts └── main.ts ├── esbuild.config.mjs └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.vscode 3 | **/node_modules 4 | 5 | package-lock.json -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.1": "0.15.0", 3 | "1.0.0": "0.15.0", 4 | "1.1.1": "0.15.0" 5 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-new-note-new-window", 3 | "name": "New Note New Window", 4 | "version": "1.1.1", 5 | "minAppVersion": "0.15.0", 6 | "description": "Plugin for easily opening new notes in a floating window.", 7 | "author": "Pr0dt0s", 8 | "authorUrl": "https://github.com/Pr0dt0s", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": ["DOM", "ES5", "ES6", "ES7"] 15 | }, 16 | "include": ["**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs'; 2 | 3 | const targetVersion = process.env.npm_package_version || process.argv[2]; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync('manifest.json', 'utf8')); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync('manifest.json', JSON.stringify(manifest, null, ' ')); 10 | console.log('manifest.json', JSON.stringify(manifest, null, ' ')); 11 | 12 | // update versions.json with target version and minAppVersion from manifest.json 13 | let versions = JSON.parse(readFileSync('versions.json', 'utf8')); 14 | versions[targetVersion] = minAppVersion; 15 | writeFileSync('versions.json', JSON.stringify(versions, null, ' ')); 16 | console.log('versions.json', JSON.stringify(versions, null, ' ')); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-new-note-new-window", 3 | "version": "1.0.0", 4 | "description": "Plugin for opening new notes in a floating window in Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json", 10 | "lint": "prettier --write ." 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@popperjs/core": "^2.11.7", 17 | "@types/node": "^16.11.6", 18 | "@typescript-eslint/eslint-plugin": "5.29.0", 19 | "@typescript-eslint/parser": "5.29.0", 20 | "all-contributors-cli": "^6.24.0", 21 | "builtin-modules": "3.3.0", 22 | "esbuild": "0.14.47", 23 | "obsidian": "latest", 24 | "prettier": "^2.7.1", 25 | "tslib": "2.4.0", 26 | "typescript": "4.7.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/folderSuggester.ts: -------------------------------------------------------------------------------- 1 | // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes 2 | 3 | import { TAbstractFile, TFolder } from 'obsidian'; 4 | import { TextInputSuggest } from './suggest'; 5 | 6 | export class FolderSuggest extends TextInputSuggest { 7 | getSuggestions(inputStr: string): TFolder[] { 8 | const abstractFiles = app.vault.getAllLoadedFiles(); 9 | const folders: TFolder[] = []; 10 | const lowerCaseInputStr = inputStr.toLowerCase(); 11 | 12 | abstractFiles.forEach((folder: TAbstractFile) => { 13 | if ( 14 | folder instanceof TFolder && 15 | folder.path.toLowerCase().contains(lowerCaseInputStr) 16 | ) { 17 | folders.push(folder); 18 | } 19 | }); 20 | 21 | return folders; 22 | } 23 | 24 | renderSuggestion(file: TFolder, el: HTMLElement): void { 25 | el.setText(file.path); 26 | } 27 | 28 | selectSuggestion(file: TFolder): void { 29 | this.inputEl.value = file.path; 30 | this.inputEl.trigger('input'); 31 | this.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild'; 2 | import process from 'process'; 3 | import builtins from 'builtin-modules'; 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | `; 10 | 11 | const prod = process.argv[2] === 'production'; 12 | 13 | esbuild 14 | .build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['src/main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins, 35 | ], 36 | format: 'cjs', 37 | watch: !prod, 38 | target: 'es2018', 39 | logLevel: 'info', 40 | sourcemap: prod ? false : 'inline', 41 | treeShaking: true, 42 | outfile: 'main.js', 43 | }) 44 | .catch(() => process.exit(1)); 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New Note New Window for Obsidian 2 | 3 | Plugin for opening new notes in a floating window in [Obsidian](https://obsidian.md). 4 | 5 | ## How to use 6 | 7 | - Just run the provided command **New Note New Window: Create New Note in Window**, or better yet add a custom shortcut through settings! 8 | 9 | New Notes are created in the folder path set under the "Default location for new notes" in Obsidian Settings > Files & Links. 10 | 11 | ## Settings 12 | 13 | | Setting | Default | Description | 14 | | -------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 15 | | **Reuse Floating Window** | `false` | if `true` new notes will open in the same floating window
if `false` new notes will create a new floating window | 16 | | **Create in the default folder** | `true` | if `true` new notes will be created in the default Obsidian folder (as configured)
if `false` new notes will be created in the specified folder | 17 | | **Custom folder** | "" | Custom folder in wich to create the new notes. Only used when **Create in the default folder** is set to `false` | 18 | 19 | ## Manually installing the plugin 20 | 21 | - Copy over the `main.js`, `manifest.json` from the latest [release](https://github.com/Pr0dt0s/new-note-new-window/releases) to your vault `VaultFolder/.obsidian/plugins/new-note-new-window/`. 22 | 23 | ## TODOs 24 | 25 | - [ ] Add an option to create new commands with a set of configuration combinations. E.g. to be able to create a separate hotkey for creating files on path X and another on path Y. 26 | 27 | ## Contributors ✨ 28 | 29 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 |
Paul Luckett
Paul Luckett

📆
Pr0dt0s
Pr0dt0s

💻
44 | 45 | Add your contributions 46 | 47 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 58 | -------------------------------------------------------------------------------- /src/settings/suggest.ts: -------------------------------------------------------------------------------- 1 | // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes 2 | 3 | import { ISuggestOwner, Scope } from 'obsidian'; 4 | import { createPopper, Instance as PopperInstance } from '@popperjs/core'; 5 | 6 | const wrapAround = (value: number, size: number): number => { 7 | return ((value % size) + size) % size; 8 | }; 9 | 10 | class Suggest { 11 | private owner: ISuggestOwner; 12 | private values: T[]; 13 | private suggestions: HTMLDivElement[]; 14 | private selectedItem: number; 15 | private containerEl: HTMLElement; 16 | 17 | constructor(owner: ISuggestOwner, containerEl: HTMLElement, scope: Scope) { 18 | this.owner = owner; 19 | this.containerEl = containerEl; 20 | 21 | containerEl.on( 22 | 'click', 23 | '.suggestion-item', 24 | this.onSuggestionClick.bind(this) 25 | ); 26 | containerEl.on( 27 | 'mousemove', 28 | '.suggestion-item', 29 | this.onSuggestionMouseover.bind(this) 30 | ); 31 | 32 | scope.register([], 'ArrowUp', (event) => { 33 | if (!event.isComposing) { 34 | this.setSelectedItem(this.selectedItem - 1, true); 35 | return false; 36 | } 37 | }); 38 | 39 | scope.register([], 'ArrowDown', (event) => { 40 | if (!event.isComposing) { 41 | this.setSelectedItem(this.selectedItem + 1, true); 42 | return false; 43 | } 44 | }); 45 | 46 | scope.register([], 'Enter', (event) => { 47 | if (!event.isComposing) { 48 | this.useSelectedItem(event); 49 | return false; 50 | } 51 | }); 52 | } 53 | 54 | onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void { 55 | event.preventDefault(); 56 | 57 | const item = this.suggestions.indexOf(el); 58 | this.setSelectedItem(item, false); 59 | this.useSelectedItem(event); 60 | } 61 | 62 | onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void { 63 | const item = this.suggestions.indexOf(el); 64 | this.setSelectedItem(item, false); 65 | } 66 | 67 | setSuggestions(values: T[]) { 68 | this.containerEl.empty(); 69 | const suggestionEls: HTMLDivElement[] = []; 70 | 71 | values.forEach((value) => { 72 | const suggestionEl = this.containerEl.createDiv('suggestion-item'); 73 | this.owner.renderSuggestion(value, suggestionEl); 74 | suggestionEls.push(suggestionEl); 75 | }); 76 | 77 | this.values = values; 78 | this.suggestions = suggestionEls; 79 | this.setSelectedItem(0, false); 80 | } 81 | 82 | useSelectedItem(event: MouseEvent | KeyboardEvent) { 83 | const currentValue = this.values[this.selectedItem]; 84 | if (currentValue) { 85 | this.owner.selectSuggestion(currentValue, event); 86 | } 87 | } 88 | 89 | setSelectedItem(selectedIndex: number, scrollIntoView: boolean) { 90 | const normalizedIndex = wrapAround(selectedIndex, this.suggestions.length); 91 | const prevSelectedSuggestion = this.suggestions[this.selectedItem]; 92 | const selectedSuggestion = this.suggestions[normalizedIndex]; 93 | 94 | prevSelectedSuggestion?.removeClass('is-selected'); 95 | selectedSuggestion?.addClass('is-selected'); 96 | 97 | this.selectedItem = normalizedIndex; 98 | 99 | if (scrollIntoView) { 100 | selectedSuggestion.scrollIntoView(false); 101 | } 102 | } 103 | } 104 | 105 | export abstract class TextInputSuggest implements ISuggestOwner { 106 | protected inputEl: HTMLInputElement | HTMLTextAreaElement; 107 | 108 | private popper: PopperInstance; 109 | private scope: Scope; 110 | private suggestEl: HTMLElement; 111 | private suggest: Suggest; 112 | 113 | constructor(inputEl: HTMLInputElement | HTMLTextAreaElement) { 114 | this.inputEl = inputEl; 115 | this.scope = new Scope(); 116 | 117 | this.suggestEl = createDiv('suggestion-container'); 118 | const suggestion = this.suggestEl.createDiv('suggestion'); 119 | this.suggest = new Suggest(this, suggestion, this.scope); 120 | 121 | this.scope.register([], 'Escape', this.close.bind(this)); 122 | 123 | this.inputEl.addEventListener('input', this.onInputChanged.bind(this)); 124 | this.inputEl.addEventListener('focus', this.onInputChanged.bind(this)); 125 | this.inputEl.addEventListener('blur', this.close.bind(this)); 126 | this.suggestEl.on( 127 | 'mousedown', 128 | '.suggestion-container', 129 | (event: MouseEvent) => { 130 | event.preventDefault(); 131 | } 132 | ); 133 | } 134 | 135 | onInputChanged(): void { 136 | const inputStr = this.inputEl.value; 137 | const suggestions = this.getSuggestions(inputStr); 138 | 139 | if (!suggestions) { 140 | this.close(); 141 | return; 142 | } 143 | 144 | if (suggestions.length > 0) { 145 | this.suggest.setSuggestions(suggestions); 146 | //@ts-ignore 147 | this.open(app.dom.appContainerEl, this.inputEl); 148 | } else { 149 | this.close(); 150 | } 151 | } 152 | 153 | open(container: HTMLElement, inputEl: HTMLElement): void { 154 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 155 | app.keymap.pushScope(this.scope); 156 | 157 | container.appendChild(this.suggestEl); 158 | this.popper = createPopper(inputEl, this.suggestEl, { 159 | placement: 'bottom-start', 160 | modifiers: [ 161 | { 162 | name: 'sameWidth', 163 | enabled: true, 164 | fn: ({ state, instance }) => { 165 | // Note: positioning needs to be calculated twice - 166 | // first pass - positioning it according to the width of the popper 167 | // second pass - position it with the width bound to the reference element 168 | // we need to early exit to avoid an infinite loop 169 | const targetWidth = `${state.rects.reference.width}px`; 170 | if (state.styles.popper.width === targetWidth) { 171 | return; 172 | } 173 | state.styles.popper.width = targetWidth; 174 | instance.update(); 175 | }, 176 | phase: 'beforeWrite', 177 | requires: ['computeStyles'], 178 | }, 179 | ], 180 | }); 181 | } 182 | 183 | close(): void { 184 | app.keymap.popScope(this.scope); 185 | 186 | this.suggest.setSuggestions([]); 187 | if (this.popper) this.popper.destroy(); 188 | this.suggestEl.detach(); 189 | } 190 | 191 | abstract getSuggestions(inputStr: string): T[]; 192 | abstract renderSuggestion(item: T, el: HTMLElement): void; 193 | abstract selectSuggestion(item: T): void; 194 | } 195 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | Notice, 4 | Plugin, 5 | PluginSettingTab, 6 | Setting, 7 | TFolder, 8 | WorkspaceLeaf, 9 | } from 'obsidian'; 10 | 11 | import { FolderSuggest } from 'src/settings/folderSuggester'; 12 | 13 | interface LeafStatus { 14 | leaf: WorkspaceLeaf; 15 | status: 'new' | 'loaded'; 16 | } 17 | interface NewNoteNewWindowSettings { 18 | openInLastFloatingWindow: boolean; 19 | useDefaultFolder: boolean; 20 | customFolder: string; 21 | } 22 | 23 | const DEFAULT_SETTINGS: NewNoteNewWindowSettings = { 24 | openInLastFloatingWindow: true, 25 | useDefaultFolder: true, 26 | customFolder: '/', 27 | }; 28 | 29 | export default class NewNoteNewWindow extends Plugin { 30 | settings: NewNoteNewWindowSettings; 31 | 32 | leaves: LeafStatus[] = []; 33 | 34 | async onload() { 35 | await this.loadSettings(); 36 | 37 | const trackClose = (leafStatus: LeafStatus) => { 38 | this.registerInterval( 39 | window.setInterval(() => { 40 | if ( 41 | leafStatus.status == 'loaded' && 42 | leafStatus.leaf.getViewState().type == 'empty' 43 | ) { 44 | this.leaves = this.leaves.filter((l) => l.leaf != leafStatus.leaf); 45 | leafStatus.leaf.detach(); 46 | } 47 | }, 10) 48 | ); 49 | }; 50 | 51 | const createLeaf = (where: 'new-window' | 'same-window') => { 52 | let leaf: WorkspaceLeaf | undefined = undefined; 53 | 54 | if (!this.leaves.length) { 55 | leaf = this.app.workspace.getLeaf('window'); 56 | } else { 57 | if (where == 'same-window') { 58 | let lastLeafStatus = this.leaves[this.leaves.length - 1]; 59 | this.app.workspace.setActiveLeaf(lastLeafStatus.leaf); 60 | leaf = this.app.workspace.getLeaf('tab'); 61 | } else { 62 | leaf = this.app.workspace.getLeaf('window'); 63 | } 64 | } 65 | const newLeafStatus: LeafStatus = { leaf, status: 'new' }; 66 | 67 | trackClose(newLeafStatus); 68 | this.leaves.push(newLeafStatus); 69 | return newLeafStatus; 70 | }; 71 | 72 | this.addCommand({ 73 | id: 'nnnw-create-new-note-in-window', 74 | name: 'Create note in Window', 75 | icon: 'popup-open', 76 | callback: async () => { 77 | let fileName = this.generateNewFileNameInFolder(); 78 | let nleafStatus = this.settings.openInLastFloatingWindow 79 | ? createLeaf('same-window') 80 | : createLeaf('new-window'); 81 | const newFile = await this.app.vault.create(fileName, '', {}); 82 | await nleafStatus.leaf.openFile(newFile); 83 | nleafStatus.status = 'loaded'; 84 | }, 85 | }); 86 | 87 | this.addSettingTab(new SettingTab(this.app, this)); 88 | } 89 | 90 | private getFileParent() { 91 | if (!this.settings.useDefaultFolder) { 92 | let folder = this.settings.customFolder; 93 | const abstractFile = this.app.vault.getAbstractFileByPath(folder); 94 | if (abstractFile && 'children' in (abstractFile as TFolder)) { 95 | return abstractFile as TFolder; 96 | } else { 97 | new Notice(`Error opening folder '${folder}'!`); 98 | throw new Error(`Could not open the folder at '${folder}'`); 99 | } 100 | } 101 | 102 | let lastFile = this.app.workspace.getActiveFile(); 103 | let path = !!lastFile ? lastFile.path : ''; 104 | return this.app.fileManager.getNewFileParent(path); 105 | } 106 | 107 | private generateNewFileNameInFolder() { 108 | const tfolder = this.getFileParent(); 109 | 110 | let newFilePath = tfolder.path; 111 | let untitleds = tfolder.children 112 | .filter((c) => c.name.startsWith('Untitled')) 113 | .map((c) => c.name); 114 | 115 | let fileName = ''; 116 | for (let i = 0; i <= untitleds.length; i++) { 117 | fileName = `Untitled${i > 0 ? ' ' + (i + 1) : ''}.md`; 118 | if (!untitleds.includes(fileName)) { 119 | break; 120 | } 121 | } 122 | return `${newFilePath}/${fileName}`; 123 | } 124 | 125 | onunload() { 126 | this.leaves = []; 127 | } 128 | 129 | async loadSettings() { 130 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 131 | } 132 | 133 | async saveSettings() { 134 | await this.saveData(this.settings); 135 | } 136 | } 137 | 138 | class SettingTab extends PluginSettingTab { 139 | plugin: NewNoteNewWindow; 140 | 141 | constructor(app: App, plugin: NewNoteNewWindow) { 142 | super(app, plugin); 143 | this.plugin = plugin; 144 | } 145 | 146 | display() { 147 | const { containerEl } = this; 148 | 149 | containerEl.empty(); 150 | containerEl.createEl('h2', { 151 | text: `${this.plugin.manifest.name} ${this.plugin.manifest.version}`, 152 | }); 153 | 154 | new Setting(containerEl) 155 | .setName('Reuse the same floating window.') 156 | .setDesc('If true new notes will open in the same floating window.') 157 | .addToggle((cb) => { 158 | cb.setValue(this.plugin.settings.openInLastFloatingWindow); 159 | cb.onChange(async (value) => { 160 | this.plugin.settings.openInLastFloatingWindow = value; 161 | await this.plugin.saveSettings(); 162 | }); 163 | }); 164 | 165 | new Setting(containerEl) 166 | .setName('Create in the default folder.') 167 | .setDesc( 168 | "Create the new files in the default folder as per Obsidian's configuration." 169 | ) 170 | .addToggle((cb) => { 171 | cb.setValue(this.plugin.settings.useDefaultFolder); 172 | cb.onChange(async (value) => { 173 | folderSetting.settingEl.style.display = value ? 'none' : 'block'; 174 | this.plugin.settings.useDefaultFolder = value; 175 | await this.plugin.saveSettings(); 176 | }); 177 | }); 178 | 179 | const folderSetting = new Setting(this.containerEl) 180 | .setName('Custom folder') 181 | .setDesc('Custom folder in wich to create the new notes.') 182 | .addSearch((cb) => { 183 | new FolderSuggest(cb.inputEl); 184 | cb.setPlaceholder('Example: folder1/folder2') 185 | .setValue(this.plugin.settings.customFolder) 186 | .onChange((new_folder) => { 187 | this.plugin.settings.customFolder = new_folder; 188 | this.plugin.saveSettings(); 189 | }); 190 | // // @ts-ignore 191 | // cb.containerEl.addClass('templater_search'); 192 | }); 193 | folderSetting.settingEl.style.display = this.plugin.settings 194 | .useDefaultFolder 195 | ? 'none' 196 | : 'block'; 197 | 198 | containerEl.createEl('hr'); 199 | const div1 = containerEl.createEl('div', { 200 | text: 'Developed by ', 201 | }); 202 | div1.createEl('a', { 203 | text: `Pr0dt0s`, 204 | href: `https://github.com/Pr0dt0s`, 205 | }); 206 | div1.appendText(' & '); 207 | div1.createEl('a', { 208 | text: `Brainflurry`, 209 | href: `http://brainflurry.com/`, 210 | }); 211 | containerEl.createEl('br'); 212 | const div2 = containerEl.createEl('div', { 213 | text: 'If you want to see the documentation, submit a bug, or a feature request you can do so ', 214 | }); 215 | div2.createEl('a', { 216 | text: 'here', 217 | href: 'https://github.com/Pr0dt0s/new-note-new-window', 218 | }); 219 | div2.appendText('.'); 220 | } 221 | } 222 | --------------------------------------------------------------------------------