├── styles.css ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── dependabot.yml ├── FUNDING.yml └── workflows │ ├── stale-bot.yml │ └── obsidian-plugin-release.yml ├── .gitignore ├── .knip.jsonc ├── manifest.json ├── .editorconfig ├── package.json ├── versions.json ├── Justfile ├── LICENSE ├── tsconfig.json ├── .esbuild.mjs ├── biome.jsonc ├── .release.mjs ├── README.md └── src ├── settings.ts └── main.ts /styles.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | 4 | # obsidian 5 | data.json 6 | main.js 7 | 8 | # Mac 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "chore(dependabot): " 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/displaying-a-sponsor-button-in-your-repository 2 | 3 | custom: https://www.paypal.me/ChrisGrieser 4 | ko_fi: pseudometa 5 | -------------------------------------------------------------------------------- /.knip.jsonc: -------------------------------------------------------------------------------- 1 | // DOCS https://knip.dev/overview/configuration 2 | { 3 | "$schema": "https://unpkg.com/knip@5/schema-jsonc.json", 4 | "entry": ["src/main.ts"], 5 | "project": ["**/*.ts"], 6 | "ignoreDependencies": [ 7 | "@biomejs/biome", // used only in pre-commit hook / task 8 | "markdownlint-cli" // used only in pre-commit hook / task 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "new-tab-default-page", 3 | "name": "Default New Tab Page", 4 | "version": "0.11.9", 5 | "minAppVersion": "1.0.0", 6 | "description": "Open a note of your choice when creating a new tab, like in the browser.", 7 | "author": "Chris Grieser (aka pseudometa)", 8 | "fundingUrl": "https://ko-fi.com/pseudometa", 9 | "authorUrl": "https://chris-grieser.de/", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | max_line_length = 100 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 3 10 | tab_width = 3 11 | trim_trailing_whitespace = true 12 | 13 | [*.{yml,yaml,cff}] 14 | indent_style = space 15 | indent_size = 2 16 | tab_width = 2 17 | 18 | [*.py] 19 | indent_style = space 20 | indent_size = 4 21 | tab_width = 4 22 | 23 | [*.md] 24 | indent_size = 4 25 | tab_width = 4 26 | trim_trailing_whitespace = false 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "New Tab Default Page", 3 | "description": "Open a note of your choice when creating a new tab, like in the browser.", 4 | "author": "Chris Grieser", 5 | "version": "0.11.9", 6 | "keywords": [], 7 | "license": "MIT", 8 | "main": "main.js", 9 | "devDependencies": { 10 | "@biomejs/biome": "latest", 11 | "@types/node": "^22.5.5", 12 | "builtin-modules": "^3.2.0", 13 | "esbuild": "^0.24.0", 14 | "knip": "latest", 15 | "markdownlint-cli": "latest", 16 | "obsidian": "latest", 17 | "tslib": "2.7.0", 18 | "typescript": "^5.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.2.0": "0.16.0", 3 | "0.2.1": "0.16.0", 4 | "0.3.0": "0.16.0", 5 | "0.4.0": "0.16.0", 6 | "0.4.1": "0.16.0", 7 | "0.4.2": "0.16.0", 8 | "0.5.0": "0.16.0", 9 | "0.5.1": "0.16.0", 10 | "0.6.0": "0.16.0", 11 | "0.7.0": "0.16.0", 12 | "0.7.1": "0.16.0", 13 | "0.7.2": "0.16.0", 14 | "0.8.0": "0.16.0", 15 | "0.9.0": "0.16.0", 16 | "0.9.1": "0.16.0", 17 | "0.10.0": "0.16.0", 18 | "0.10.1": "0.16.0", 19 | "0.10.2": "0.16.0", 20 | "0.11.0": "1.0.0", 21 | "0.11.1": "1.0.0", 22 | "0.11.2": "1.0.0", 23 | "0.11.3": "1.0.0", 24 | "0.11.4": "1.0.0", 25 | "0.11.5": "1.0.0", 26 | "0.11.6": "1.0.0", 27 | "0.11.7": "1.0.0", 28 | "0.11.8": "1.0.0", 29 | "0.11.9": "1.0.0" 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea 3 | title: "Feature Request: " 4 | labels: ["feature request"] 5 | body: 6 | - type: textarea 7 | id: feature-requested 8 | attributes: 9 | label: Feature Requested 10 | description: A clear and concise description of the feature. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshot 15 | attributes: 16 | label: Relevant Screenshot 17 | description: If applicable, add screenshots or a screen recording to help explain the request. 18 | - type: checkboxes 19 | id: checklist 20 | attributes: 21 | label: Checklist 22 | options: 23 | - label: The feature would be useful to more users than just me. 24 | required: true 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/stale-bot.yml: -------------------------------------------------------------------------------- 1 | name: Stale bot 2 | on: 3 | schedule: 4 | - cron: "18 04 * * 3" 5 | 6 | permissions: 7 | issues: write 8 | pull-requests: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Close stale issues 15 | uses: actions/stale@v10 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | # DOCS https://github.com/actions/stale#all-options 20 | days-before-stale: 180 21 | days-before-close: 7 22 | stale-issue-label: "Stale" 23 | stale-issue-message: | 24 | This issue has been automatically marked as stale. 25 | **If this issue is still affecting you, please leave any comment**, for example "bump", and it will be kept open. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity, and will not be monitored. 28 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | set quiet := true 2 | 3 | test_vault := "$HOME/Vaults/phd-data-analysis" 4 | 5 | #─────────────────────────────────────────────────────────────────────────────── 6 | 7 | [macos] 8 | build-and-reload: 9 | #!/usr/bin/env zsh 10 | node .esbuild.mjs 11 | cp -f "main.js" "{{ test_vault }}/.obsidian/plugins/quadro/main.js" 12 | vault_name=$(basename "{{ test_vault }}") 13 | open "obsidian://open?vault=$vault_name" 14 | 15 | # reload (INFO: requires registering the URI manually in a helper plugin) 16 | plugin_id=$(grep '"id"' "./manifest.json" | cut -d'"' -f4) 17 | open "obsidian://reload-plugin?id=$plugin_id&vault=$vault_name" 18 | 19 | check-all: 20 | zsh ./.githooks/pre-commit 21 | 22 | check-tsc-qf: 23 | npx tsc --noEmit --skipLibCheck --strict && echo "Typescript OK" 24 | 25 | release: 26 | node .release.mjs 27 | 28 | analyze: 29 | node .esbuild.mjs analyze 30 | 31 | init: 32 | #!/usr/bin/env zsh 33 | git config core.hooksPath .githooks 34 | npm install 35 | node .esbuild.mjs 36 | 37 | update-deps: 38 | #!/usr/bin/env zsh 39 | npm update 40 | node .esbuild.mjs 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Christopher Grieser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/obsidian-plugin-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | env: 8 | PLUGIN_NAME: ${{ github.event.repository.name }} 9 | 10 | #─────────────────────────────────────────────────────────────────────────────── 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-latest 15 | permissions: { contents: write } 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v6 22 | with: { node-version: "22.x" } 23 | 24 | - name: Build plugin 25 | run: | 26 | npm install 27 | node .esbuild.mjs "production" 28 | mkdir ${{ env.PLUGIN_NAME }} 29 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} 30 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 31 | 32 | - name: Release 33 | uses: softprops/action-gh-release@v2 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | generate_release_notes: true 37 | files: | 38 | ${{ env.PLUGIN_NAME }}.zip 39 | main.js 40 | manifest.json 41 | styles.css 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // DOCS https://www.typescriptlang.org/tsconfig 2 | //────────────────────────────────────────────────────────────────────────────── 3 | 4 | { 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "moduleResolution": "node", 8 | "module": "ESNext", 9 | "target": "ES2022", 10 | "lib": ["DOM", "ES5", "ES6", "ES7", "es2023"], 11 | 12 | // SOURCE strictest tsconfig 2.0.0 https://github.com/tsconfig/bases/blob/main/bases/strictest.json 13 | "strict": true, 14 | "allowUnusedLabels": false, 15 | "allowUnreachableCode": false, 16 | "exactOptionalPropertyTypes": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitOverride": true, 19 | "noImplicitReturns": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | 23 | // disabled 24 | "noUncheckedIndexedAccess": false, // *too* strict since it complains about every [0] 25 | "noPropertyAccessFromIndexSignature": false, // not useful https://stackoverflow.com/a/70748402/22114136 26 | 27 | // helpers 28 | "isolatedModules": true, 29 | "esModuleInterop": false, // setting to true causes issues with Obsidian's imported `moment` 30 | "importHelpers": true, 31 | "skipLibCheck": true, 32 | "forceConsistentCasingInFileNames": true 33 | }, 34 | "include": ["src/**/*.ts"], 35 | 36 | "$schema": "https://json.schemastore.org/tsconfig" 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: textarea 7 | id: bug-description 8 | attributes: 9 | label: Bug Description 10 | description: A clear and concise description of the bug. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshot 15 | attributes: 16 | label: Relevant Screenshot 17 | description: If applicable, add screenshots or a screen recording to help explain your problem. 18 | - type: textarea 19 | id: reproduction-steps 20 | attributes: 21 | label: To Reproduce 22 | description: Steps to reproduce the problem 23 | placeholder: | 24 | For example: 25 | 1. Go to '...' 26 | 2. Click on '...' 27 | 3. Scroll down to '...' 28 | - type: input 29 | id: obsi-version 30 | attributes: 31 | label: Obsidian Version 32 | description: You can find the version in the *About* Tab of the settings. 33 | placeholder: 0.13.19 34 | validations: 35 | required: true 36 | - type: checkboxes 37 | id: editor 38 | attributes: 39 | label: Which editor are you using? 40 | options: 41 | - label: New Editor 42 | - label: Legacy Editor 43 | - type: checkboxes 44 | id: checklist 45 | attributes: 46 | label: Checklist 47 | options: 48 | - label: I updated to the latest version of the plugin. 49 | required: true 50 | 51 | -------------------------------------------------------------------------------- /.esbuild.mjs: -------------------------------------------------------------------------------- 1 | import { appendFileSync } from "node:fs"; 2 | import builtins from "builtin-modules"; 3 | import esbuild from "esbuild"; 4 | 5 | const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 6 | If you want to view the source, please visit the GitHub repository of this plugin. */`; 7 | 8 | const production = process.argv[2] === "production"; 9 | const analyze = process.argv[2] === "analyze"; 10 | 11 | //────────────────────────────────────────────────────────────────────────────── 12 | 13 | const result = await esbuild 14 | .build({ 15 | entryPoints: ["src/main.ts"], 16 | banner: { js: banner + "\n" }, 17 | outfile: "main.js", 18 | bundle: true, 19 | // biome-ignore format: no need to inspect this regularly 20 | external: ["obsidian", "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr", ...builtins], 21 | format: "cjs", 22 | target: "es2022", 23 | sourcemap: production || analyze ? false : "inline", 24 | minify: production || analyze, 25 | drop: ["debugger"], 26 | treeShaking: true, 27 | logLevel: analyze ? "silent" : "info", 28 | metafile: analyze, 29 | }) 30 | .catch(() => process.exit(1)); 31 | 32 | //────────────────────────────────────────────────────────────────────────────── 33 | 34 | // DOCS https://esbuild.github.io/api/index#metafile 35 | if (result.metafile) { 36 | const sizes = await esbuild.analyzeMetafile(result.metafile, { verbose: false }); 37 | console.info(sizes); 38 | } 39 | 40 | // FIX prevent Obsidian from removing the source map when using dev build 41 | // https://forum.obsidian.md/t/source-map-trimming-in-dev-builds/87612 42 | if (!production) appendFileSync(import.meta.dirname + "/main.js", "\n/* nosourcemap */"); 43 | -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | // DOCS https://biomejs.dev/reference/configuration/ 2 | //────────────────────────────────────────────────────────────────────────────── 3 | { 4 | "linter": { 5 | "rules": { 6 | "all": true, 7 | "nursery": { 8 | "all": true, 9 | "useImportRestrictions": "off", // over-complicating stuff 10 | "noSecrets": "off", // buggy, many false positives 11 | "useGuardForIn": "off" 12 | }, 13 | "complexity": { 14 | "noExcessiveCognitiveComplexity": "info", 15 | "useSimplifiedLogicExpression": "off" // complains about negated and-conditions 16 | }, 17 | "performance": { 18 | "useTopLevelRegex": "off" // often not needed 19 | }, 20 | "suspicious": { 21 | "noConsole": { 22 | "level": "info", 23 | "options": { "allow": ["assert", "error", "info", "warn"] } // only disallow `.log` 24 | } 25 | }, 26 | "correctness": { 27 | "noUndeclaredDependencies": "off", // incompatible with typescript's default auto-imports 28 | "useImportExtensions": "off", // incompatible with typescript's default auto-imports 29 | "noNodejsModules": "off" // Obsidian is client-site, thus requiring them 30 | }, 31 | "style": { 32 | "useBlockStatements": "off", // too much clutter 33 | "useImportType": "off", // incompatible with typescript's default auto-imports 34 | "useTemplate": "off", // too strict, simple concatenations are often fine 35 | "noParameterAssign": "off", // not useful 36 | "useNamingConvention": { "level": "info", "options": { "strictCase": false } } 37 | } 38 | } 39 | }, 40 | "javascript": { 41 | "globals": ["activeDocument", "activeWindow"] // electron 42 | }, 43 | "formatter": { 44 | "useEditorconfig": true, 45 | "formatWithErrors": true 46 | }, 47 | "files": { 48 | "ignoreUnknown": true 49 | }, 50 | "vcs": { 51 | "enabled": true, 52 | "clientKind": "git", 53 | "useIgnoreFile": true 54 | }, 55 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json" 56 | } 57 | -------------------------------------------------------------------------------- /.release.mjs: -------------------------------------------------------------------------------- 1 | import { spawn } from "node:child_process"; 2 | import { readFileSync, writeFileSync } from "node:fs"; 3 | import readlinePromises from "node:readline/promises"; 4 | 5 | /** @param {string} filepath */ 6 | function readJson(filepath) { 7 | return JSON.parse(readFileSync(filepath, "utf8")); 8 | } 9 | 10 | /** @param {string} filepath @param {object} jsonObj */ 11 | function writeJson(filepath, jsonObj) { 12 | writeFileSync(filepath, JSON.stringify(jsonObj, null, "\t") + "\n"); 13 | } 14 | 15 | //────────────────────────────────────────────────────────────────────────────── 16 | // PROMPT FOR TARGET VERSION 17 | 18 | const manifest = readJson("manifest.json"); 19 | const currentVersion = manifest.version; 20 | const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout }); 21 | 22 | console.info(`current version: ${currentVersion}`); 23 | const nextVersion = await rl.question(" next version: "); 24 | console.info("───────────────────────────"); 25 | if (!nextVersion?.match(/\d+\.\d+\.\d+/) || nextVersion === currentVersion) { 26 | console.error("\x1b[1;31mInvalid target version given, aborting.\x1b[0m"); 27 | process.exit(1); 28 | } 29 | 30 | rl.close(); 31 | 32 | //────────────────────────────────────────────────────────────────────────────── 33 | // UPDATE VERSION IN VARIOUS JSONS 34 | 35 | manifest.version = nextVersion; 36 | writeJson("manifest.json", manifest); 37 | 38 | const versionsJson = readJson("versions.json"); 39 | versionsJson[nextVersion] = manifest.minAppVersion; 40 | writeJson("versions.json", versionsJson); 41 | 42 | const packageJson = readJson("package.json"); 43 | packageJson.version = nextVersion; 44 | writeJson("package.json", packageJson); 45 | 46 | const packageLock = readJson("package-lock.json"); 47 | packageLock.version = nextVersion; 48 | packageLock.packages[""].version = nextVersion; 49 | writeJson("package-lock.json", packageLock); 50 | 51 | //────────────────────────────────────────────────────────────────────────────── 52 | // UPDATE GIT REPO 53 | 54 | const gitCommands = [ 55 | "git add manifest.json versions.json package.json package-lock.json", 56 | `git commit --no-verify --message="release: ${nextVersion}"`, // skip hook, since only bumping 57 | "git pull --no-progress", 58 | "git push --no-progress", 59 | `git tag ${nextVersion}`, // tag triggers the release action 60 | "git push --no-progress origin --tags", 61 | ]; 62 | 63 | // INFO as opposed to `exec`, `spawn` does not buffer the output 64 | const gitProcess = spawn(gitCommands.join(" && "), [], { shell: true }); 65 | gitProcess.stdout.on("data", (data) => console.info(data.toString().trim())); 66 | gitProcess.stderr.on("data", (data) => console.info(data.toString().trim())); 67 | gitProcess.on("error", (_err) => process.exit(1)); 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New Tab Default Page 2 | 3 |   4 | 5 | [Obsidian](https://obsidian.md/) plugin to open a note of your choice or the Quick Switcher when creating a new tab, like in the browser. 6 | 7 | > [!IMPORTANT] 8 | > This plugin is in maintenance mode, since I am not using it anymore myself. 9 | > No new features will be added, including support for other plugins. It will 10 | > remain available for anyone who wants to use it, but I suggest using one of 11 | > the many more advanced plugins with similar functionality, such as far more, 12 | > such as [Obsidian Homepage](https://obsidian.md/plugins?id=homepage). 13 | 14 | --- 15 | 16 | 📽️ [YouTube Demo of the plugin](https://www.youtube.com/watch?v=PKcnKqErwJw&t=2s) 17 | 18 | --- 19 | 20 | 21 | - [Examples for Default New Tab Pages](#examples-for-default-new-tab-pages) 22 | - [Usage](#usage) 23 | - [Advanced Examples](#advanced-examples) 24 | - [Random Quote](#random-quote) 25 | - [Installation](#installation) 26 | - [Donate](#donate) 27 | - [Thanks](#thanks) 28 | 29 | 30 | ## Examples for Default New Tab Pages 31 | - your homepage note 32 | - your daily note 33 | - a scratchpad note 34 | - trigger a quick switcher 35 | - an image 36 | - a random quote 37 | - … 38 | 39 | ## Usage 40 | 1. Set the note to open in new tabs in the plugin settings. 41 | 2. Set the mode in which the note should open. 42 | 3. Open a new tab. 43 | 44 | > __Note__ 45 | > Closing your last tab also opens the new tab page. 46 | 47 | ## Advanced Examples 48 | 49 | ### Random Quote 50 | To have a random quote on every new tab, you can use dataview and paste the following code as a dataviewjs-codeblock in a note. 51 | 52 | ```js 53 | const quote = JSON.parse(await request("https://api.quotable.io/random")); 54 | dv.span(`> "${quote.content}" \n>