├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.js ├── .npmrc ├── .nvmrc ├── .nxignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps ├── .gitkeep └── plugin │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── constants.ts │ │ ├── methods │ │ │ └── .gitkeep │ │ ├── plugin.ts │ │ ├── settingTab │ │ │ ├── args-search-and-remove.intf.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ │ ├── escape-reg-exp.fn.ts │ │ │ ├── find-queries.fn.ts │ │ │ ├── folder-suggest.ts │ │ │ ├── is-excalidraw-file.fn.ts │ │ │ ├── is-supported-query-type.fn.ts │ │ │ ├── is-table-query.fn.ts │ │ │ ├── log.ts │ │ │ ├── only-unique-array.tn.ts │ │ │ └── serialize-query.fn.ts │ ├── assets │ │ ├── buy-me-a-coffee.png │ │ └── styles.css │ ├── main.ts │ └── types │ │ └── .gitkeep │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── docs ├── README.md ├── SUMMARY.md ├── configuration.md └── usage.md ├── jest.config.ts ├── jest.preset.js ├── libs └── .gitkeep ├── manifest.json ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── executors │ └── .gitkeep ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json ├── version-bump.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | 15 | [*.sh] 16 | end_of_line = lf 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | }, 34 | { 35 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 36 | "env": { 37 | "jest": true 38 | }, 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | ## AUTO-DETECT 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | ## SOURCE CODE 19 | *.bat text eol=crlf 20 | *.coffee text 21 | *.css text 22 | *.htm text 23 | *.html text 24 | *.inc text 25 | *.ini text 26 | *.js text 27 | *.json text 28 | *.jsx text 29 | *.less text 30 | *.od text 31 | *.onlydata text 32 | *.php text 33 | *.pl text 34 | *.py text 35 | *.rb text 36 | *.sass text 37 | *.scm text 38 | *.scss text 39 | *.sh text eol=lf 40 | *.sql text 41 | *.styl text 42 | *.tag text 43 | *.ts text 44 | *.tsx text 45 | *.xml text 46 | *.xhtml text 47 | 48 | ## DOCKER 49 | *.dockerignore text 50 | Dockerfile text 51 | Dockerfile* text 52 | 53 | ## DOCUMENTATION 54 | *.markdown text 55 | *.md text 56 | *.mdwn text 57 | *.mdown text 58 | *.mkd text 59 | *.mkdn text 60 | *.mdtxt text 61 | *.mdtext text 62 | *.txt text 63 | AUTHORS text 64 | CHANGELOG text 65 | CHANGES text 66 | CONTRIBUTING text 67 | COPYING text 68 | copyright text 69 | *COPYRIGHT* text 70 | INSTALL text 71 | license text 72 | LICENSE text 73 | NEWS text 74 | readme text 75 | *README* text 76 | TODO text 77 | 78 | ## TEMPLATES 79 | *.dot text 80 | *.ejs text 81 | *.haml text 82 | *.handlebars text 83 | *.hbs text 84 | *.hbt text 85 | *.jade text 86 | *.latte text 87 | *.mustache text 88 | *.njk text 89 | *.phtml text 90 | *.tmpl text 91 | *.tpl text 92 | *.twig text 93 | 94 | ## LINTERS 95 | .babelrc text 96 | .csslintrc text 97 | .eslintrc text 98 | .htmlhintrc text 99 | .jscsrc text 100 | .jshintrc text 101 | .jshintignore text 102 | .prettierrc text 103 | .stylelintrc text 104 | 105 | ## CONFIGS 106 | *.bowerrc text 107 | *.cnf text 108 | *.conf text 109 | *.config text 110 | .browserslistrc text 111 | .editorconfig text 112 | .gitattributes text 113 | .gitconfig text 114 | .gitignore text 115 | .htaccess text 116 | *.npmignore text 117 | *.yaml text 118 | *.yml text 119 | browserslist text 120 | Makefile text 121 | makefile text 122 | 123 | ## HEROKU 124 | Procfile text 125 | .slugignore text 126 | 127 | ## GRAPHICS 128 | *.ai binary 129 | *.bmp binary 130 | *.eps binary 131 | *.gif binary 132 | *.ico binary 133 | *.jng binary 134 | *.jp2 binary 135 | *.jpg binary 136 | *.jpeg binary 137 | *.jpx binary 138 | *.jxr binary 139 | *.pdf binary 140 | *.png binary 141 | *.psb binary 142 | *.psd binary 143 | *.svg text 144 | *.svgz binary 145 | *.tif binary 146 | *.tiff binary 147 | *.wbmp binary 148 | *.webp binary 149 | 150 | ## AUDIO 151 | *.kar binary 152 | *.m4a binary 153 | *.mid binary 154 | *.midi binary 155 | *.mp3 binary 156 | *.ogg binary 157 | *.ra binary 158 | 159 | ## VIDEO 160 | *.3gpp binary 161 | *.3gp binary 162 | *.as binary 163 | *.asf binary 164 | *.asx binary 165 | *.fla binary 166 | *.flv binary 167 | *.m4v binary 168 | *.mng binary 169 | *.mov binary 170 | *.mp4 binary 171 | *.mpeg binary 172 | *.mpg binary 173 | *.ogv binary 174 | *.swc binary 175 | *.swf binary 176 | *.webm binary 177 | 178 | ## ARCHIVES 179 | *.7z binary 180 | *.gz binary 181 | *.jar binary 182 | *.rar binary 183 | *.tar binary 184 | *.zip binary 185 | 186 | ## FONTS 187 | *.ttf binary 188 | *.eot binary 189 | *.otf binary 190 | *.woff binary 191 | *.woff2 binary 192 | 193 | ## EXECUTABLES 194 | *.exe binary 195 | *.pyc binary 196 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [dsebastien] 2 | buy_me_a_coffee: dsebastien 3 | #thanks_dev: 4 | #custom: [''] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | actions: read 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | # Connect your workspace on nx.app and uncomment this to enable task distribution. 22 | # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested 23 | # - run: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-js" --stop-agents-after="build" 24 | 25 | # Install dependencies and cache node modules 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version-file: '.nvmrc' 29 | cache: 'npm' 30 | - run: npm ci 31 | 32 | - uses: nrwl/nx-set-shas@v4 33 | - run: git branch --track main origin/main 34 | if: ${{ github.event_name == 'pull_request' }} 35 | 36 | - run: npx nx-cloud record -- nx format:check 37 | - run: npx nx affected -t lint test build 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | actions: read 10 | contents: write 11 | 12 | env: 13 | PLUGIN_NAME: obsidian-dataview-serializer 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | # Install node and install dependencies 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version-file: '.nvmrc' 28 | cache: 'npm' 29 | - run: npm install 30 | 31 | # Build the production version 32 | - name: Build 33 | id: build 34 | run: | 35 | npm run build:prod 36 | ls ./dist/apps/plugin 37 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 38 | 39 | - name: Create Release 40 | id: create_release 41 | uses: actions/create-release@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | VERSION: ${{ github.ref }} 45 | with: 46 | tag_name: ${{ github.ref }} 47 | release_name: ${{ github.ref }} 48 | draft: false 49 | prerelease: false 50 | 51 | - name: Upload zip file 52 | id: upload-zip 53 | uses: actions/upload-release-asset@v1 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | upload_url: ${{ steps.create_release.outputs.upload_url }} 58 | asset_path: ./dist/apps/plugin/${{ env.PLUGIN_NAME }}.zip 59 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 60 | asset_content_type: application/zip 61 | 62 | - name: Upload main.js 63 | id: upload-main 64 | uses: actions/upload-release-asset@v1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: ./dist/apps/plugin/main.js 70 | asset_name: main.js 71 | asset_content_type: text/javascript 72 | 73 | - name: Upload manifest.json 74 | id: upload-manifest 75 | uses: actions/upload-release-asset@v1 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | with: 79 | upload_url: ${{ steps.create_release.outputs.upload_url }} 80 | asset_path: ./dist/apps/plugin/manifest.json 81 | asset_name: manifest.json 82 | asset_content_type: application/json 83 | 84 | - name: Upload styles.css 85 | id: upload-styles 86 | uses: actions/upload-release-asset@v1 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | with: 90 | upload_url: ${{ steps.create_release.outputs.upload_url }} 91 | asset_path: ./dist/apps/plugin/styles.css 92 | asset_name: styles.css 93 | asset_content_type: text/css 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /Drive 8 | 9 | # dependencies 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | 42 | # Obsidian 43 | data.json 44 | 45 | # Nx 46 | tools/**/*.js 47 | .nx/cache 48 | .vscode/tasks.json 49 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npx lint-staged 6 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.{md,json,yml,html,kr,cjs,mjs,js,ts,tsx,css,scss}': [ 3 | 'prettier --write --html-whitespace-sensitivity strict', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.13.1 2 | -------------------------------------------------------------------------------- /.nxignore: -------------------------------------------------------------------------------- 1 | apps/plugin/src/assets/** 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache 5 | /docs 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "firsttris.vscode-jest-runner", 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Creating an Issue 4 | 5 | Before you create a new Issue: 6 | 7 | 1. Please make sure there is no open issue about the topic yet 8 | 2. If it is a bug report, include the steps to reproduce the issue 9 | 3. If it is a feature request, please share the motivation for the new feature and how you would implement it 10 | 11 | ## Tests 12 | 13 | If you want to submit a bug fix or new feature, make sure that all tests are passing. 14 | 15 | ```bash 16 | $ npm test 17 | ``` 18 | 19 | ## Submitting a Pull Request 20 | 21 | - Check out the open issues first. Create new ones or discuss if needed 22 | - Fork the project 23 | - Push changes to a dedicated branch of your fork 24 | - Submit a pull request 25 | - Be sure to tag any issues your pull request is taking care of or is contributing to. 26 | 27 | ## Development environment 28 | 29 | In addition to the classic (npm/node, installation, etc), make sure to define the `OBSIDIAN_VAULT_LOCATION` environment variable. It should point to the root folder of an existing Obsidian vault. When building the DEV version (`npm run build:dev` or `npm run watch`), the plugin will be copied to that vault's `.obsidian/plugins` folder. This makes it easy to build and automatically have the up to date plugin for testing in Obsidian. It also avoids having to store the codebase within the Obsidian vault... 30 | 31 | ## Releasing a new version 32 | 33 | - Update the `minAppVersion` manually in `manifest.json` if needed 34 | - Run `npm version patch`, `npm version minor` or `npm version major` to update the `manifest.json` and `package.json` files 35 | - Commit all changes 36 | - Tag with the correct version number (e.g., 1.1.0) 37 | - Push the changes and the tag 38 | - The GitHub workflow will create the GitHub release and will add the necessary files as binary attachments 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sebastien Dubois 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Dataview Serializer 2 | 3 | Obsidian plugin that gives you the power of [Dataview](https://github.com/blacksmithgu/obsidian-dataview), but generates Markdown. Thanks to this, the output of your queries is saved in the notes, and the links actually appear on the Graph, making it even more useful. 4 | Turning Dataview queries into Markdown also ensures that the generated content appears on Obsidian Publish websites, which is not the case with the Dataview plugin. 5 | 6 | ## Pre-requisites 7 | 8 | The [Dataview](https://github.com/blacksmithgu/obsidian-dataview) plugin MUST be installed for this plugin to function correctly. 9 | 10 | ## Documentation 11 | 12 | You can find the documentation [here](https://developassion.gitbook.io/obsidian-dataview-serializer). 13 | 14 | ## News & support 15 | 16 | To stay up to date about this plugin, Obsidian in general, Personal Knowledge Management and note-taking, subscribe to [my newsletter](https://dsebastien.net). Note that the best way to support my work is to become a paid subscriber ❤️. 17 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/apps/.gitkeep -------------------------------------------------------------------------------- /apps/plugin/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/plugin/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'plugin', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/apps/plugin', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/plugin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugin", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/plugin/src", 5 | "projectType": "application", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/esbuild:esbuild", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "platform": "browser", 13 | "outputPath": "dist/apps/plugin", 14 | "outputFileName": "main.js", 15 | "format": ["cjs"], 16 | "target": "esnext", 17 | "bundle": true, 18 | "deleteOutputPath": true, 19 | "generatePackageJson": false, 20 | "watchMode": false, 21 | "logLevel": "info", 22 | "main": "apps/plugin/src/main.ts", 23 | "tsConfig": "apps/plugin/tsconfig.app.json", 24 | "thirdParty": true, 25 | "esbuildOptions": { 26 | "outExtension": { 27 | ".js": ".js" 28 | }, 29 | "treeShaking": false, 30 | "sourcemap": "inline", 31 | "external": [ 32 | "obsidian", 33 | "electron", 34 | "@codemirror/autocomplete", 35 | "@codemirror/collab", 36 | "@codemirror/commands", 37 | "@codemirror/language", 38 | "@codemirror/lint", 39 | "@codemirror/search", 40 | "@codemirror/state", 41 | "@codemirror/view", 42 | "@lezer/common", 43 | "@lezer/highlight", 44 | "@lezer/lr", 45 | "assert", 46 | "async_hooks", 47 | "buffer", 48 | "child_process", 49 | "cluster", 50 | "console", 51 | "constants", 52 | "crypto", 53 | "dgram", 54 | "diagnostics_channel", 55 | "dns", 56 | "domain", 57 | "events", 58 | "fs", 59 | "http", 60 | "http2", 61 | "https", 62 | "inspector", 63 | "module", 64 | "net", 65 | "os", 66 | "path", 67 | "perf_hooks", 68 | "process", 69 | "punycode", 70 | "querystring", 71 | "readline", 72 | "repl", 73 | "stream", 74 | "string_decoder", 75 | "timers", 76 | "tls", 77 | "trace_events", 78 | "tty", 79 | "url", 80 | "util", 81 | "v8", 82 | "vm", 83 | "wasi", 84 | "worker_threads", 85 | "zlib" 86 | ] 87 | } 88 | }, 89 | "configurations": { 90 | "development": {}, 91 | "production": { 92 | "minify": true, 93 | "esbuildOptions": { 94 | "banner": { 95 | "js": "/*\nTHIS IS A GENERATED/BUNDLED FILE BY ESBUILD\nif you want to view the source, please visit the github repository of this plugin\n*/" 96 | }, 97 | "legalComments": "inline", 98 | "outExtension": { 99 | ".js": ".js" 100 | }, 101 | "treeShaking": true, 102 | "sourcemap": false, 103 | "external": [ 104 | "obsidian", 105 | "electron", 106 | "@codemirror/autocomplete", 107 | "@codemirror/collab", 108 | "@codemirror/commands", 109 | "@codemirror/language", 110 | "@codemirror/lint", 111 | "@codemirror/search", 112 | "@codemirror/state", 113 | "@codemirror/view", 114 | "@lezer/common", 115 | "@lezer/highlight", 116 | "@lezer/lr", 117 | "assert", 118 | "async_hooks", 119 | "buffer", 120 | "child_process", 121 | "cluster", 122 | "console", 123 | "constants", 124 | "crypto", 125 | "dgram", 126 | "diagnostics_channel", 127 | "dns", 128 | "domain", 129 | "events", 130 | "fs", 131 | "http", 132 | "http2", 133 | "https", 134 | "inspector", 135 | "module", 136 | "net", 137 | "os", 138 | "path", 139 | "perf_hooks", 140 | "process", 141 | "punycode", 142 | "querystring", 143 | "readline", 144 | "repl", 145 | "stream", 146 | "string_decoder", 147 | "timers", 148 | "tls", 149 | "trace_events", 150 | "tty", 151 | "url", 152 | "util", 153 | "v8", 154 | "vm", 155 | "wasi", 156 | "worker_threads", 157 | "zlib" 158 | ] 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /apps/plugin/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/apps/plugin/src/app/.gitkeep -------------------------------------------------------------------------------- /apps/plugin/src/app/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * How many milliseconds to wait before hiding notices 3 | */ 4 | export const NOTICE_TIMEOUT = 5000; 5 | 6 | export const DEFAULT_CANVAS_FILE_NAME = 'Canvas.md'; 7 | export const MARKDOWN_FILE_EXTENSION = 'md'; 8 | 9 | export const QUERY_FLAG_OPEN = ``; 11 | 12 | // Query and serialized query structure: \n\n 13 | export const SERIALIZED_QUERY_START = `'; 15 | 16 | export const serializedQueriesRegex = new RegExp( 17 | `${SERIALIZED_QUERY_START}[^\\n]*${QUERY_FLAG_CLOSE}\\n([\\s\\S]*?)${SERIALIZED_QUERY_END}\\n`, 18 | 'g' 19 | ); 20 | 21 | export const MINIMUM_SECONDS_BETWEEN_UPDATES = 5; 22 | 23 | export const MINIMUM_MS_BETWEEN_EVENTS = 500; 24 | 25 | export const QUERY_TYPE_LIST = 'list'; 26 | export const QUERY_TYPE_TABLE = 'table'; 27 | 28 | export const SUPPORTED_QUERY_TYPES = [QUERY_TYPE_LIST, QUERY_TYPE_TABLE]; 29 | -------------------------------------------------------------------------------- /apps/plugin/src/app/methods/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/apps/plugin/src/app/methods/.gitkeep -------------------------------------------------------------------------------- /apps/plugin/src/app/plugin.ts: -------------------------------------------------------------------------------- 1 | import { debounce, Notice, Plugin, TAbstractFile, TFile } from 'obsidian'; 2 | import { DEFAULT_SETTINGS, PluginSettings } from './types'; 3 | import { SettingsTab } from './settingTab'; 4 | import { log } from './utils/log'; 5 | import { Draft, produce } from 'immer'; 6 | import { isExcalidrawFile } from './utils/is-excalidraw-file.fn'; 7 | import { 8 | DEFAULT_CANVAS_FILE_NAME, 9 | MARKDOWN_FILE_EXTENSION, 10 | MINIMUM_MS_BETWEEN_EVENTS, 11 | MINIMUM_SECONDS_BETWEEN_UPDATES, 12 | NOTICE_TIMEOUT, 13 | QUERY_FLAG_CLOSE, 14 | QUERY_FLAG_OPEN, 15 | SERIALIZED_QUERY_END, 16 | SERIALIZED_QUERY_START, 17 | serializedQueriesRegex, 18 | } from './constants'; 19 | import { getAPI } from 'obsidian-dataview'; 20 | import { DataviewApi } from 'obsidian-dataview/lib/api/plugin-api'; 21 | import { add, isBefore } from 'date-fns'; 22 | import { serializeQuery } from './utils/serialize-query.fn'; 23 | import { findQueries, QueryWithContext } from './utils/find-queries.fn'; 24 | import { escapeRegExp } from './utils/escape-reg-exp.fn'; 25 | import { isTableQuery } from './utils/is-table-query.fn'; 26 | 27 | export class DataviewSerializerPlugin extends Plugin { 28 | /** 29 | * The plugin settings are immutable 30 | */ 31 | settings: PluginSettings = produce(DEFAULT_SETTINGS, () => DEFAULT_SETTINGS); 32 | /** 33 | * The API of the Dataview plugin 34 | */ 35 | dataviewApi: DataviewApi | undefined; 36 | /** 37 | * When a recently updated file can be updated again 38 | */ 39 | nextPossibleUpdates: Map = new Map(); 40 | /** 41 | * List of recently updated files in the vault 42 | * Those will be processed by the next scheduled update 43 | */ 44 | recentlyUpdatedFiles: Set = new Set(); 45 | 46 | /** 47 | * Debounce file updates 48 | */ 49 | scheduleUpdate = debounce( 50 | this.processRecentlyUpdatedFiles.bind(this), 51 | MINIMUM_MS_BETWEEN_EVENTS, 52 | true 53 | ); 54 | 55 | /** 56 | * Process all the identified recently updated files 57 | */ 58 | async processRecentlyUpdatedFiles(): Promise { 59 | this.recentlyUpdatedFiles.forEach((file) => { 60 | this.processFile(file); 61 | }); 62 | this.recentlyUpdatedFiles.clear(); 63 | } 64 | 65 | /** 66 | * Executed as soon as the plugin loads 67 | */ 68 | async onload() { 69 | log('Initializing', 'debug'); 70 | 71 | // Retrieve the Dataview API 72 | this.dataviewApi = getAPI(); 73 | 74 | if (!this.dataviewApi) { 75 | const errMessage = 76 | 'The Dataview plugin is not installed or enabled. Please make sure it is installed and enabled, then restart Obsidian'; 77 | log(errMessage, 'error'); 78 | new Notice(errMessage, NOTICE_TIMEOUT); 79 | 80 | // DO NOTHING unless Dataview is installed and enabled 81 | return; 82 | } 83 | 84 | await this.loadSettings(); 85 | 86 | this.setupEventHandlers(); 87 | 88 | // Add a settings screen for the plugin 89 | this.addSettingTab(new SettingsTab(this.app, this)); 90 | 91 | // Add commands 92 | this.addCommand({ 93 | id: 'serialize-all-dataview-queries', 94 | name: 'Scan and serialize all Dataview queries', 95 | callback: async () => { 96 | log('Scanning and serializing all Dataview queries', 'debug'); 97 | const allVaultFiles = this.app.vault.getMarkdownFiles(); 98 | 99 | for (const vaultFile of allVaultFiles) { 100 | await this.processFile(vaultFile); 101 | } 102 | }, 103 | }); 104 | 105 | // Add command to insert dataview serializer block 106 | this.addCommand({ 107 | id: 'insert-dataview-serializer-block', 108 | name: 'Insert Dataview serializer block', 109 | editorCallback: (editor) => { 110 | const cursor = editor.getCursor(); 111 | const line = editor.getLine(cursor.line); 112 | const indentation = line.match(/^(\s*)/)?.[1] || ''; 113 | 114 | // Insert the dataview serializer block template 115 | const template = `${indentation}${QUERY_FLAG_OPEN}LIST FROM #foo ${QUERY_FLAG_CLOSE}`; 116 | 117 | editor.replaceRange(template, cursor); 118 | 119 | // Position cursor after "LIST" so user can replace it with their query 120 | const listStartPos = template.indexOf('LIST FROM #foo'); 121 | const newCursor = { 122 | line: cursor.line, 123 | ch: cursor.ch + listStartPos, 124 | }; 125 | const newCursorEnd = { 126 | line: cursor.line, 127 | ch: cursor.ch + listStartPos + 14, // Length of "LIST FROM #foo" 128 | }; 129 | 130 | editor.setSelection(newCursor, newCursorEnd); 131 | 132 | new Notice( 133 | 'Dataview serializer block inserted. Replace "LIST FROM #foo" with your query.' 134 | ); 135 | }, 136 | }); 137 | } 138 | 139 | // eslint-disable-next-line @typescript-eslint/no-empty-function 140 | onunload() {} 141 | 142 | /** 143 | * Load the plugin settings 144 | */ 145 | async loadSettings() { 146 | log('Loading settings', 'debug'); 147 | let loadedSettings = (await this.loadData()) as PluginSettings; 148 | 149 | if (!loadedSettings) { 150 | log('Using default settings', 'debug'); 151 | loadedSettings = produce(DEFAULT_SETTINGS, () => DEFAULT_SETTINGS); 152 | } 153 | 154 | let needToSaveSettings = false; 155 | 156 | this.settings = produce(this.settings, (draft: Draft) => { 157 | if ( 158 | loadedSettings.foldersToScan !== undefined && 159 | loadedSettings.foldersToScan !== null && 160 | Array.isArray(loadedSettings.foldersToScan) 161 | ) { 162 | draft.foldersToScan = loadedSettings.foldersToScan; 163 | } else { 164 | log('The loaded settings miss the [foldersToScan] property', 'debug'); 165 | needToSaveSettings = true; 166 | } 167 | 168 | if ( 169 | loadedSettings.ignoredFolders !== undefined && 170 | loadedSettings.ignoredFolders !== null && 171 | Array.isArray(loadedSettings.ignoredFolders) 172 | ) { 173 | draft.ignoredFolders = loadedSettings.ignoredFolders; 174 | } else { 175 | log('The loaded settings miss the [ignoredFolders] property', 'debug'); 176 | needToSaveSettings = true; 177 | } 178 | }); 179 | 180 | log(`Settings loaded`, 'debug', loadedSettings); 181 | 182 | if (needToSaveSettings) { 183 | this.saveSettings(); 184 | } 185 | } 186 | 187 | /** 188 | * Save the plugin settings 189 | */ 190 | async saveSettings() { 191 | log('Saving settings', 'debug'); 192 | await this.saveData(this.settings); 193 | log('Settings saved', 'debug', this.settings); 194 | } 195 | 196 | /** 197 | * Add the event handlers 198 | */ 199 | setupEventHandlers() { 200 | // Register events after layout is built to avoid initial wave of 'create' events 201 | this.app.workspace.onLayoutReady(async () => { 202 | //log('Adding event handlers', 'debug'); 203 | 204 | this.registerEvent( 205 | this.app.vault.on('create', (file) => { 206 | this.recentlyUpdatedFiles.add(file); 207 | this.scheduleUpdate(); 208 | }) 209 | ); 210 | 211 | this.registerEvent( 212 | this.app.vault.on('rename', (file) => { 213 | this.recentlyUpdatedFiles.add(file); 214 | this.scheduleUpdate(); 215 | }) 216 | ); 217 | 218 | this.registerEvent( 219 | this.app.vault.on('modify', (file) => { 220 | this.recentlyUpdatedFiles.add(file); 221 | this.scheduleUpdate(); 222 | }) 223 | ); 224 | }); 225 | } 226 | 227 | async processFile(_file: TAbstractFile): Promise { 228 | if (!(_file instanceof TFile)) { 229 | return; 230 | } 231 | 232 | // Safe from here on 233 | const file = _file as TFile; 234 | 235 | const shouldBeIgnored = await this.shouldFileBeIgnored(file); 236 | if (shouldBeIgnored) { 237 | return; 238 | } 239 | 240 | try { 241 | //log(`Processing file: ${file.path}`, 'debug'); 242 | 243 | const text = await this.app.vault.cachedRead(file); 244 | const foundQueries: QueryWithContext[] = findQueries(text); 245 | 246 | if (foundQueries.length === 0) { 247 | // No queries to serialize found in the file 248 | return; 249 | } 250 | 251 | // Process the modified file 252 | let updatedText = `${text}`; // To ensure we have access to replaceAll... 253 | 254 | // Remove existing serialized queries if any 255 | updatedText = updatedText.replace(serializedQueriesRegex, ''); 256 | //log('Cleaned up: ', 'debug', updatedText); 257 | 258 | // Serialize the supported queries in memory 259 | for (const queryWithContext of foundQueries) { 260 | const foundQuery = queryWithContext.query; 261 | const indentation = queryWithContext.indentation; 262 | //log(`Processing query: [${foundQuery}] in file [${file.path}]`, 'debug'); 263 | // Reference: https://github.com/IdreesInc/Waypoint/blob/master/main.ts 264 | const serializedQuery = await serializeQuery({ 265 | query: foundQuery, 266 | originFile: file.path, 267 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 268 | dataviewApi: this.dataviewApi!, 269 | app: this.app, 270 | indentation, 271 | }); 272 | //log('Serialized query: ', 'debug', serializedQuery); 273 | 274 | if ('' !== serializedQuery) { 275 | const escapedQuery = escapeRegExp(foundQuery); 276 | const escapedIndentation = escapeRegExp(indentation); 277 | const queryToSerializeRegex = new RegExp( 278 | `^${escapedIndentation}${QUERY_FLAG_OPEN}${escapedQuery}.*${QUERY_FLAG_CLOSE}\\n`, 279 | 'gm' 280 | ); 281 | 282 | let queryAndSerializedQuery = ''; 283 | if (isTableQuery(foundQuery)) { 284 | queryAndSerializedQuery = `${indentation}${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${foundQuery}${QUERY_FLAG_CLOSE}\n\n${serializedQuery}${ 285 | indentation.length > 0 ? '\n' : '' 286 | }${SERIALIZED_QUERY_END}\n`; 287 | } else { 288 | queryAndSerializedQuery = `${indentation}${QUERY_FLAG_OPEN}${foundQuery}${QUERY_FLAG_CLOSE}\n${SERIALIZED_QUERY_START}${foundQuery}${QUERY_FLAG_CLOSE}\n${serializedQuery}${ 289 | indentation.length > 0 ? '\n' : '' 290 | }${SERIALIZED_QUERY_END}\n`; 291 | } 292 | //log('Query to serialize regex: ', 'debug', queryToSerializeRegex); 293 | 294 | //log('Updated text before: ', 'debug', updatedText); 295 | updatedText = updatedText.replace( 296 | queryToSerializeRegex, 297 | queryAndSerializedQuery 298 | ); 299 | //log('Updated text after: ', 'debug', updatedText); 300 | } 301 | } 302 | 303 | // Keep track of the last time this file was updated to avoid modification loops 304 | const nextPossibleUpdateTimeForFile = add(new Date(file.stat.mtime), { 305 | seconds: MINIMUM_SECONDS_BETWEEN_UPDATES, 306 | }); 307 | this.nextPossibleUpdates.set(file.path, nextPossibleUpdateTimeForFile); 308 | 309 | // Save the updated version 310 | 311 | if (updatedText !== text) { 312 | //log('The file content has changed. Saving the modifications', 'info'); 313 | await this.app.vault.modify(file, updatedText); 314 | } 315 | } catch (e: unknown) { 316 | log('Failed to process the file', 'warn', e); 317 | } 318 | } 319 | 320 | async shouldFileBeIgnored(file: TFile): Promise { 321 | if (!file.path) { 322 | return true; 323 | } 324 | 325 | if (MARKDOWN_FILE_EXTENSION !== file.extension) { 326 | return true; 327 | } 328 | 329 | // Ignored Canvas files 330 | if (DEFAULT_CANVAS_FILE_NAME === file.name) { 331 | return true; 332 | } 333 | 334 | const fileContent = (await this.app.vault.read(file)).trim(); 335 | 336 | if (fileContent.length === 0) { 337 | return true; 338 | } 339 | 340 | if (isExcalidrawFile(file)) { 341 | return true; 342 | } 343 | 344 | // Make sure the file was not modified too recently (avoid update loops) 345 | if (this.nextPossibleUpdates.has(file.path)) { 346 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 347 | const nextPossibleUpdateForFile = this.nextPossibleUpdates.get( 348 | file.path 349 | )!; 350 | 351 | if (isBefore(file.stat.mtime, nextPossibleUpdateForFile)) { 352 | log('File has been updated recently. Ignoring', 'debug', file.path); 353 | return true; 354 | } else { 355 | log( 356 | 'File has not been updated recently. Processing', 357 | 'debug', 358 | file.path 359 | ); 360 | } 361 | } 362 | 363 | return this.settings.ignoredFolders.some((ignoredFolder) => { 364 | if (file.path.startsWith(ignoredFolder)) { 365 | //log(`Skipping because the file is part of an ignored folder: [${ignoredFolder}]`, 'debug'); 366 | return true; 367 | } else { 368 | return false; 369 | } 370 | }); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /apps/plugin/src/app/settingTab/args-search-and-remove.intf.ts: -------------------------------------------------------------------------------- 1 | export interface ArgsSearchAndRemove { 2 | name: string; 3 | description: string; 4 | currentList: string[]; 5 | setValue: (newValue: string[]) => Promise; 6 | } 7 | -------------------------------------------------------------------------------- /apps/plugin/src/app/settingTab/index.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, SearchComponent, Setting } from 'obsidian'; 2 | import { DataviewSerializerPlugin } from '../plugin'; 3 | import { Draft, produce } from 'immer'; 4 | import { PluginSettings } from '../types'; 5 | import { onlyUniqueArray } from '../utils/only-unique-array.tn'; 6 | import { FolderSuggest } from '../utils/folder-suggest'; 7 | import { ArgsSearchAndRemove } from './args-search-and-remove.intf'; 8 | 9 | export class SettingsTab extends PluginSettingTab { 10 | plugin: DataviewSerializerPlugin; 11 | 12 | constructor(app: App, plugin: DataviewSerializerPlugin) { 13 | super(app, plugin); 14 | this.plugin = plugin; 15 | } 16 | 17 | display(): void { 18 | const { containerEl } = this; 19 | 20 | containerEl.empty(); 21 | 22 | this.renderFoldersToScan(); 23 | this.renderFoldersToIgnore(); 24 | this.renderFollowButton(containerEl); 25 | this.renderSupportHeader(containerEl); 26 | } 27 | 28 | renderFollowButton(containerEl: HTMLElement) { 29 | new Setting(containerEl) 30 | .setName('Follow me on X') 31 | .setDesc('@dSebastien') 32 | .addButton((button) => { 33 | button.setCta(); 34 | button.setButtonText('Follow me on X').onClick(() => { 35 | window.open('https://x.com/dSebastien'); 36 | }); 37 | }); 38 | } 39 | 40 | renderSupportHeader(containerEl: HTMLElement) { 41 | new Setting(containerEl).setName('Support').setHeading(); 42 | 43 | const supportDesc = new DocumentFragment(); 44 | supportDesc.createDiv({ 45 | text: 'Buy me a coffee to support the development of this plugin ❤️', 46 | }); 47 | 48 | new Setting(containerEl).setDesc(supportDesc); 49 | 50 | this.renderBuyMeACoffeeBadge(containerEl); 51 | const spacing = containerEl.createDiv(); 52 | spacing.classList.add('support-header-margin'); 53 | } 54 | 55 | renderFoldersToScan(): void { 56 | this.doSearchAndRemoveList({ 57 | currentList: this.plugin.settings.foldersToScan, 58 | setValue: async (newValue) => { 59 | this.plugin.settings = produce( 60 | this.plugin.settings, 61 | (draft: Draft) => { 62 | draft.foldersToScan = newValue; 63 | } 64 | ); 65 | }, 66 | name: 'Folders to scan', 67 | description: 'Folders to scan when looking for queries to serialize.', 68 | }); 69 | } 70 | 71 | renderFoldersToIgnore(): void { 72 | this.doSearchAndRemoveList({ 73 | currentList: this.plugin.settings.ignoredFolders, 74 | setValue: async (newValue) => { 75 | this.plugin.settings = produce( 76 | this.plugin.settings, 77 | (draft: Draft) => { 78 | draft.ignoredFolders = newValue; 79 | } 80 | ); 81 | }, 82 | name: 'Folders to ignore', 83 | description: 'Folders to ignore when processing added/modified files.', 84 | }); 85 | } 86 | 87 | doSearchAndRemoveList({ 88 | currentList, 89 | setValue, 90 | description, 91 | name, 92 | }: ArgsSearchAndRemove) { 93 | let searchInput: SearchComponent | undefined; 94 | new Setting(this.containerEl) 95 | .setName(name) 96 | .setDesc(description) 97 | .addSearch((cb) => { 98 | searchInput = cb; 99 | new FolderSuggest(cb.inputEl, this.app); 100 | cb.setPlaceholder('Example: folder1/folder2'); 101 | }) 102 | .addButton((cb) => { 103 | cb.setIcon('plus'); 104 | cb.setTooltip('Add folder'); 105 | cb.onClick(async () => { 106 | if (!searchInput) { 107 | return; 108 | } 109 | const newFolder = searchInput.getValue(); 110 | 111 | await setValue([...currentList, newFolder].filter(onlyUniqueArray)); 112 | await this.plugin.saveSettings(); 113 | searchInput.setValue(''); 114 | this.display(); 115 | }); 116 | }); 117 | 118 | currentList.forEach((ignoreFolder) => 119 | new Setting(this.containerEl).setName(ignoreFolder).addButton((button) => 120 | button.setButtonText('Remove').onClick(async () => { 121 | await setValue(currentList.filter((value) => value !== ignoreFolder)); 122 | await this.plugin.saveSettings(); 123 | this.display(); 124 | }) 125 | ) 126 | ); 127 | } 128 | 129 | renderBuyMeACoffeeBadge( 130 | contentEl: HTMLElement | DocumentFragment, 131 | width = 175 132 | ) { 133 | const linkEl = contentEl.createEl('a', { 134 | href: 'https://www.buymeacoffee.com/dsebastien', 135 | }); 136 | const imgEl = linkEl.createEl('img'); 137 | imgEl.src = 138 | 'https://github.com/dsebastien/obsidian-plugin-template/raw/main/apps/plugin/src/assets/buy-me-a-coffee.png'; 139 | imgEl.alt = 'Buy me a coffee'; 140 | imgEl.width = width; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /apps/plugin/src/app/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface PluginSettings { 2 | foldersToScan: string[]; 3 | ignoredFolders: string[]; 4 | } 5 | 6 | export const DEFAULT_SETTINGS: PluginSettings = { 7 | foldersToScan: [], 8 | ignoredFolders: [], 9 | }; 10 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/escape-reg-exp.fn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensure the given string can be used safely as part of a RegExp 3 | * Reference: https://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript 4 | * @param text 5 | */ 6 | export function escapeRegExp(text: string) { 7 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 8 | } 9 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/find-queries.fn.ts: -------------------------------------------------------------------------------- 1 | import { QUERY_FLAG_CLOSE, QUERY_FLAG_OPEN } from '../constants'; 2 | import { isSupportedQueryType } from './is-supported-query-type.fn'; 3 | 4 | /** 5 | * Interface to represent a query with its indentation context 6 | */ 7 | export interface QueryWithContext { 8 | query: string; 9 | indentation: string; 10 | } 11 | 12 | /** 13 | * Detect the queries in the given string with their indentation context. 14 | * Ignores duplicates and ignores unsupported query types 15 | * @param text 16 | */ 17 | export const findQueries = (text: string): QueryWithContext[] => { 18 | const retVal: QueryWithContext[] = []; 19 | 20 | const lines: string[] = text.split('\n'); 21 | for (const line of lines) { 22 | const trimmedLine = line.trim(); 23 | if ( 24 | trimmedLine.includes(QUERY_FLAG_OPEN) && 25 | trimmedLine.includes(QUERY_FLAG_CLOSE) 26 | ) { 27 | // Extract the indentation (everything before the QUERY_FLAG_OPEN in the original line) 28 | const indentation = line.substring(0, line.indexOf(QUERY_FLAG_OPEN)); 29 | 30 | // Extract the query content between the flags 31 | const openFlagIndex = trimmedLine.indexOf(QUERY_FLAG_OPEN); 32 | const closeFlagIndex = trimmedLine.indexOf(QUERY_FLAG_CLOSE); 33 | const foundQuery = trimmedLine 34 | .substring(openFlagIndex + QUERY_FLAG_OPEN.length, closeFlagIndex) 35 | .trim(); 36 | 37 | // Ignore duplicates 38 | // Make sure it is a supported query 39 | if ( 40 | !retVal.some((item) => item.query === foundQuery) && 41 | isSupportedQueryType(foundQuery) 42 | ) { 43 | retVal.push({ query: foundQuery, indentation }); 44 | } 45 | } 46 | } 47 | 48 | return retVal; 49 | }; 50 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/folder-suggest.ts: -------------------------------------------------------------------------------- 1 | import { AbstractInputSuggest, App, TAbstractFile, TFolder } from 'obsidian'; 2 | 3 | export class FolderSuggest extends AbstractInputSuggest { 4 | constructor(private inputEl: HTMLInputElement, app: App) { 5 | super(app, inputEl); 6 | } 7 | 8 | /** 9 | * Return all vault folders 10 | * @param inputStr 11 | */ 12 | getSuggestions(inputStr: string): TFolder[] { 13 | const abstractFiles = this.app.vault.getAllLoadedFiles(); 14 | const folders: TFolder[] = []; 15 | const lowerCaseInputStr = inputStr.toLowerCase(); 16 | 17 | abstractFiles.forEach((folder: TAbstractFile) => { 18 | if ( 19 | folder instanceof TFolder && 20 | folder.path.toLowerCase().contains(lowerCaseInputStr) 21 | ) { 22 | folders.push(folder); 23 | } 24 | }); 25 | 26 | return folders; 27 | } 28 | 29 | renderSuggestion(folder: TFolder, el: HTMLElement): void { 30 | el.setText(folder.path); 31 | } 32 | 33 | selectSuggestion(folder: TFolder): void { 34 | this.inputEl.value = folder.path; 35 | this.close(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/is-excalidraw-file.fn.ts: -------------------------------------------------------------------------------- 1 | import { TFile } from 'obsidian'; 2 | 3 | /** 4 | * Check if the given TFile is an Excalidraw file 5 | * Taken from https://github.com/beaussan/update-time-on-edit-obsidian 6 | * @param file 7 | */ 8 | export const isExcalidrawFile = (file: TFile): boolean => { 9 | const ea = 10 | //@ts-expect-error this is coming from global context, injected by Excalidraw 11 | typeof ExcalidrawAutomate === 'undefined' 12 | ? undefined 13 | : //@ts-expect-error this is comming from global context, injected by Excalidraw 14 | ExcalidrawAutomate; //ea will be undefined if the Excalidraw plugin is not running 15 | return ea ? ea.isExcalidrawFile(file) : false; 16 | }; 17 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/is-supported-query-type.fn.ts: -------------------------------------------------------------------------------- 1 | import { SUPPORTED_QUERY_TYPES } from '../constants'; 2 | 3 | /** 4 | * Returns true if the query uses a supported type 5 | * @param query 6 | */ 7 | export const isSupportedQueryType = (query: string): boolean => { 8 | let retVal = false; 9 | 10 | const queryLower = query.toLowerCase(); 11 | 12 | for (const queryType of SUPPORTED_QUERY_TYPES) { 13 | if (queryLower.startsWith(queryType)) { 14 | retVal = true; 15 | } 16 | } 17 | 18 | return retVal; 19 | }; 20 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/is-table-query.fn.ts: -------------------------------------------------------------------------------- 1 | import { QUERY_TYPE_TABLE } from '../constants'; 2 | 3 | /** 4 | * Returns true if the query uses a supported type 5 | * @param query 6 | */ 7 | export const isTableQuery = (query: string): boolean => { 8 | let retVal = false; 9 | 10 | const queryLower = query.toLowerCase(); 11 | 12 | if (queryLower.startsWith(QUERY_TYPE_TABLE)) { 13 | retVal = true; 14 | } 15 | 16 | return retVal; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/log.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @nx/enforce-module-boundaries 2 | import * as pluginManifest from '../../../../../manifest.json'; 3 | 4 | export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; 5 | 6 | export const LOG_SEPARATOR = 7 | '--------------------------------------------------------'; 8 | export const LOG_PREFIX = `${pluginManifest.name}:`; 9 | 10 | /** 11 | * Log a message 12 | * @param message 13 | * @param level 14 | * @param data 15 | */ 16 | export const log = ( 17 | message: string, 18 | level?: LogLevel, 19 | ...data: unknown[] 20 | ): void => { 21 | const logMessage = `${LOG_PREFIX} ${message}`; 22 | switch (level) { 23 | case 'debug': 24 | console.debug(logMessage, data); 25 | break; 26 | case 'info': 27 | console.info(logMessage, data); 28 | break; 29 | case 'warn': 30 | console.warn(logMessage, data); 31 | break; 32 | case 'error': 33 | console.error(logMessage, data); 34 | break; 35 | default: 36 | console.log(logMessage, data); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/only-unique-array.tn.ts: -------------------------------------------------------------------------------- 1 | export const onlyUniqueArray = (value: T, index: number, self: T[]) => { 2 | return self.indexOf(value) === index; 3 | }; 4 | -------------------------------------------------------------------------------- /apps/plugin/src/app/utils/serialize-query.fn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Serialize the given query to Markdown 3 | * @param query 4 | */ 5 | import { DataviewApi } from 'obsidian-dataview/lib/api/plugin-api'; 6 | import { log } from './log'; 7 | import { App, TFile } from 'obsidian'; 8 | import path from 'path'; 9 | 10 | interface SerializeQueryParams { 11 | query: string; 12 | originFile: string; 13 | dataviewApi: DataviewApi; 14 | app: App; 15 | indentation?: string; 16 | } 17 | 18 | export const serializeQuery = async ( 19 | params: SerializeQueryParams 20 | ): Promise => { 21 | const allVaultFiles = app.vault.getFiles(); 22 | 23 | // Check if the name is unique. If it is, we will be able to replace the long path with just the note name. Aids 24 | // readability. 25 | function isNameUnique(name: string): boolean { 26 | const occurrences = allVaultFiles.filter((x: TFile) => x.name == name); 27 | return occurrences.length <= 1; 28 | } 29 | 30 | // Determine if the note name and alias are different 31 | function isValidAlias(name: string, alias: string): boolean { 32 | return path.parse(name).name !== alias; 33 | } 34 | 35 | let serializedQuery = ''; 36 | try { 37 | serializedQuery = await params.dataviewApi.tryQueryMarkdown( 38 | params.query, 39 | params.originFile 40 | ); 41 | // Reference: https://github.com/dsebastien/obsidian-dataview-serializer/issues/3 42 | 43 | if (params.query.toLocaleLowerCase().contains('table')) { 44 | serializedQuery = serializedQuery 45 | .replaceAll('\\\\', '\\') 46 | .replaceAll('\n<', '<'); 47 | 48 | // Set up to match the pattern 49 | // [[path to note\|alias]] - we are only interested in the path and \| that follow it 50 | const linkExp = new RegExp(/\[\[(.+?)\\\|(.+?)\]\]/g); 51 | 52 | // Returned links are delivered as the full path to the .md (or other filetype) file, aliased to the note name 53 | const matchedLinks = [...serializedQuery.matchAll(linkExp)]; 54 | for (const match of matchedLinks) { 55 | // Matched array 56 | // mathc[0]: Full matched string 57 | // match{1]: Matched group 1 = filepath 58 | // match[2]: Alias 59 | const name = path.basename(match[1]); 60 | const alias = match[2]; 61 | if (isNameUnique(name)) { 62 | // The name is unique, so ok to replace the path 63 | if (!isValidAlias(name, alias)) { 64 | // Name and alias match. Can replace the lot and leave what is the alias as the link 65 | serializedQuery = serializedQuery.replace(match[1] + '\\|', ''); 66 | } else { 67 | // Name and alias are different. Need to remove the path and keep the alias 68 | if (name.endsWith('.md')) { 69 | // For .md we can keep just the note name without extension 70 | serializedQuery = serializedQuery.replace( 71 | match[1] + '\\|', 72 | path.parse(name).name + '|' 73 | ); 74 | } else { 75 | // File types not .md need to keep full filename 76 | serializedQuery = serializedQuery.replace( 77 | match[1] + '\\|', 78 | name + '|' 79 | ); 80 | } 81 | } 82 | } else { 83 | // Name is not unique, keep the full path but remove the backslash from the pipe 84 | serializedQuery = serializedQuery.replace( 85 | match[1] + '\\|', 86 | match[1] + '|' 87 | ); 88 | } 89 | } 90 | } else { 91 | // Not a table. Assuming for now a list as that's all we're processing. 92 | // Set up to match the pattern 93 | // [[path to note\|alias]] - we are only interested in the path and \| that follow it 94 | const linkExp = new RegExp(/\[\[(.+?)\|.+?\]\]/g); 95 | 96 | // Returned links are delivered as the full path to the .md (or other filetype) file, aliased to the note name 97 | const matchedLinks = [...serializedQuery.matchAll(linkExp)]; 98 | for (const match of matchedLinks) { 99 | // Matched array 100 | // mathc[0]: Full matched string 101 | // match{1]: Matched group 1 = filepath 102 | // mathc[2]: alias 103 | console.log(serializedQuery); 104 | if (isNameUnique(path.basename(match[1]))) { 105 | serializedQuery = serializedQuery.replace(match[1] + '|', ''); 106 | } 107 | } 108 | } 109 | } catch (err: unknown) { 110 | log('Failed to serialize query', 'warn', err); 111 | } 112 | 113 | // Apply indentation if provided 114 | if (params.indentation && serializedQuery) { 115 | const lines = serializedQuery.split('\n'); 116 | const indentedLines = lines.map((line) => { 117 | return params.indentation + line; 118 | }); 119 | serializedQuery = indentedLines.join('\n'); 120 | } 121 | 122 | return serializedQuery; 123 | }; 124 | -------------------------------------------------------------------------------- /apps/plugin/src/assets/buy-me-a-coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/apps/plugin/src/assets/buy-me-a-coffee.png -------------------------------------------------------------------------------- /apps/plugin/src/assets/styles.css: -------------------------------------------------------------------------------- 1 | /* Plugin Styles */ 2 | .support-header-margin { 3 | margin-top: 0.75rem; 4 | } 5 | -------------------------------------------------------------------------------- /apps/plugin/src/main.ts: -------------------------------------------------------------------------------- 1 | import { DataviewSerializerPlugin } from './app/plugin'; 2 | 3 | // noinspection JSUnusedGlobalSymbols 4 | export default DataviewSerializerPlugin; 5 | -------------------------------------------------------------------------------- /apps/plugin/src/types/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/apps/plugin/src/types/.gitkeep -------------------------------------------------------------------------------- /apps/plugin/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"] 7 | }, 8 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"], 9 | "include": ["**/*.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/plugin/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Welcome to the official documentation of the Dataview Serializer plugin for Obsidian. 4 | 5 | This plugin gives you the power of [Dataview](https://github.com/blacksmithgu/obsidian-dataview), but generates Markdown. Thanks to this, the output of your queries is saved in the notes, and the links actually appear on the Graph, making it even more useful. 6 | 7 | Turning Dataview queries into Markdown also ensures that the generated content appears on Obsidian Publish websites, which is not the case with the Dataview plugin. 8 | 9 | ### Features 10 | 11 | Automatically serialize Dataview queries to Markdown. 12 | 13 | Currently, this plugin is only compatible with `LIST` and `TABLE` queries. `CALENDAR` and `TASK` queries are not supported. 14 | 15 | ### About 16 | 17 | This plugin is an [open source project](https://github.com/dsebastien/obsidian-dataview-serializer), created by [Sébastien Dubois](https://dsebastien.net/). 18 | 19 | [Sébastien](https://www.dsebastien.net/about/) is a Personal Knowledge Management expert, and [community leader](https://dsebastien.net/pkm-community). You can find his newsletter [here](https://newsletter.dsebastien.net/). Sébastien has also created the [Obsidian Starter Kit](https://obsidianstarterkit.com), a popular template for Obsidian, as well as various projects and [courses](https://knowledge-management-for-beginners.com/). 20 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | - [Overview](README.md) 4 | - [Usage](usage.md) 5 | - [Configuration](configuration.md) 6 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | In the settings of the plugin, you can configure: 4 | 5 | - **Folders to scan**: the folders that should be scanned when the "Scan and serialize all Dataview queries" command is executed 6 | - **Folders to ignore**: the folders that should be excluded when processing files 7 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | This plugin automatically serializes configured Dataview queries present in your notes. The queries will be serialized when you save the note (it happens at most once every 3 seconds). 4 | 5 | The queries need to be wrapped in a very specific structure for the plugin to recognize those ``. 6 | 7 | Those tags are HTML comments, which are supported by Markdown, and Obsidian. Those are only visible in the source view, meaning that they disappear in the Reading mode. Moreover, the "code" that is within HTML comments is not interpreted by Obsidian. This means that even if you use a tag within a query, it won't be seen by Obsidian as an actual tag. 8 | 9 | Here's an example query: 10 | 11 | ``` 12 | 13 | ``` 14 | 15 | If the above line is present in one of your notes, this plugin will detect it, and will replace it by the following: 16 | 17 | ``` 18 | 19 | 20 | - [[20 years from now, the only people who will remember that you worked late are your kids]] 21 | - [[A beautiful book is a victory won in all the battlefields of human thought.md|A beautiful book is a victory won in all the battlefields of human thought]] 22 | - [[A busy mind accelerates the perceived passage of time. Buy more time by cultivating peace of mind]] 23 | ... 24 | 25 | ``` 26 | 27 | As you can see above, the result of the query gets added as Markdown below the query. Notice that the serialized version is surrounded by `` and ``. Those allow the plugin to know what to replace. They should not be removed. 28 | 29 | Whenever you update that note, the query will be executed and serialized, replacing the previous serialized version. 30 | 31 | WARNING: For now, the queries can only be put on a single line. Take a look at [this issue](https://github.com/dsebastien/obsidian-dataview-serializer/issues/12) for details/updates. 32 | 33 | Note that a single note can include multiple queries. As soon as a file is modified, this plugin reads it and tries to locate queries to serialize. It starts by removing all the serialized queries, recognized by the ``line. Then, it serializes all the found queries to Markdown and saves the file again. 34 | 35 | There is a minimal delay between the executions of this plugin, to avoid issues with file synchronization systems. 36 | 37 | ### Commands 38 | 39 | #### Manual scan of all queries 40 | 41 | The plugin also includes a command you can use to scan and update all the Dataview queries to serialize in the folders to scan: Hit CTRL/CMD + P then type "Scan and serialize all Dataview queries" to invoke it. 42 | 43 | #### Add a new Dataview Serializer query 44 | 45 | The plugin includes a command called "Insert Dataview serializer block" that can be used to quickly add a new Dataview Serializer query to the current note. 46 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjectsAsync } from '@nx/jest'; 2 | 3 | export default async () => ({ 4 | projects: await getJestProjectsAsync(), 5 | }); 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/libs/.gitkeep -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dataview-serializer", 3 | "name": "Dataview Serializer", 4 | "description": "Serialize Dataview queries to Markdown, and keep the Markdown representation up to date.", 5 | "version": "1.8.0", 6 | "minAppVersion": "0.15.0", 7 | "isDesktopOnly": false, 8 | "author": "Sébastien Dubois", 9 | "authorUrl": "https://dsebastien.net", 10 | "fundingUrl": "https://www.buymeacoffee.com/dsebastien" 11 | } 12 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 5 | "production": [ 6 | "default", 7 | "!{projectRoot}/.eslintrc.json", 8 | "!{projectRoot}/eslint.config.js", 9 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 10 | "!{projectRoot}/tsconfig.spec.json", 11 | "!{projectRoot}/jest.config.[jt]s", 12 | "!{projectRoot}/src/test-setup.[jt]s", 13 | "!{projectRoot}/test-setup.[jt]s" 14 | ], 15 | "sharedGlobals": [] 16 | }, 17 | "targetDefaults": { 18 | "@nx/esbuild:esbuild": { 19 | "cache": true, 20 | "dependsOn": ["^build"], 21 | "inputs": ["production", "^production"] 22 | } 23 | }, 24 | "plugins": [ 25 | { 26 | "plugin": "@nx/eslint/plugin", 27 | "options": { 28 | "targetName": "lint" 29 | } 30 | }, 31 | { 32 | "plugin": "@nx/jest/plugin", 33 | "options": { 34 | "targetName": "test" 35 | } 36 | } 37 | ], 38 | "nxCloudAccessToken": "OWNmNjQ5YWUtMmRlYS00ZGM4LWI0NTAtNDMyOWE3MzBhZDc4fHJlYWQtd3JpdGU=" 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-dataview-serializer", 3 | "description": "Obsidian plugin that gives you the power of Dataview, but generates Markdown, making it compatible with Obsidian Publish, and making the links appear on the Graph", 4 | "version": "1.8.0", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Sébastien Dubois", 8 | "email": "sebastien@developassion.be", 9 | "url": "https://github.com/dsebastien/obsidian-dataview-serializer" 10 | }, 11 | "scripts": { 12 | "format": "nx format --all", 13 | "format:all": "npm run format", 14 | "lint": "nx lint plugin", 15 | "build": "npm run build:dev", 16 | "prepare:dist_folder": "npm run tsc && npx rimraf ./dist/apps/plugin && npx mkdirp ./dist/apps/plugin", 17 | "prebuild:dev": "npm run prepare:dist_folder", 18 | "build:dev": "nx build plugin && npm run copy:assets && npm run update:local_plugin", 19 | "update:local_plugin": "npx mkdirp ${OBSIDIAN_VAULT_LOCATION}/.obsidian/plugins/obsidian-dataview-serializer && cp -r ./dist/apps/plugin/* ${OBSIDIAN_VAULT_LOCATION}/.obsidian/plugins/obsidian-dataview-serializer/ && touch ${OBSIDIAN_VAULT_LOCATION}/.obsidian/plugins/obsidian-dataview-serializer/.hotreload", 20 | "prebuild:prod": "npm run prepare:dist_folder", 21 | "build:prod": "nx build plugin --prod && zip -r ./dist/apps/plugin/obsidian-dataview-serializer.zip ./dist/apps/plugin/", 22 | "postbuild:prod": "npm run version && npm run copy:assets", 23 | "prepare": "npx husky install", 24 | "copy:assets": "cp -r ./apps/plugin/src/assets/* ./dist/apps/plugin/ && cp ./manifest.json ./dist/apps/plugin/ && cp ./versions.json ./dist/apps/plugin/", 25 | "test": "nx affected:test --all --parallel --maxParallel 10", 26 | "test:watch": "npm run test -- --watch", 27 | "tsc": "tsc --noEmit --project ./apps/plugin/tsconfig.app.json", 28 | "tsc:watch": "npm run tsc -- --watch", 29 | "tscw": "npm run tsc:watch", 30 | "update": "nx migrate latest", 31 | "migrate": "npx nx migrate --run-migrations", 32 | "version": "node ./version-bump.mjs && npm run format && git add manifest.json versions.json", 33 | "watch": "nx watch --projects=plugin -- npm run build:dev" 34 | }, 35 | "private": true, 36 | "devDependencies": { 37 | "@nx/esbuild": "18.3.3", 38 | "@nx/eslint": "18.3.3", 39 | "@nx/eslint-plugin": "18.3.3", 40 | "@nx/jest": "18.3.3", 41 | "@nx/js": "18.3.3", 42 | "@nx/node": "18.3.3", 43 | "@nx/workspace": "18.3.3", 44 | "@swc-node/register": "~1.8.0", 45 | "@swc/core": "~1.3.85", 46 | "@swc/helpers": "~0.5.2", 47 | "@types/jest": "^29.4.0", 48 | "@types/node": "~18.16.9", 49 | "@typescript-eslint/eslint-plugin": "^7.3.0", 50 | "@typescript-eslint/parser": "^7.3.0", 51 | "esbuild": "^0.19.2", 52 | "eslint": "~8.57.0", 53 | "eslint-config-prettier": "^9.0.0", 54 | "husky": "9.0.11", 55 | "jest": "^29.4.1", 56 | "jest-environment-node": "^29.4.1", 57 | "lint-staged": "13.1.4", 58 | "nx": "18.3.3", 59 | "obsidian-dataview": "0.5.66", 60 | "prettier": "^2.6.2", 61 | "ts-jest": "^29.1.2", 62 | "ts-node": "10.9.1", 63 | "typescript": "~5.4.5" 64 | }, 65 | "dependencies": { 66 | "@popperjs/core": "2.11.8", 67 | "date-fns": "3.6.0", 68 | "immer": "10.0.4", 69 | "obsidian": "1.5.7-1", 70 | "tslib": "2.4.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tools/executors/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/tools/executors/.gitkeep -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsebastien/obsidian-dataview-serializer/025a1869cad969a932d554b9576971513e0fe315/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "allowSyntheticDefaultImports": true, 6 | "allowUnreachableCode": false, 7 | "allowUnusedLabels": false, 8 | "alwaysStrict": true, 9 | "baseUrl": ".", 10 | "declaration": false, 11 | "diagnostics": false, 12 | "emitBOM": false, 13 | "emitDecoratorMetadata": true, 14 | "esModuleInterop": true, 15 | "experimentalDecorators": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "importHelpers": true, 18 | "importsNotUsedAsValues": "remove", 19 | "inlineSourceMap": false, 20 | "inlineSources": false, 21 | "isolatedModules": false, 22 | "jsx": "preserve", 23 | "lib": ["es2022", "dom", "dom.iterable"], 24 | "listEmittedFiles": false, 25 | "listFiles": false, 26 | "module": "esnext", 27 | "moduleResolution": "node", 28 | "noEmit": true, 29 | "noEmitHelpers": false, 30 | "noEmitOnError": false, 31 | "noFallthroughCasesInSwitch": true, 32 | "noImplicitAny": true, 33 | "noImplicitReturns": true, 34 | "noImplicitThis": true, 35 | "noImplicitUseStrict": false, 36 | "noUnusedLocals": true, 37 | "noUnusedParameters": true, 38 | "outDir": "dist/out-tsc/dummy", 39 | "paths": { 40 | "*": ["@types/*"] 41 | }, 42 | "preserveConstEnums": true, 43 | "pretty": true, 44 | "removeComments": true, 45 | "resolveJsonModule": true, 46 | "rootDir": ".", 47 | "skipDefaultLibCheck": true, 48 | "skipLibCheck": true, 49 | "sourceMap": false, 50 | "strict": true, 51 | "strictBindCallApply": true, 52 | "strictFunctionTypes": true, 53 | "strictNullChecks": true, 54 | "strictPropertyInitialization": true, 55 | "stripInternal": true, 56 | "suppressExcessPropertyErrors": false, 57 | "suppressImplicitAnyIndexErrors": false, 58 | "typeRoots": ["node_modules/@types"], 59 | "types": ["node", "jest", "cypress"], 60 | "target": "es2015" 61 | }, 62 | "exclude": ["node_modules", "../tmp"] 63 | } 64 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs'; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | const pluginMetadataFolder = './'; 5 | const manifestFile = 'manifest.json'; 6 | const versionsFile = 'versions.json'; 7 | 8 | // read minAppVersion from manifest.json and bump version to target version 9 | console.log('Generating manifest'); 10 | let manifest = JSON.parse( 11 | readFileSync(`${pluginMetadataFolder}/${manifestFile}`, 'utf8') 12 | ); 13 | const { minAppVersion } = manifest; 14 | manifest.version = targetVersion; 15 | writeFileSync( 16 | `${pluginMetadataFolder}/${manifestFile}`, 17 | JSON.stringify(manifest, null, '\t') 18 | ); 19 | 20 | // update versions.json with target version and minAppVersion from manifest.json 21 | console.log('Updating versions file'); 22 | let versions = JSON.parse( 23 | readFileSync(`${pluginMetadataFolder}/${versionsFile}`, 'utf8') 24 | ); 25 | versions[targetVersion] = minAppVersion; 26 | 27 | writeFileSync( 28 | `${pluginMetadataFolder}/${versionsFile}`, 29 | JSON.stringify(versions, null, '\t') 30 | ); 31 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0", 3 | "1.0.1": "0.15.0", 4 | "1.0.2": "0.15.0", 5 | "1.0.3": "0.15.0", 6 | "1.0.4": "0.15.0", 7 | "1.0.5": "0.15.0", 8 | "1.1.0": "0.15.0", 9 | "1.1.1": "0.15.0", 10 | "1.1.2": "0.15.0", 11 | "1.1.3": "0.15.0", 12 | "1.1.4": "0.15.0", 13 | "1.1.5": "0.15.0", 14 | "1.2.0": "0.15.0", 15 | "1.3.0": "0.15.0", 16 | "1.4.0": "0.15.0", 17 | "1.4.1": "0.15.0", 18 | "1.5.0": "0.15.0", 19 | "1.5.1": "0.15.0", 20 | "1.6.0": "0.15.0", 21 | "1.7.0": "0.15.0", 22 | "1.7.1": "0.15.0", 23 | "1.7.2": "0.15.0", 24 | "1.7.3": "0.15.0", 25 | "1.8.0": "0.15.0" 26 | } 27 | --------------------------------------------------------------------------------