[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# editorconfig.org\n\nroot = true\n\n[*]\n\n# Change these settings to your own preference\nindent_style = space\nindent_size = 4\n\n# We recommend you to keep these unchanged\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.{yml,html,rb,css,xml,scss,json}]\nindent_size = 2\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yaml",
    "content": "name: Bug Report\ndescription: File a bug report\ntitle: \"🐛 \"\nlabels: [\"type: bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ## Before you start\n\n        - Check for plugin updates and make sure you're running the latest version of the plugin.\n        - Restart Obsidian and see if the issue persists.\n        - Look at existing bug reports to see if your issue has already been reported.\n        - Is this actually a bug? If this is something that you wish the plugin could do, please submit a feature request instead.\n\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected Behavior\n      description: What did you expect to happen?\n    validations:\n      required: true\n\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: Current behaviour\n      description: |\n        Describe what actually happened.\n\n        Screenshots or short recordings help, especially if the issue is visual.\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: Steps to reproduce\n      description: |\n        Which exact steps can a developer take to reproduce the issue?\n        The more detail you provide, the easier it will be to narrow down and fix the bug.\n      placeholder: |\n        1. Launch Obsidian Sandbox via the `Open Sandbox Vault` command.\n        2. Install the plugin.\n        3. Create a note with the above markdown snippet\n        4. ...\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: operating-systems\n    attributes:\n      label: Which Operating Systems are you using?\n      description: You may select more than one.\n      options:\n        - label: Android\n        - label: iPhone/iPad\n        - label: Linux\n        - label: macOS\n        - label: Windows\n\n  - type: input\n    id: obsidian-version\n    attributes:\n      label: Obsidian Version\n      description: Which Obsidian version are you using?\n      placeholder: 0.15.9\n    validations:\n      required: true\n\n  - type: input\n    id: plugin-version\n    attributes:\n      label: Plugin Version\n      description: Which plugin version are you using?\n      placeholder: 1.11.0\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: other-plugins-disabled\n    attributes:\n      label: Checks\n      description: Please confirm\n      options:\n        - label: I have tried it in the sandbox vault with only this plugin enabled\n          required: false\n\n  - type: textarea\n    id: possible-solution\n    attributes:\n      label: Possible solution\n      description: |\n        Not obligatory, but please suggest a fix or reason for the bug, if you have an idea.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yaml",
    "content": "name: Feature Request\ndescription: Request a new feature\ntitle: \"✨ \"\nlabels: [\"type: enhancement\"]\nbody:\n  - type: textarea\n    id: feature-description\n    validations:\n      required: true\n    attributes:\n      label: \"🔖 Feature description\"\n      description: |\n        A clear and concise description of what the feature request is.\n        Include your use case.\n\n        Examples:\n        - I find it difficult to [...].\n        - I would like to be able to [...]. This would help me [...].\n      placeholder: \"You should add ...\"\n\n  - type: textarea\n    id: solution\n    validations:\n      required: true\n    attributes:\n      label: \"✔️ Solution\"\n      description: \"A clear and concise description of what you want to happen.\"\n      placeholder: \"In my use-case, ...\"\n\n  - type: textarea\n    id: alternatives\n    validations:\n      required: false\n    attributes:\n      label: \"❓ Alternatives\"\n      description: \"A clear and concise description of any alternative solutions or features you've considered.\"\n      placeholder: \"I have considered ...\"\n\n  - type: textarea\n    id: additional-context\n    validations:\n      required: false\n    attributes:\n      label: \"📝 Additional Context\"\n      description: \"Add any other context or screenshots about the feature request here.\"\n      placeholder: \"...\"\n"
  },
  {
    "path": ".github/changelog.hbs",
    "content": "{{#each releases}}\n  {{#if href}}\n    ###{{#unless major}}#{{/unless}} [{{title}}]({{href}})\n  {{else}}\n    #### {{title}}\n  {{/if}}\n\n  {{#if tag}}\n    > {{niceDate}}\n  {{/if}}\n\n  {{#if summary}}\n    {{summary}}\n  {{/if}}\n\n  {{#if fixes}}\n  Fixes:\n  {{#each fixes}}\n    - {{#if commit.breaking}}**Breaking change:** {{/if}}{{commit.subject}} {{#each fixes}}#{{id}} {{/each}}\n  {{/each}}\n  {{/if}}\n\n  {{#if commits}}\n  Commits:\n  {{#each commits}}\n    - {{#if breaking}}**Breaking change:** {{/if}}{{subject}}{{#if href}} [`{{shorthash}}`]({{href}}){{/if}}\n  {{/each}}\n  {{/if}}\n\n  {{#if merges}}\n  PRs:\n  {{#each merges}}\n    - #{{id}} {{#if commit.breaking}}**Breaking change:** {{/if}}{{message}}\n  {{/each}}\n  {{/if}}\n\n{{/each}}\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n    cooldown:\n      default-days: 30\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    cooldown:\n      default-days: 30\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: CI Test\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ main ]\n    paths:\n      - '**.ts'\n      - '**.json'\n      - '**.scss'\n      - '**.css'\n  pull_request:\n    paths:\n      - '**.ts'\n      - '**.json'\n      - '**.scss'\n      - '**.css'\n\npermissions: read-all\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n    - name: Use Node.js\n      uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n      with:\n        node-version: '24'\n        cache: 'npm'\n\n    - name: Build and Test\n      id: build\n      run: |\n        npm ci\n        npm run build\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release Obsidian Plugin\n# Controls when the action will run. Workflow runs when manually triggered using the UI\n# or API.\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'New version or major, minor, patch'\n        default: 'patch'\n        required: true\n      update_manifest:\n        description: 'Update manifest.json'\n        default: true\n        required: true\n        type: boolean\n      update_brat:\n        description: 'Update brat manifest'\n        default: true\n        required: true\n        type: boolean\n\nenv:\n  GH_BOT_EMAIL: \"41898282+github-actions[bot]@users.noreply.github.com\"\n  GH_BOT_NAME: \"GitHub Action\"\n\njobs:\n  build:\n    permissions:\n      contents: write\n      id-token: write\n      attestations: write\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.build.outputs.version }}\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      # persist-credentials required: this step pushes commits and tags\n      # zizmor: ignore[artipacked]\n      with:\n        fetch-depth: 0 # otherwise, you will failed to push refs to dest repo\n\n    - name: Use Node.js\n      uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n      with:\n        node-version: '20'\n        cache: 'npm'\n\n    # Build the plugin\n    - name: Build and Tag\n      id: build\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        INPUT_VERSION: ${{ github.event.inputs.version }}\n        INPUT_UPDATE_MANIFEST: ${{ github.event.inputs.update_manifest }}\n        INPUT_UPDATE_BRAT: ${{ github.event.inputs.update_brat }}\n      run: |\n        echo \"version: $INPUT_VERSION\"\n        echo \"update_manifest: $INPUT_UPDATE_MANIFEST\"\n        echo \"update_brat: $INPUT_UPDATE_BRAT\"\n\n        git config user.name \"$GH_BOT_NAME\"\n        git config user.email \"$GH_BOT_EMAIL\"\n\n        npm ci\n        npm version \"$INPUT_VERSION\" --no-git-tag-version\n        VERSION=$(grep '^  \"version\"' package.json | cut -d'\"' -f4)\n        echo \"$VERSION\"\n\n        if git rev-parse \"refs/tags/$VERSION\" > /dev/null 2>&1; then\n          echo \"🛑 Tag $VERSION already exists. Delete the release and tag manually, then re-run.\"\n          exit 1\n        fi\n\n        if [ \"$INPUT_UPDATE_MANIFEST\" = \"true\" ]; then\n          # shellcheck disable=SC2086\n          sed -i 's|\\(version\":\\) \"[0-9\\.]*\"|\\1 \"'$VERSION'\"|' manifest.json\n        fi\n        if [ \"$INPUT_UPDATE_BRAT\" = \"true\" ]; then\n          # shellcheck disable=SC2086\n          sed -i 's|\\(version\":\\) \"[0-9\\.]*\"|\\1 \"'$VERSION'\"|' manifest-beta.json\n        fi\n\n        git add .\n        git status\n        git commit -m \"🔖 $VERSION\"\n        git push\n\n        git tag \"$VERSION\"\n        git push --tags\n\n        npm run brat-notes -- \"${VERSION}\"\n        echo \"version=${VERSION}\" >> \"$GITHUB_OUTPUT\"\n\n    # Package the required files into a zip\n    - name: Package\n      env:\n        REPO_NAME: ${{ github.event.repository.name }}\n        RELEASE_VERSION: ${{ steps.build.outputs.version }}\n      run: |\n        mv build \"$REPO_NAME\"\n        zip -r \"${REPO_NAME}-${RELEASE_VERSION}.zip\" \"$REPO_NAME\"\n        unzip -j \"${REPO_NAME}-${RELEASE_VERSION}.zip\" \\\n            \"$REPO_NAME/main.js\" \\\n            \"$REPO_NAME/styles.css\"\n\n    # Attest build provenance for release artifacts\n    - name: Attest build artifacts\n      uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26  # v4\n      with:\n        subject-path: |\n          ${{ github.event.repository.name }}-${{ steps.build.outputs.version }}.zip\n          ./main.js\n          ./styles.css\n\n    # Create the release on github\n    - name: Create Release\n      id: create_release\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        INPUT_UPDATE_MANIFEST: ${{ github.event.inputs.update_manifest }}\n        REPO_NAME: ${{ github.event.repository.name }}\n        RELEASE_VERSION: ${{ steps.build.outputs.version }}\n      run: |\n        prerelease=true\n        if [ \"$INPUT_UPDATE_MANIFEST\" = \"true\" ]; then\n            prerelease=false\n        fi\n\n        gh release create \"$RELEASE_VERSION\" \\\n            -F ./release-notes.md \\\n            --title \"$RELEASE_VERSION\" \\\n            --verify-tag \\\n            --prerelease=${prerelease} \\\n            \"${REPO_NAME}-${RELEASE_VERSION}.zip\" \\\n            './main.js' \\\n            './styles.css' \\\n            './manifest.json' \\\n            './manifest-beta.json'\n"
  },
  {
    "path": ".gitignore",
    "content": "# Intellij\n*.iml\n.idea\n\n# VS Code\n.vscode\n.claude\n\n# npm\nnode_modules\n.env\n\n# build\nbuild\n\n# obsidian\ndata.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### Changelog\n\nAll notable changes to this project will be documented in this file. Dates are displayed in UTC.\n\nGenerated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).\n\n#### [1.2.2](https://github.com/ebullient/obsidian-task-collector/compare/1.2.1...1.2.2)\n\n- 🔧 🎨 eslint updates + fixes [`992e6ca`](https://github.com/ebullient/obsidian-task-collector/commit/992e6caabd1a665e630b9592d3acfaa945a7030b)\n- Bump typescript from 5.9.3 to 6.0.2 [`c2d2949`](https://github.com/ebullient/obsidian-task-collector/commit/c2d294937764b34907c6ba18603c40dad632e860)\n- 🏷️ Use @types/moment [`2b34c24`](https://github.com/ebullient/obsidian-task-collector/commit/2b34c243d52e8fa6204176186a615465e7a77a05)\n- Bump @biomejs/biome from 2.4.8 to 2.4.9 [`a6fc813`](https://github.com/ebullient/obsidian-task-collector/commit/a6fc813f198c2b7397704f145e1fd3d02b4bddee)\n- Bump @typescript-eslint/parser from 8.57.1 to 8.57.2 [`c07feac`](https://github.com/ebullient/obsidian-task-collector/commit/c07feac9755f028b1c156d7a2993e2307689429c)\n- ⬆️ reconcile dependency updates [`b1f4093`](https://github.com/ebullient/obsidian-task-collector/commit/b1f40937321a809baa990228376b2174b59b2434)\n- Bump @types/moment from 2.11.29 to 2.13.0 [`af50f29`](https://github.com/ebullient/obsidian-task-collector/commit/af50f290a12d1edf8c311ec45c13ed5401307588)\n- Bump @codemirror/view from 6.38.6 to 6.40.0 [`222f50c`](https://github.com/ebullient/obsidian-task-collector/commit/222f50cb3b333103bfd0230e1aeee43479701f92)\n- 🎨 Minor nits; resolve additional lint issues [`171220d`](https://github.com/ebullient/obsidian-task-collector/commit/171220d5b6e6504d81fb5872451a9d46812507fa)\n\n#### [1.2.1](https://github.com/ebullient/obsidian-task-collector/compare/1.2.0...1.2.1)\n\n> 23 April 2026\n\n- 🎨 🔥 move plugin API; additional linting/cleanup [`89161a0`](https://github.com/ebullient/obsidian-task-collector/commit/89161a0d5be6cf96417a118507577adcd11421cd)\n- Bump @biomejs/biome from 2.4.7 to 2.4.8 [`4df9c3b`](https://github.com/ebullient/obsidian-task-collector/commit/4df9c3b2e7b130778ebc7f83279b9bcf64140fe0)\n\n#### [1.2.0](https://github.com/ebullient/obsidian-task-collector/compare/1.1.15...1.2.0)\n\n> 21 April 2026\n\n- 🎨 cleanup unneeded dependencies [`0061f7c`](https://github.com/ebullient/obsidian-task-collector/commit/0061f7c286042ff5bd6d6a5660ee12e1717196ca)\n- 🎨 lint fixes: popout awareness, remove old deps [`d784052`](https://github.com/ebullient/obsidian-task-collector/commit/d78405226b76de685a3ed421e9f64ca00610946a)\n- Bump @typescript-eslint/parser from 8.56.1 to 8.57.1 [`b55464c`](https://github.com/ebullient/obsidian-task-collector/commit/b55464cd68828ae8f2694f8a7182a2a219aad4d2)\n- Bump esbuild-sass-plugin from 3.6.0 to 3.7.0 [`196449a`](https://github.com/ebullient/obsidian-task-collector/commit/196449a3caf20e15c951c8db2feacbbf53affc6c)\n\n#### [1.1.15](https://github.com/ebullient/obsidian-task-collector/compare/1.1.14...1.1.15)\n\n> 15 April 2026\n\n- Bump jest from 30.2.0 to 30.3.0 [`f0e885d`](https://github.com/ebullient/obsidian-task-collector/commit/f0e885d6fb65a4e436084ad2eb6c457c013a98d6)\n- ➕ jest-util as direct dev dependency [`159a020`](https://github.com/ebullient/obsidian-task-collector/commit/159a020d27d1b2b2ab399dd0cfa9d4142d1706c4)\n- Bump jest-environment-jsdom from 30.2.0 to 30.3.0 [`4eaaa9b`](https://github.com/ebullient/obsidian-task-collector/commit/4eaaa9b54cd69c569c5a68d34951f88f58cb10c3)\n- Bump esbuild from 0.27.3 to 0.27.4 [`7aa90d3`](https://github.com/ebullient/obsidian-task-collector/commit/7aa90d334142ac6e0728839c663eee1fb2725864)\n- Bump @typescript-eslint/parser from 8.56.0 to 8.56.1 [`29c1cbe`](https://github.com/ebullient/obsidian-task-collector/commit/29c1cbe56ebb2cf4046a542cafcb5be426ccd5b3)\n- Bump @biomejs/biome from 2.4.6 to 2.4.7 [`f1d234e`](https://github.com/ebullient/obsidian-task-collector/commit/f1d234ec5fbdddcdb00345da72a3b43d3d3d10c1)\n- Bump @biomejs/biome from 2.3.15 to 2.4.4 [`a370762`](https://github.com/ebullient/obsidian-task-collector/commit/a37076236be493dc4d6a37fbcf27f3876f8f1251)\n- Bump @biomejs/biome from 2.3.15 to 2.4.4 [`b1a12df`](https://github.com/ebullient/obsidian-task-collector/commit/b1a12df061ee6f4c5b80702ab81e2ac78b4f1451)\n- ⬆️ bump dependencies; audit [`11fe2b5`](https://github.com/ebullient/obsidian-task-collector/commit/11fe2b5fa98c7dfd720c5108663e3fb88513881a)\n- Bump @types/node from 25.3.5 to 25.5.0 [`38059af`](https://github.com/ebullient/obsidian-task-collector/commit/38059af695198d5d2902d34a8f44a50ff7ece2bf)\n- Bump @types/node from 25.3.0 to 25.3.2 [`9578091`](https://github.com/ebullient/obsidian-task-collector/commit/9578091ad1d86de62d1f0466c7123961c9e62d41)\n- Bump obsidian from 1.12.2 to 1.12.3 [`4287435`](https://github.com/ebullient/obsidian-task-collector/commit/4287435baf00b8627dbacbf0d2461705851da39c)\n- Bump @types/node from 25.3.0 to 25.3.2 [`01dac7b`](https://github.com/ebullient/obsidian-task-collector/commit/01dac7bea8451c6d0547c5ebce4e65eb40a32643)\n- Bump flatted in the npm_and_yarn group across 1 directory [`117e341`](https://github.com/ebullient/obsidian-task-collector/commit/117e341b663f5a630bb1180aaf6a1abcc11b406d)\n- Bump actions/setup-node from 6.2.0 to 6.3.0 [`3425f21`](https://github.com/ebullient/obsidian-task-collector/commit/3425f217945b151cbca5ddfecd652a9c6a0c4200)\n\n#### [1.1.14](https://github.com/ebullient/obsidian-task-collector/compare/1.1.13...1.1.14)\n\n> 15 March 2026\n\n#### [1.1.13](https://github.com/ebullient/obsidian-task-collector/compare/1.1.12...1.1.13)\n\n> 15 March 2026\n\n- Bump @biomejs/biome from 2.3.12 to 2.3.14 [`d7e9caf`](https://github.com/ebullient/obsidian-task-collector/commit/d7e9caf825a778d348a5acaad02502a1d9830beb)\n- Bump @biomejs/biome from 2.3.12 to 2.3.14 [`1fae6fa`](https://github.com/ebullient/obsidian-task-collector/commit/1fae6fa383d9b5bd4a128ad6c7dccdc295a4068d)\n- Bump @biomejs/biome from 2.3.11 to 2.3.12 [`8d825cf`](https://github.com/ebullient/obsidian-task-collector/commit/8d825cfa5cf3fa06b6e6e03484bd7a3257b56abc)\n- 🐛 empty lines converted when option set [`8bbcfbe`](https://github.com/ebullient/obsidian-task-collector/commit/8bbcfbe04a3b00ebf26c7eb9c5ca00c224da6713)\n- ⬆️ update minimatch + flatted [`bcfe67a`](https://github.com/ebullient/obsidian-task-collector/commit/bcfe67a61863aa2df7417a2fd7dadbdfff3e66d5)\n- Bump immutable in the npm_and_yarn group across 1 directory [`342e841`](https://github.com/ebullient/obsidian-task-collector/commit/342e8411443833a228fe42633ab471171d60b03a)\n\n#### [1.1.12](https://github.com/ebullient/obsidian-task-collector/compare/1.1.11...1.1.12)\n\n> 20 February 2026\n\n- ⬆️ override glob, minimatch ajv [`bfa5731`](https://github.com/ebullient/obsidian-task-collector/commit/bfa573185a9f3f26e2f320a20b168435a36e8130)\n- ⬆️ dependency bump [`d00ab9d`](https://github.com/ebullient/obsidian-task-collector/commit/d00ab9d4d2f170a5b81db2ac86551acc68bd7f60)\n- Bump esbuild-sass-plugin from 3.3.1 to 3.6.0 [`df2e9b8`](https://github.com/ebullient/obsidian-task-collector/commit/df2e9b8d5958e49c2b43d937b5fa2c3ba10bdf6c)\n- Bump esbuild from 0.25.12 to 0.27.0 [`f1ed266`](https://github.com/ebullient/obsidian-task-collector/commit/f1ed26639315eceaed2a850472efd325e18a83f2)\n- Bump @typescript-eslint/parser from 8.51.0 to 8.52.0 [`8c6dc67`](https://github.com/ebullient/obsidian-task-collector/commit/8c6dc671c6320d0f433b47a28b888e66abef8481)\n- Bump @biomejs/biome from 2.3.8 to 2.3.11 [`394b96a`](https://github.com/ebullient/obsidian-task-collector/commit/394b96ac6f09ba7205e990d60dc0581bca8048dc)\n- Bump @biomejs/biome from 2.3.6 to 2.3.8 [`e4971a1`](https://github.com/ebullient/obsidian-task-collector/commit/e4971a132c93f1b7d7d8f12170c49c43863ef637)\n- Bump @biomejs/biome from 2.3.4 to 2.3.5 [`e5ebef4`](https://github.com/ebullient/obsidian-task-collector/commit/e5ebef465cec79d7f67c0e7dd546046c4373b037)\n- Bump @biomejs/biome from 2.3.4 to 2.3.5 [`2cc6c46`](https://github.com/ebullient/obsidian-task-collector/commit/2cc6c46a0cdef89cae2d2c5818aa9e29801ba908)\n- Bump @biomejs/biome from 2.3.2 to 2.3.4 [`1a4f2f2`](https://github.com/ebullient/obsidian-task-collector/commit/1a4f2f2a552e7cd2ebce6472fb2554bee1cd2911)\n- Bump @types/node from 24.10.4 to 25.0.1 [`2e7a3c2`](https://github.com/ebullient/obsidian-task-collector/commit/2e7a3c266ef0e0aa72286eb6ff9357b353dc90db)\n- Bump obsidian from 1.10.2 to 1.10.3 [`869c4b2`](https://github.com/ebullient/obsidian-task-collector/commit/869c4b2a092c4d7f9593fbdd51ab52d69f87b2d0)\n- Bump @types/node from 25.0.1 to 25.0.5 [`bd49006`](https://github.com/ebullient/obsidian-task-collector/commit/bd490060a589f8f484d5939741881b3259db7c8f)\n- Bump obsidian from 1.11.0 to 1.11.4 [`a689efa`](https://github.com/ebullient/obsidian-task-collector/commit/a689efa656f00aaae20b7d6b4291ae471c02ce33)\n- Bump @types/node from 24.10.0 to 24.10.1 [`6a390b7`](https://github.com/ebullient/obsidian-task-collector/commit/6a390b7136361cda7d0bd3a7811a62e6b43676c5)\n- Bump actions/setup-node from 6.1.0 to 6.2.0 [`20324c3`](https://github.com/ebullient/obsidian-task-collector/commit/20324c312beb6f40b08d689ad9592e2ff221613e)\n- Bump actions/checkout from 6.0.1 to 6.0.2 [`5d14739`](https://github.com/ebullient/obsidian-task-collector/commit/5d147395b24018e88572541e2444b848968205db)\n- Bump actions/setup-node from 6.0.0 to 6.1.0 [`55097a5`](https://github.com/ebullient/obsidian-task-collector/commit/55097a5a9cbd2277869c9a557e5edc45bf0de06a)\n- Bump actions/checkout from 5.0.0 to 6.0.1 [`08b9b71`](https://github.com/ebullient/obsidian-task-collector/commit/08b9b71476eb48e91a652770337f9fe5fffd9035)\n- Bump @codemirror/language from `a9c3c7e` to `2d416d7` [`746e71b`](https://github.com/ebullient/obsidian-task-collector/commit/746e71baf308d8d6b4fefbcfacb0faeabf301964)\n- 🎨 settings icon [`dfc7dd3`](https://github.com/ebullient/obsidian-task-collector/commit/dfc7dd326f8b9d1d3e85265cf4cc4184ac506eb7)\n\n#### [1.1.11](https://github.com/ebullient/obsidian-task-collector/compare/1.1.10...1.1.11)\n\n> 2 December 2025\n\n- ⚰️ ⬆️  remove unnecessary cache methods [`d59c45e`](https://github.com/ebullient/obsidian-task-collector/commit/d59c45eab25a7fa098c98748808869598f36919c)\n- 🔧 eslint for obsidian plugins [`566c9c0`](https://github.com/ebullient/obsidian-task-collector/commit/566c9c0f76e74ecb856a7645747abd835bbda460)\n- Bump jest from 30.0.2 to 30.0.5 [`7a79cab`](https://github.com/ebullient/obsidian-task-collector/commit/7a79cabc35c30762d64756514c9ec156145e0d8f)\n- ➖ remove direct codemirror dependencies [`e658f42`](https://github.com/ebullient/obsidian-task-collector/commit/e658f42753fdb8e5254b8e4a9182c7b69be1f3ad)\n- Bump jest from 30.0.5 to 30.1.3 [`4d6454c`](https://github.com/ebullient/obsidian-task-collector/commit/4d6454cd4ee45b3be678f3e50bd366556ea6e47b)\n- ⬆️ dependency bump [`cc9f98d`](https://github.com/ebullient/obsidian-task-collector/commit/cc9f98d28484e17f6f944f0ed81a7ad80bf2fd5e)\n- ⬆️ dependency bump [`b4adbdb`](https://github.com/ebullient/obsidian-task-collector/commit/b4adbdb410d179d4c775a186bc4052e96c8303eb)\n- Bump esbuild from 0.25.9 to 0.25.10 [`fc10c3a`](https://github.com/ebullient/obsidian-task-collector/commit/fc10c3a47e0e2d33d72a74cfa79e8621bcd3af4b)\n- Bump esbuild from 0.25.8 to 0.25.9 [`bd8b277`](https://github.com/ebullient/obsidian-task-collector/commit/bd8b277673c510469333fe30b509d00856eeac06)\n- ✨ regex test setting (when regex defined) [`1dc22a6`](https://github.com/ebullient/obsidian-task-collector/commit/1dc22a6a90d573aa82b60e02da61bdba4b15d0ae)\n- Bump jest-environment-jsdom from 30.0.5 to 30.1.2 [`8f50d49`](https://github.com/ebullient/obsidian-task-collector/commit/8f50d49b305d5b965b46a4beeae39141bd77e6c2)\n- dependency bump [`d2c171b`](https://github.com/ebullient/obsidian-task-collector/commit/d2c171bf88500b4bfef89e6baa47c881428fd387)\n- Bump @biomejs/biome from 2.2.4 to 2.2.6 [`722cc04`](https://github.com/ebullient/obsidian-task-collector/commit/722cc040a359178151f478968133d8cee9530120)\n- Bump @biomejs/biome from 2.2.4 to 2.3.2 [`e92e966`](https://github.com/ebullient/obsidian-task-collector/commit/e92e966c4003864af0f31f64a8de62106ac32f5f)\n- Bump @biomejs/biome from 2.2.3 to 2.2.4 [`bb2fe1c`](https://github.com/ebullient/obsidian-task-collector/commit/bb2fe1cc98f3dbaa7382488aa0afc2910fbc5724)\n- Bump @biomejs/biome from 2.2.2 to 2.2.3 [`c756bcd`](https://github.com/ebullient/obsidian-task-collector/commit/c756bcd707b3a13e1642c07a44bd7b17c1dabd88)\n- Bump @biomejs/biome from 2.2.0 to 2.2.2 [`5c7f1de`](https://github.com/ebullient/obsidian-task-collector/commit/5c7f1de52c1ffb8fac65f224ed09304c6a253caa)\n- Bump @biomejs/biome from 2.1.4 to 2.2.0 [`a440c76`](https://github.com/ebullient/obsidian-task-collector/commit/a440c76834ed0a591a595d917f094cfca0e7e19e)\n- Bump @biomejs/biome from 2.1.3 to 2.1.4 [`6c286b0`](https://github.com/ebullient/obsidian-task-collector/commit/6c286b043a23c490743352d2b05da28c056dc2d8)\n- Bump @biomejs/biome from 2.1.2 to 2.1.3 [`aab78e6`](https://github.com/ebullient/obsidian-task-collector/commit/aab78e6e08637e51969b5b10ba5df4ec120e6ed9)\n- Bump @biomejs/biome from 2.0.5 to 2.1.2 [`84c9f2b`](https://github.com/ebullient/obsidian-task-collector/commit/84c9f2b68b8ba721c5570f3b4cd86634807dcc98)\n- Bump @types/codemirror from 5.60.16 to 5.60.17 [`ab000ea`](https://github.com/ebullient/obsidian-task-collector/commit/ab000ea22264f8490d9cee8dc3b40d5029bab22f)\n- Resolve package dependencies [`131027b`](https://github.com/ebullient/obsidian-task-collector/commit/131027bed6a372698e2a3eeea93b29034aab2d03)\n- Bump js-yaml in the npm_and_yarn group across 1 directory [`c51fea3`](https://github.com/ebullient/obsidian-task-collector/commit/c51fea31e37344d02840f06e9da16a3df67ad41f)\n- 🧪 Add test for more complicated regular expression [`b96338f`](https://github.com/ebullient/obsidian-task-collector/commit/b96338f409b312461696e94799285d865e1fe033)\n- Bump @types/node from 24.3.3 to 24.6.2 [`5a8d5c0`](https://github.com/ebullient/obsidian-task-collector/commit/5a8d5c064bdcfa534c2b923c9e224fc417a5eb6b)\n- Bump @types/node from 24.1.0 to 24.2.0 [`badcb1f`](https://github.com/ebullient/obsidian-task-collector/commit/badcb1f8605c90b36ac66606417e67c2404692a5)\n- 👷 CI consistency [`e28cbb0`](https://github.com/ebullient/obsidian-task-collector/commit/e28cbb08c91efe2517358e446941c275866359d6)\n- 📌 pin dependencies; apply cooldown [`65c9a3d`](https://github.com/ebullient/obsidian-task-collector/commit/65c9a3de4695bb2b775b61ae0dd316474945d961)\n- Bump @codemirror/view from 6.37.2 to 6.38.0 [`dd83bbb`](https://github.com/ebullient/obsidian-task-collector/commit/dd83bbba36014bf2e96499c23e21e1d95ac26544)\n- Bump typescript from 5.9.2 to 5.9.3 [`c1672ad`](https://github.com/ebullient/obsidian-task-collector/commit/c1672ad54e2bdfe658c3f5dd1ac1a92073ca9c4e)\n- Bump ts-jest from 29.4.1 to 29.4.4 [`a530d3f`](https://github.com/ebullient/obsidian-task-collector/commit/a530d3fc6444df6d74a2ff22237d186fa93416f2)\n- Bump @types/node from 24.3.1 to 24.3.3 [`44b9416`](https://github.com/ebullient/obsidian-task-collector/commit/44b94167ecf50f1c7ac41ab6671d65906db7d238)\n- Bump @types/node from 24.3.0 to 24.3.1 [`6167aee`](https://github.com/ebullient/obsidian-task-collector/commit/6167aeeb6b0550e914501dfc608efefa021e3cc4)\n- Bump @codemirror/view from 6.38.1 to 6.38.2 [`dcdec6f`](https://github.com/ebullient/obsidian-task-collector/commit/dcdec6fe3a1c43286f3e5624a04d016666b3475f)\n- Bump dotenv from 17.2.1 to 17.2.2 [`aeaaa7c`](https://github.com/ebullient/obsidian-task-collector/commit/aeaaa7cb7c429cbf420b786f5528400f9dfed001)\n- Bump typescript from 5.8.3 to 5.9.2 [`6734e20`](https://github.com/ebullient/obsidian-task-collector/commit/6734e2076d0a458aa527b398eeacbb5e79fe3a34)\n- Bump @types/node from 24.0.4 to 24.1.0 [`6ddc5c0`](https://github.com/ebullient/obsidian-task-collector/commit/6ddc5c028dc3ebdab8e49f093c83f51d07e37560)\n- Bump dotenv from 16.5.0 to 17.2.1 [`6f62ebd`](https://github.com/ebullient/obsidian-task-collector/commit/6f62ebd30f4b5061f36a0587d0078dc605d8508b)\n- ⚒️ updates due to version updates [`e9594f5`](https://github.com/ebullient/obsidian-task-collector/commit/e9594f5ad85e87e4940469fbd7ed38b0d456673a)\n- Bump js-yaml in the npm_and_yarn group across 1 directory [`80e5696`](https://github.com/ebullient/obsidian-task-collector/commit/80e5696a5021f9cce2d6f409823240560afb93d8)\n- Bump actions/setup-node from 5.0.0 to 6.0.0 [`4546207`](https://github.com/ebullient/obsidian-task-collector/commit/454620700aec35570b34eb65560878f740a81756)\n- Bump actions/checkout from 4.3.0 to 5.0.0 [`04fc1da`](https://github.com/ebullient/obsidian-task-collector/commit/04fc1da98c8b7a496a4cd3eaf8b30123616f2c8a)\n- Bump @types/node from 24.2.1 to 24.3.0 [`d619ac6`](https://github.com/ebullient/obsidian-task-collector/commit/d619ac6449283f4a8bf9836928fcf4d5aa8b3085)\n- Bump @types/node from 24.2.0 to 24.2.1 [`3117ffc`](https://github.com/ebullient/obsidian-task-collector/commit/3117ffcc9920b8f87a301fe75720a9e3eb01f495)\n- 📌 Update minimum obsidian version [`e4da0a9`](https://github.com/ebullient/obsidian-task-collector/commit/e4da0a98b6df79f36633be2d7217464b962bfca2)\n- Bump actions/setup-node from 4.4.0 to 5.0.0 [`ca15a3c`](https://github.com/ebullient/obsidian-task-collector/commit/ca15a3cacd1359f92068521873950fa133e22937)\n- Bump actions/checkout from 4.3.0 to 5.0.0 [`c3bb951`](https://github.com/ebullient/obsidian-task-collector/commit/c3bb95128fb1ec16e947b68f16bd1b0e52796612)\n- Bump ts-jest from 29.4.0 to 29.4.1 [`8b833bd`](https://github.com/ebullient/obsidian-task-collector/commit/8b833bda4da4ce44c65f5bb9de287ee140c8e80a)\n- Bump @types/node from 24.0.4 to 24.1.0 [`9288f5a`](https://github.com/ebullient/obsidian-task-collector/commit/9288f5acd0949e7c8a349d23d8588e71099edece)\n\n#### [1.1.10](https://github.com/ebullient/obsidian-task-collector/compare/1.1.9...1.1.10)\n\n> 26 June 2025\n\n- 🐛 Support + for unlinked lists; Resolves [`#657`](https://github.com/ebullient/obsidian-task-collector/issues/657)\n- Bump @types/node from 22.15.30 to 24.0.2 [`f7bfaf9`](https://github.com/ebullient/obsidian-task-collector/commit/f7bfaf92fbaf4f446db0d8bf1e75ebab1c6bee6b)\n- Bump jest and @types/jest [`3f6a0e9`](https://github.com/ebullient/obsidian-task-collector/commit/3f6a0e9174d5f695ab0319ea8da1130058f364a2)\n- ⬆️ dependency bump [`95322fa`](https://github.com/ebullient/obsidian-task-collector/commit/95322fa69543a4e35124d55f7aeaa39d792cae20)\n- Bump jest from 30.0.0 to 30.0.2 [`8ac23c3`](https://github.com/ebullient/obsidian-task-collector/commit/8ac23c3beb8d567faad8bf7ddbf67d66845edd78)\n- 🩹 biome formatting [`0bf9f27`](https://github.com/ebullient/obsidian-task-collector/commit/0bf9f27d884f4cb04dff3bbf286493f272f003f0)\n- Bump jest-environment-jsdom from 29.7.0 to 30.0.0 [`5f7a367`](https://github.com/ebullient/obsidian-task-collector/commit/5f7a367fab14fd815261c8d31a44ad1045c03d50)\n- Bump jest-environment-jsdom from 30.0.0 to 30.0.2 [`ab94b6b`](https://github.com/ebullient/obsidian-task-collector/commit/ab94b6b10a81cc15401e901cdc84d761e39fe414)\n- 🩹 biome upgrade [`9ecef24`](https://github.com/ebullient/obsidian-task-collector/commit/9ecef249519e28158e1a1d2368b3960fe14ab778)\n- Bump @biomejs/biome from 1.9.4 to 2.0.4 [`958091f`](https://github.com/ebullient/obsidian-task-collector/commit/958091fbec805ae1cf453d18ce41200213818f8a)\n- Bump @biomejs/biome from 1.9.4 to 2.0.4 [`f9e41d3`](https://github.com/ebullient/obsidian-task-collector/commit/f9e41d32811dcae55287823a8fcd0abde6fcc343)\n- Bump @codemirror/view from 6.37.1 to 6.37.2 [`b198c1a`](https://github.com/ebullient/obsidian-task-collector/commit/b198c1ac3dbd693276f4ab58fac5ac91dd94b68e)\n- Bump ts-jest from 29.3.4 to 29.4.0 [`d27271b`](https://github.com/ebullient/obsidian-task-collector/commit/d27271b943e65c9490ce4282b3974bee97d855ff)\n\n#### [1.1.9](https://github.com/ebullient/obsidian-task-collector/compare/1.1.8...1.1.9)\n\n> 6 June 2025\n\n- ⬆️ dependency bump [`b269a35`](https://github.com/ebullient/obsidian-task-collector/commit/b269a35bf5ceedde6d0d9bc285b549830614722d)\n- Bump esbuild from 0.25.4 to 0.25.5 [`d4414cb`](https://github.com/ebullient/obsidian-task-collector/commit/d4414cb6de3f24ee3eb4bc20b8ca15f6e07367aa)\n- Bump @codemirror/view from 6.36.8 to 6.37.1 [`6920ff7`](https://github.com/ebullient/obsidian-task-collector/commit/6920ff71341fd20ab8da31fbfff2e40dc218e0a1)\n- Bump @types/node from 22.15.21 to 22.15.29 [`77c3f68`](https://github.com/ebullient/obsidian-task-collector/commit/77c3f680025bd20921be7fbf4f0b023b94355689)\n- Bump @types/codemirror from 5.60.15 to 5.60.16 [`ee49f2c`](https://github.com/ebullient/obsidian-task-collector/commit/ee49f2cb40cabe8febf3446827f4d42ab2838903)\n\n#### [1.1.8](https://github.com/ebullient/obsidian-task-collector/compare/1.1.7...1.1.8)\n\n> 23 May 2025\n\n- 🐛 Ensure checkbox handlers are unique; fixes [`#641`](https://github.com/ebullient/obsidian-task-collector/issues/641)\n- ⬆️ dependency bump [`56f438f`](https://github.com/ebullient/obsidian-task-collector/commit/56f438ff27b4eddb55d841cde57600e1bc346ff3)\n- Bump esbuild from 0.25.3 to 0.25.4 [`f358589`](https://github.com/ebullient/obsidian-task-collector/commit/f358589dcc0c5b42550eacef1d6b9b4e015a6e38)\n- Bump esbuild from 0.25.2 to 0.25.3 [`5098148`](https://github.com/ebullient/obsidian-task-collector/commit/50981485fd72b5dd8ad92797c5f1ea408dcc58dc)\n- Bump esbuild from 0.25.1 to 0.25.2 [`51d4c3f`](https://github.com/ebullient/obsidian-task-collector/commit/51d4c3f574087a95faf81d2ba3d9dcdc8597a867)\n- Bump ts-jest from 29.3.2 to 29.3.4 [`1d8d60d`](https://github.com/ebullient/obsidian-task-collector/commit/1d8d60d290a84ff029be1229540b411decb75a63)\n- Bump ts-jest from 29.2.6 to 29.3.0 [`09842cd`](https://github.com/ebullient/obsidian-task-collector/commit/09842cd0b168c0753835eb218bafa30c6ab1dda7)\n- Bump ts-jest from 29.3.1 to 29.3.2 [`ef55b1f`](https://github.com/ebullient/obsidian-task-collector/commit/ef55b1f278518d5aa723b84e2bcc1e97d810d14b)\n- Bump @types/node from 22.13.14 to 22.14.1 [`dfb8d92`](https://github.com/ebullient/obsidian-task-collector/commit/dfb8d92980851afa611e1f0a8223a698f1139d30)\n- Bump ts-jest from 29.2.6 to 29.3.0 [`922bfba`](https://github.com/ebullient/obsidian-task-collector/commit/922bfba20bd7ed6380aba785e72085d2875f6195)\n- Bump @types/node from 22.15.3 to 22.15.19 [`78671a0`](https://github.com/ebullient/obsidian-task-collector/commit/78671a05da4f6ad1cf62fa445a0dba9b38355f54)\n- Bump @codemirror/view from 6.36.7 to 6.36.8 [`b66d066`](https://github.com/ebullient/obsidian-task-collector/commit/b66d0661cbdb6866b10c5943a36c6e0c8cbbf5e6)\n- Bump @types/node from 22.15.3 to 22.15.19 [`ef7568a`](https://github.com/ebullient/obsidian-task-collector/commit/ef7568a7ed81a755fe6f2025137dbc337749018f)\n- Bump @codemirror/view from 6.36.6 to 6.36.7 [`f61636b`](https://github.com/ebullient/obsidian-task-collector/commit/f61636b48e2a1ec7663bc8473d9c125162d36be8)\n- Bump @types/node from 22.14.1 to 22.15.3 [`db5f52e`](https://github.com/ebullient/obsidian-task-collector/commit/db5f52ecd1332fd0fd441c53edcbd1074b643e32)\n- Bump @codemirror/view from 6.36.5 to 6.36.6 [`6dd3c43`](https://github.com/ebullient/obsidian-task-collector/commit/6dd3c430914d1b39fb7a44afb1cef594c5952768)\n- Bump dotenv from 16.4.7 to 16.5.0 [`7e17a6b`](https://github.com/ebullient/obsidian-task-collector/commit/7e17a6bff276cfe958030b034ba5cde64bf93f29)\n- Bump typescript from 5.8.2 to 5.8.3 [`c5b5687`](https://github.com/ebullient/obsidian-task-collector/commit/c5b568704b214006ff9e70b98a5c60b2519b380f)\n- Bump @codemirror/view from 6.36.4 to 6.36.5 [`c57fdcd`](https://github.com/ebullient/obsidian-task-collector/commit/c57fdcdb06e1c7ea86c8e7ef170f1ec408010f14)\n- Bump @types/node from 22.13.10 to 22.13.14 [`c3816c7`](https://github.com/ebullient/obsidian-task-collector/commit/c3816c7d8d9871366920ed8dda5205759afd9b4c)\n- Bump builtin-modules from 4.0.0 to 5.0.0 [`8a3fe97`](https://github.com/ebullient/obsidian-task-collector/commit/8a3fe97d2ab12d15ef643f3df56ea0af0c572908)\n- Bump typescript from 5.7.3 to 5.8.2 [`fea9878`](https://github.com/ebullient/obsidian-task-collector/commit/fea9878b9f82fae45db7199e13f28634a7c2bd4e)\n- 🩹 nit fixes for esbuild [`dc34c89`](https://github.com/ebullient/obsidian-task-collector/commit/dc34c899feffde206c3a1f104e87e0b7b101367d)\n\n#### [1.1.7](https://github.com/ebullient/obsidian-task-collector/compare/1.1.6...1.1.7)\n\n> 25 February 2025\n\n- ✨ Always add reset all command. Resolves [`#611`](https://github.com/ebullient/obsidian-task-collector/issues/611)\n- ⬆️ bump dependencies [`f72cb31`](https://github.com/ebullient/obsidian-task-collector/commit/f72cb319bc54aa3d53d8a273c89bf28403a92b4c)\n- Bump esbuild from 0.24.0 to 0.24.2 [`7034d38`](https://github.com/ebullient/obsidian-task-collector/commit/7034d38afd4b92371d18adc633cdb5fa27582e0f)\n- Bump esbuild from 0.24.2 to 0.25.0 [`06e2ca3`](https://github.com/ebullient/obsidian-task-collector/commit/06e2ca379dacfc56176a0ac087e53d502151223b)\n- Bump ts-jest from 29.2.5 to 29.2.6 [`41dbfa6`](https://github.com/ebullient/obsidian-task-collector/commit/41dbfa6f67b36d78adb56e5611f4b73ef5a8cb22)\n- Bump @codemirror/view from 6.36.2 to 6.36.3 [`6ff0037`](https://github.com/ebullient/obsidian-task-collector/commit/6ff0037acb273e11204ddce4501befbe3227bcbf)\n- Bump @codemirror/state from 6.5.1 to 6.5.2 [`f3b71e0`](https://github.com/ebullient/obsidian-task-collector/commit/f3b71e07b9d19b86d0a25ae165c0d5c6edccd4bb)\n- Bump @types/node from 22.10.7 to 22.10.10 [`7dd01ff`](https://github.com/ebullient/obsidian-task-collector/commit/7dd01ff2263cbf8180cfd107decb82c1843537af)\n- Bump @codemirror/state from 6.5.0 to 6.5.1 [`1b7622e`](https://github.com/ebullient/obsidian-task-collector/commit/1b7622ece4ae1b2caa92806933d1f25b7fcc94f5)\n- Bump typescript from 5.7.2 to 5.7.3 [`295062c`](https://github.com/ebullient/obsidian-task-collector/commit/295062c802945d7c4c8f7e36853cb101bd905077)\n- Bump @codemirror/view from 6.35.3 to 6.36.1 [`e42114a`](https://github.com/ebullient/obsidian-task-collector/commit/e42114a095387c452649001b60ec896a63ca08dc)\n- Bump obsidian from 1.7.2 to 1.8.7 [`bc54d7f`](https://github.com/ebullient/obsidian-task-collector/commit/bc54d7f5eb26214c6ef182ba4af625f341479425)\n- Bump @types/node from 22.13.4 to 22.13.5 [`4a2ca21`](https://github.com/ebullient/obsidian-task-collector/commit/4a2ca21c21d2e26dc00be3c94ef8a1d1f9985098)\n- Bump @types/node from 22.13.1 to 22.13.4 [`18138b7`](https://github.com/ebullient/obsidian-task-collector/commit/18138b795b1da04d61ea3ea6b155dfe8dc36d805)\n- Bump @types/node from 22.13.0 to 22.13.1 [`3921f0a`](https://github.com/ebullient/obsidian-task-collector/commit/3921f0aba8aab9e282c3e99775e17e4e7153b693)\n- Bump @types/node from 22.10.10 to 22.13.0 [`f670717`](https://github.com/ebullient/obsidian-task-collector/commit/f670717577bc616762a5d5b0d9f4146dbef99ec2)\n- Bump @types/node from 22.10.5 to 22.10.7 [`4837af0`](https://github.com/ebullient/obsidian-task-collector/commit/4837af0ea41fd7eb701dc17565bc99e8f47d995e)\n- Bump @codemirror/view from 6.36.1 to 6.36.2 [`9881a27`](https://github.com/ebullient/obsidian-task-collector/commit/9881a27702ac0a16d1ccd91ab78b67110f244b15)\n- Bump @types/node from 22.10.2 to 22.10.5 [`aecdedf`](https://github.com/ebullient/obsidian-task-collector/commit/aecdedff38d4ea73d7595dd2e7f76f1334f130cf)\n\n#### [1.1.6](https://github.com/ebullient/obsidian-task-collector/compare/1.1.5...1.1.6)\n\n> 19 December 2024\n\n- ♻️ eslint/prettier -&gt; biome [`b866018`](https://github.com/ebullient/obsidian-task-collector/commit/b8660180c067ea90c7bdbea9b5a71d58317c3284)\n- ⬆️ wrap-up bump [`b36f549`](https://github.com/ebullient/obsidian-task-collector/commit/b36f549a577d023be47d3288808a7e9ae3c7769e)\n- Bump @eslint/js from 9.13.0 to 9.14.0 [`7325cdb`](https://github.com/ebullient/obsidian-task-collector/commit/7325cdba9857b24c6f3e5691bda505e03697d0b0)\n- Bump @eslint/js from 9.14.0 to 9.15.0 [`b0b2219`](https://github.com/ebullient/obsidian-task-collector/commit/b0b2219283606c051980d363ac93eae0d0185220)\n- Bump eslint from 9.13.0 to 9.14.0 [`37407f3`](https://github.com/ebullient/obsidian-task-collector/commit/37407f3f997771a03f9d9ae83f1a0f15d4cea336)\n- Bump eslint from 9.14.0 to 9.15.0 [`cc02e95`](https://github.com/ebullient/obsidian-task-collector/commit/cc02e9504b3750308aecfab36502c028feb6b5f8)\n- Bump tslib from 2.8.0 to 2.8.1 [`9d9c66b`](https://github.com/ebullient/obsidian-task-collector/commit/9d9c66b0dcb32173752005e6c31230af6b4556eb)\n- Bump @types/node from 22.10.1 to 22.10.2 [`770a6de`](https://github.com/ebullient/obsidian-task-collector/commit/770a6de85c6779ef8ecf39539e34399d3f662c6c)\n- Bump @types/node from 22.9.1 to 22.9.3 [`62328b8`](https://github.com/ebullient/obsidian-task-collector/commit/62328b8fd4888bf4456639054cff14a367cad234)\n- Bump typescript from 5.6.3 to 5.7.2 [`875f8f1`](https://github.com/ebullient/obsidian-task-collector/commit/875f8f1292c47a6d996ed324ce81a3013d62910c)\n- Bump @eslint/eslintrc from 3.1.0 to 3.2.0 [`b8a0d99`](https://github.com/ebullient/obsidian-task-collector/commit/b8a0d99d7d098951df949ffed0ffe0240fb8d8f1)\n- Bump @types/node from 22.8.2 to 22.8.7 [`f6440b7`](https://github.com/ebullient/obsidian-task-collector/commit/f6440b743e623ce7fd05e18ba22026280518623d)\n- 💸 org-level funding [`f44c8e3`](https://github.com/ebullient/obsidian-task-collector/commit/f44c8e3243679b424422fc174e16a8e4abc556ec)\n\n#### [1.1.5](https://github.com/ebullient/obsidian-task-collector/compare/1.1.4...1.1.5)\n\n> 29 October 2024\n\n- ⬆️ Resolve updated dependencies [`ff3a01c`](https://github.com/ebullient/obsidian-task-collector/commit/ff3a01cb3439eff9d11539697b1e76b00b1c5769)\n- Bump @types/node from 22.7.7 to 22.8.1 [`6a3ebfb`](https://github.com/ebullient/obsidian-task-collector/commit/6a3ebfb6ea97af362b37208ca3c44fa2bfafaa34)\n- MIT License [`e3b06d1`](https://github.com/ebullient/obsidian-task-collector/commit/e3b06d16f7ca9270ad840786129b460b3e9052a4)\n- ⬆️ fix eslint. See #557 [`e7b1987`](https://github.com/ebullient/obsidian-task-collector/commit/e7b1987398003964d3f16aa56f2814fa0a786c4a)\n- Bump @typescript-eslint/eslint-plugin from 8.10.0 to 8.11.0 [`382a51c`](https://github.com/ebullient/obsidian-task-collector/commit/382a51cae74bf38f5893603a0e95a611e769b25c)\n- Bump esbuild from 0.23.1 to 0.24.0 [`8421869`](https://github.com/ebullient/obsidian-task-collector/commit/84218690a7ca944c7fc70da5c118eba7654e738e)\n- Bump @typescript-eslint/eslint-plugin from 8.8.0 to 8.8.1 [`85144b3`](https://github.com/ebullient/obsidian-task-collector/commit/85144b33ca7cd7a4ec9e716bcf7ad050ed42f77a)\n- Bump @typescript-eslint/eslint-plugin from 8.6.0 to 8.8.0 [`a348f0e`](https://github.com/ebullient/obsidian-task-collector/commit/a348f0e0e8ef66d8bd2c50c67b531e83a58e82e5)\n- Bump @typescript-eslint/eslint-plugin from 8.3.0 to 8.4.0 [`301a5ff`](https://github.com/ebullient/obsidian-task-collector/commit/301a5ffeaa9325d4c4ee6ba1b040c433f06c9652)\n- Bump @typescript-eslint/parser from 8.6.0 to 8.8.0 [`d196a32`](https://github.com/ebullient/obsidian-task-collector/commit/d196a326484133d28d42372200683bd9bb28b50b)\n- Bump @typescript-eslint/eslint-plugin from 8.4.0 to 8.6.0 [`ec62381`](https://github.com/ebullient/obsidian-task-collector/commit/ec62381fae252be3a00a5d31f5eb1dde505ff591)\n- Bump @typescript-eslint/eslint-plugin from 8.8.1 to 8.10.0 [`15158b9`](https://github.com/ebullient/obsidian-task-collector/commit/15158b9173f11e24f112e16ee42997343f3a9d37)\n- Bump @typescript-eslint/parser from 8.4.0 to 8.6.0 [`54a1b3b`](https://github.com/ebullient/obsidian-task-collector/commit/54a1b3bdfded09d92a7f333786cc39d01a5f6b5d)\n- Bump eslint from 9.12.0 to 9.13.0 [`3dbbfbb`](https://github.com/ebullient/obsidian-task-collector/commit/3dbbfbb1f9f720a0b0d56eb257317e7cfd161633)\n- 🩹 fix issue template [`f058bc6`](https://github.com/ebullient/obsidian-task-collector/commit/f058bc660286d77949b89667afdc0373af2a2294)\n- Bump @eslint/js from 9.12.0 to 9.13.0 [`94e9c9e`](https://github.com/ebullient/obsidian-task-collector/commit/94e9c9e564234c02899f06792dd072c289aea458)\n- Bump @types/jest from 29.5.13 to 29.5.14 [`128d873`](https://github.com/ebullient/obsidian-task-collector/commit/128d8736af78dce039cf4cd5b117d947584ff818)\n- Bump obsidian from 1.6.6 to 1.7.2 [`6b0c29d`](https://github.com/ebullient/obsidian-task-collector/commit/6b0c29dcae30cbee4ae62f2bb10a1c2ad0e32fe2)\n- Bump typescript from 5.5.4 to 5.6.2 [`8ad87fb`](https://github.com/ebullient/obsidian-task-collector/commit/8ad87fb656ab3185a5d0025c6bb5b0f80f04c8ad)\n- Bump @types/jest from 29.5.12 to 29.5.13 [`80a9ee7`](https://github.com/ebullient/obsidian-task-collector/commit/80a9ee724fed033d71acce38ce2c499c0461f33d)\n- Bump tslib from 2.7.0 to 2.8.0 [`8ea0ad2`](https://github.com/ebullient/obsidian-task-collector/commit/8ea0ad2ae9d9e3de666f83b05ceb7de4fbb86899)\n- Bump @types/node from 22.7.5 to 22.7.7 [`747820b`](https://github.com/ebullient/obsidian-task-collector/commit/747820b5889ccfe7bb99e4d701e4d1949113f4a8)\n- Bump typescript from 5.6.2 to 5.6.3 [`1ab59b5`](https://github.com/ebullient/obsidian-task-collector/commit/1ab59b5dfc4fdde6cf476864ebb61c0efa456e54)\n- Bump @types/node from 22.7.4 to 22.7.5 [`eae1b56`](https://github.com/ebullient/obsidian-task-collector/commit/eae1b56559469e4cf38b20975279d35db6b3fcdb)\n- Bump @codemirror/view from 6.33.0 to 6.34.1 [`40923e5`](https://github.com/ebullient/obsidian-task-collector/commit/40923e55fd4711fd976e946deffd290de54b5629)\n- Bump @types/node from 22.5.5 to 22.7.4 [`2d78736`](https://github.com/ebullient/obsidian-task-collector/commit/2d787368e9e83b54c2d435e41de57c0c260862dd)\n- Bump @types/node from 22.5.4 to 22.5.5 [`45551fc`](https://github.com/ebullient/obsidian-task-collector/commit/45551fca79214e7f67f6d7eceb1db166b3a1bc57)\n- Bump @types/node from 22.5.2 to 22.5.4 [`c09d5a1`](https://github.com/ebullient/obsidian-task-collector/commit/c09d5a1ed72cde78f0af5ef6a2550e76e02683ed)\n\n#### [1.1.4](https://github.com/ebullient/obsidian-task-collector/compare/1.1.3...1.1.4)\n\n> 4 September 2024\n\n- Bump @typescript-eslint/eslint-plugin from 8.2.0 to 8.3.0 [`7c6f55c`](https://github.com/ebullient/obsidian-task-collector/commit/7c6f55c419e6d5e0313789d006330ade7b77fd6d)\n- Bump @typescript-eslint/parser from 8.2.0 to 8.4.0 [`0feb45b`](https://github.com/ebullient/obsidian-task-collector/commit/0feb45b27f28e3a7a1bb5ddfe9d8ed55a374b91f)\n- ✨ support checking tasks in embedded sections [`a0b40b6`](https://github.com/ebullient/obsidian-task-collector/commit/a0b40b6bd3b9340ea95d90db4a82648403673635)\n- Bump @types/node from 22.5.0 to 22.5.2 [`7578441`](https://github.com/ebullient/obsidian-task-collector/commit/7578441ff6960225c91b4ee26d8224bc54e2a0e4)\n\n#### [1.1.3](https://github.com/ebullient/obsidian-task-collector/compare/1.1.2...1.1.3)\n\n> 29 August 2024\n\n- ⬆️ bump eslint [`6c53709`](https://github.com/ebullient/obsidian-task-collector/commit/6c537099ed851d8ab1e15d3ef09fcd4e2d0c3194)\n- 🐛 modal supports notes embedded in canvas [`d9821cb`](https://github.com/ebullient/obsidian-task-collector/commit/d9821cb51a7eb1573436a4f05786d0d9952db5de)\n- Bump ts-jest from 29.2.4 to 29.2.5 [`1ecfbb3`](https://github.com/ebullient/obsidian-task-collector/commit/1ecfbb3fe6a6a16b59044ecbac53b2347b09e75d)\n- Bump tslib from 2.6.3 to 2.7.0 [`c6e96bf`](https://github.com/ebullient/obsidian-task-collector/commit/c6e96bfd454bd720eb4ab116645d2600bbd6cda6)\n- Bump @codemirror/view from 6.32.0 to 6.33.0 [`fbbebfe`](https://github.com/ebullient/obsidian-task-collector/commit/fbbebfeeb07c434a83845311f011604863563e41)\n- Bump @types/node from 22.4.0 to 22.4.1 [`e840e03`](https://github.com/ebullient/obsidian-task-collector/commit/e840e03ac0e96921373b60407dac1fd4f3a5a980)\n\n#### [1.1.2](https://github.com/ebullient/obsidian-task-collector/compare/1.1.1...1.1.2)\n\n> 19 August 2024\n\n- ⬆️ bump eslint plugin [`7fb8179`](https://github.com/ebullient/obsidian-task-collector/commit/7fb817921b31d2ba6b06bc12281d51907f93265e)\n- Bump @typescript-eslint/eslint-plugin from 7.16.1 to 7.17.0 [`6ad3cb6`](https://github.com/ebullient/obsidian-task-collector/commit/6ad3cb6c0f43a7e1bc2db03e8d86526d6f799632)\n- Bump @typescript-eslint/parser from 7.16.1 to 7.17.0 [`c17ca23`](https://github.com/ebullient/obsidian-task-collector/commit/c17ca238f1480fa08015f3e32f44cbe717d422df)\n- Bump @typescript-eslint/parser from 7.17.0 to 7.18.0 [`7485675`](https://github.com/ebullient/obsidian-task-collector/commit/7485675413e82f94650bde69fc479cef0343c567)\n- Bump @types/node from 20.14.11 to 22.0.0 [`4c05384`](https://github.com/ebullient/obsidian-task-collector/commit/4c05384a8721aa06a8aa5ee7553a5932a511c4e0)\n- 🔇 re-align with debug setting [`7c7cf27`](https://github.com/ebullient/obsidian-task-collector/commit/7c7cf27a28bffea911f3d4947bf49e0d3af7cbbd)\n- Bump @types/node from 22.0.0 to 22.1.0 [`f894f8b`](https://github.com/ebullient/obsidian-task-collector/commit/f894f8bbc5f648755e35f8c4c4cc61d6e3036f94)\n- Bump ts-jest from 29.2.2 to 29.2.3 [`3d783ad`](https://github.com/ebullient/obsidian-task-collector/commit/3d783ad1e01f995ae33d2cb1fe5b5bf4f976180a)\n- Bump typescript from 5.5.3 to 5.5.4 [`90dd9aa`](https://github.com/ebullient/obsidian-task-collector/commit/90dd9aa8437370339136bc5b5f07bcd621fa01cf)\n- Bump @codemirror/view from 6.28.4 to 6.28.6 [`7b5c6b0`](https://github.com/ebullient/obsidian-task-collector/commit/7b5c6b065de02374e7b4e0602a0ef48813c5db43)\n- Bump @types/node from 20.14.10 to 20.14.11 [`78dd141`](https://github.com/ebullient/obsidian-task-collector/commit/78dd141cd534998f0b747cfc0d93ca27d4269793)\n- Bump @codemirror/view from 6.30.0 to 6.32.0 [`903d894`](https://github.com/ebullient/obsidian-task-collector/commit/903d89466e1a3cc9f688f90ef635b8240fb73f2e)\n- Bump @types/node from 22.1.0 to 22.2.0 [`823c5ad`](https://github.com/ebullient/obsidian-task-collector/commit/823c5ad4771c729f688cc7cecca068b3cc6122da)\n- Bump @codemirror/view from 6.29.1 to 6.30.0 [`2e6e559`](https://github.com/ebullient/obsidian-task-collector/commit/2e6e559817e81fad426c32db8db75f7daf5abffe)\n- Bump ts-jest from 29.2.3 to 29.2.4 [`f01ca0a`](https://github.com/ebullient/obsidian-task-collector/commit/f01ca0adde3a801b7ea88e6c1d33edc393a39ca3)\n- Bump @codemirror/view from 6.28.6 to 6.29.1 [`c9fa6eb`](https://github.com/ebullient/obsidian-task-collector/commit/c9fa6eb8173673456a83d0847a488e88b0a9bc50)\n- 🐛 Fix build issues [`da41212`](https://github.com/ebullient/obsidian-task-collector/commit/da41212e6e189e787ca6d4e3bcb76cc34ffe98e0)\n\n#### [1.1.1](https://github.com/ebullient/obsidian-task-collector/compare/1.1.0...1.1.1)\n\n> 16 July 2024\n\n- ✨ allow task toggle in mark cycle; resolves [`#456`](https://github.com/ebullient/obsidian-task-collector/issues/456)\n- ⬆️ upgrade dependencies [`a76f791`](https://github.com/ebullient/obsidian-task-collector/commit/a76f791bc9904394b08acf4172f1148667ceea79)\n- Bump builtin-modules from 3.3.0 to 4.0.0 [`5563822`](https://github.com/ebullient/obsidian-task-collector/commit/556382296bd7ad5c792bf66dc641b86d74ed67b9)\n- ⬆️ workflow to node 20 [`edd9712`](https://github.com/ebullient/obsidian-task-collector/commit/edd97123af546cdf335cfa9c10d64dc3562025ea)\n\n#### [1.1.0](https://github.com/ebullient/obsidian-task-collector/compare/1.0.17...1.1.0)\n\n> 16 July 2024\n\n- 🐛 Do not bind to metadata checkbox; fixes [`#435`](https://github.com/ebullient/obsidian-task-collector/issues/435)\n- ⬆️ upgrade vitest [`7763a89`](https://github.com/ebullient/obsidian-task-collector/commit/7763a898369eeae8c6a59782c9a73e61c0b61ad5)\n- Bump esbuild from 0.21.5 to 0.23.0 [`c642571`](https://github.com/ebullient/obsidian-task-collector/commit/c64257134cee0d0dcaadac0aaf260c55ec542248)\n- Bump @typescript-eslint/eslint-plugin from 7.8.0 to 7.9.0 [`1e515b4`](https://github.com/ebullient/obsidian-task-collector/commit/1e515b45c24333df264a53a493866fe317e3ace9)\n- Bump @typescript-eslint/eslint-plugin from 7.10.0 to 7.11.0 [`d10bb19`](https://github.com/ebullient/obsidian-task-collector/commit/d10bb19410a60406fcf646a317f0c503068e95d9)\n- Bump @typescript-eslint/eslint-plugin from 7.9.0 to 7.10.0 [`8863c42`](https://github.com/ebullient/obsidian-task-collector/commit/8863c4296b377e64a87ee681ee11784b05e9df23)\n- Bump @typescript-eslint/eslint-plugin from 7.7.1 to 7.8.0 [`9e5882f`](https://github.com/ebullient/obsidian-task-collector/commit/9e5882f548021f7fff2953760b640f0a9e7db14c)\n- Bump @typescript-eslint/eslint-plugin from 7.7.0 to 7.7.1 [`86bf26e`](https://github.com/ebullient/obsidian-task-collector/commit/86bf26ea5578dc931897074627026fc7788ce46d)\n- Bump @typescript-eslint/eslint-plugin from 7.3.1 to 7.4.0 [`450d603`](https://github.com/ebullient/obsidian-task-collector/commit/450d603101a9155b38f3c706395138892f6be1f5)\n- Bump @typescript-eslint/parser from 7.7.1 to 7.8.0 [`f1605fd`](https://github.com/ebullient/obsidian-task-collector/commit/f1605fdf6e1043a48ef9e2c58cb6e2caa9595d56)\n- Bump esbuild from 0.21.4 to 0.21.5 [`747c963`](https://github.com/ebullient/obsidian-task-collector/commit/747c963622c8ae514b194a8b776190a5da6cae18)\n- Bump esbuild from 0.20.2 to 0.21.4 [`11d9fbe`](https://github.com/ebullient/obsidian-task-collector/commit/11d9fbec0c6ae439b7dc8a9efe5ad85987a0d652)\n- Bump @typescript-eslint/eslint-plugin from 7.4.0 to 7.6.0 [`eca7863`](https://github.com/ebullient/obsidian-task-collector/commit/eca7863ff8ffd77e6ae14da1ed094699b01eeed9)\n- 📝 issue templates [`9370432`](https://github.com/ebullient/obsidian-task-collector/commit/9370432834bc0acb913a067083a353d0096d4865)\n- Bump @typescript-eslint/eslint-plugin from 7.13.1 to 7.15.0 [`b735172`](https://github.com/ebullient/obsidian-task-collector/commit/b73517244d9c78063c186dd2c68aa5b90d77a964)\n- Bump @typescript-eslint/eslint-plugin from 7.11.0 to 7.13.1 [`7b5e2e3`](https://github.com/ebullient/obsidian-task-collector/commit/7b5e2e3a87fc3cd94b7e57640352a86e978784c6)\n- Bump @typescript-eslint/eslint-plugin from 7.6.0 to 7.7.0 [`b42ddbf`](https://github.com/ebullient/obsidian-task-collector/commit/b42ddbfceb59aae9e411b90cbb39ad3fb6b6c3eb)\n- Bump @typescript-eslint/parser from 7.6.0 to 7.7.0 [`df1e928`](https://github.com/ebullient/obsidian-task-collector/commit/df1e928bfdac7436a8f226de4139784720649895)\n- Bump ts-jest from 29.2.0 to 29.2.2 [`bc713b6`](https://github.com/ebullient/obsidian-task-collector/commit/bc713b67bacc0f67cc8b8e58facc58b2803557ec)\n- Bump @typescript-eslint/parser from 7.3.1 to 7.6.0 [`f1b6922`](https://github.com/ebullient/obsidian-task-collector/commit/f1b692223c27735e5ee28173734ded38ad6367c1)\n- Bump @typescript-eslint/parser from 7.10.0 to 7.12.0 [`aec3bac`](https://github.com/ebullient/obsidian-task-collector/commit/aec3bacb65b77b64e2c32071232fce05e7a78268)\n- --- [`5e20eb2`](https://github.com/ebullient/obsidian-task-collector/commit/5e20eb2a9d1e624e012994f0d90caa679297635f)\n- Bump @typescript-eslint/parser from 7.7.0 to 7.7.1 [`ce5a487`](https://github.com/ebullient/obsidian-task-collector/commit/ce5a487df35d23d9e5a55c2172080f1f9a6140a1)\n- Bump esbuild-sass-plugin from 3.3.0 to 3.3.1 [`8974f0e`](https://github.com/ebullient/obsidian-task-collector/commit/8974f0e1d2e5ae826f89d69a229683ca93bcacf3)\n- Bump ts-jest from 29.1.2 to 29.1.3 [`42252b4`](https://github.com/ebullient/obsidian-task-collector/commit/42252b484e56f7dc2e8eb2cb6e580fefa363d1c5)\n- Bump braces from 3.0.2 to 3.0.3 in the npm_and_yarn group [`b1c592c`](https://github.com/ebullient/obsidian-task-collector/commit/b1c592cf3b58a3ac4e76a36dac8e0a07264b5781)\n- ✅ test for asterisk task list; see #491 [`5cd2d62`](https://github.com/ebullient/obsidian-task-collector/commit/5cd2d624df7a49ab2a03f3cb182301b3778838de)\n- Bump esbuild-sass-plugin from 3.2.0 to 3.3.0 [`815ac76`](https://github.com/ebullient/obsidian-task-collector/commit/815ac76655ad53d996d83dfdedc41a6da7011c94)\n- Bump typescript from 5.5.2 to 5.5.3 [`b8734e3`](https://github.com/ebullient/obsidian-task-collector/commit/b8734e3c0c184812397a4e24f66db706481e4266)\n- Bump prettier from 3.3.2 to 3.3.3 [`185e09e`](https://github.com/ebullient/obsidian-task-collector/commit/185e09e8b92e396460040654d83d6ef1c650b8bf)\n- Bump @types/node from 20.14.2 to 20.14.10 [`501462c`](https://github.com/ebullient/obsidian-task-collector/commit/501462c0b46d65b49cb9a5e2a4fe95518a02bef1)\n- Bump @codemirror/view from 6.28.2 to 6.28.4 [`41e4133`](https://github.com/ebullient/obsidian-task-collector/commit/41e4133ad4bd67197985d5ccb733c3cb4e253237)\n- Bump tslib from 2.6.2 to 2.6.3 [`98adcfc`](https://github.com/ebullient/obsidian-task-collector/commit/98adcfc709fbad328b5a5a4719b82357b4514ef4)\n- Bump @codemirror/view from 6.28.1 to 6.28.2 [`3f42cb6`](https://github.com/ebullient/obsidian-task-collector/commit/3f42cb613a0b01c7ccf07cc773c4b1a5828920d0)\n- Bump typescript from 5.4.5 to 5.5.2 [`940c237`](https://github.com/ebullient/obsidian-task-collector/commit/940c2377073740f3fe75b7cba94b21d00ab7bb36)\n- Bump ts-jest from 29.1.4 to 29.1.5 [`ae15f5c`](https://github.com/ebullient/obsidian-task-collector/commit/ae15f5c302e046be9503f472c4282b48b4259e6f)\n- Bump @types/node from 20.14.0 to 20.14.2 [`4bf81fa`](https://github.com/ebullient/obsidian-task-collector/commit/4bf81fae2e80ce5fdfd3bc9eba28160780820ffa)\n- Bump prettier from 3.3.0 to 3.3.2 [`3eb6dd7`](https://github.com/ebullient/obsidian-task-collector/commit/3eb6dd7a594e9a64adc4941304ad4e6269172326)\n- Bump @codemirror/view from 6.27.0 to 6.28.1 [`e826895`](https://github.com/ebullient/obsidian-task-collector/commit/e82689500a3d46e917260d374a58818206d4cda2)\n- Bump @codemirror/view from 6.26.3 to 6.27.0 [`c2134dc`](https://github.com/ebullient/obsidian-task-collector/commit/c2134dc684a148411383305549af99f2a836db6b)\n- Bump prettier from 3.2.5 to 3.3.0 [`ca6169a`](https://github.com/ebullient/obsidian-task-collector/commit/ca6169acfe18e924512d2de7942ef627b4bca1d4)\n- Bump ts-jest from 29.1.3 to 29.1.4 [`a4bf072`](https://github.com/ebullient/obsidian-task-collector/commit/a4bf07228251362516e6a57d5a3814fd73f2e9a8)\n- Bump @types/node from 20.12.12 to 20.14.0 [`10de9f2`](https://github.com/ebullient/obsidian-task-collector/commit/10de9f2d93a55bbf10f05ed98a998d0eeb89e50d)\n- Bump @types/node from 20.12.11 to 20.12.12 [`091636e`](https://github.com/ebullient/obsidian-task-collector/commit/091636eba26dfb087377d29b8b49bd5d5caa942a)\n- Bump @types/node from 20.12.8 to 20.12.11 [`a6c0152`](https://github.com/ebullient/obsidian-task-collector/commit/a6c015267af6ee77ad018c8b66a9e543582fc158)\n- Bump @types/node from 20.12.7 to 20.12.8 [`53c0dd4`](https://github.com/ebullient/obsidian-task-collector/commit/53c0dd461d1970cdeac465c1967bddf270fb8012)\n- Bump typescript from 5.4.4 to 5.4.5 [`f071b4b`](https://github.com/ebullient/obsidian-task-collector/commit/f071b4bb9b1ec164a72fc2483d4bc1629477f0cc)\n- Bump @types/node from 20.11.30 to 20.12.7 [`19f0b91`](https://github.com/ebullient/obsidian-task-collector/commit/19f0b91aa1eb2358a240150d4f2efcc9606cc917)\n- Bump @codemirror/view from 6.26.1 to 6.26.3 [`ab8d60e`](https://github.com/ebullient/obsidian-task-collector/commit/ab8d60ec515417ace0440c12cf928b21924497e4)\n- Bump typescript from 5.4.3 to 5.4.4 [`cbf172c`](https://github.com/ebullient/obsidian-task-collector/commit/cbf172cc52a6d1d08c6b90367897d610d8fda3d6)\n- Bump @codemirror/view from 6.26.0 to 6.26.1 [`f29c595`](https://github.com/ebullient/obsidian-task-collector/commit/f29c59570f3e223e6879c505059ece6d475cb169)\n- Bump typescript from 5.4.2 to 5.4.3 [`f95c735`](https://github.com/ebullient/obsidian-task-collector/commit/f95c73587ea16839d33e1ce5ac10b8558d5ee22a)\n- 🩹 app reference [`fba8830`](https://github.com/ebullient/obsidian-task-collector/commit/fba8830faaf70f7f79586e92a6a896f764a2bab6)\n- Bump ws from 8.16.0 to 8.17.1 in the npm_and_yarn group [`b190e1a`](https://github.com/ebullient/obsidian-task-collector/commit/b190e1a447fbe776d6372e50968c8bc4fc289538)\n- 🎨 formatting [`614d40a`](https://github.com/ebullient/obsidian-task-collector/commit/614d40a222c12e737407815dd931d95ec1a52476)\n- fixed 491 issue of staredd checklists [`6c3ecb2`](https://github.com/ebullient/obsidian-task-collector/commit/6c3ecb29e0a7b77856df1b21e5f766bf8314ce26)\n\n#### [1.0.17](https://github.com/ebullient/obsidian-task-collector/compare/1.0.16...1.0.17)\n\n> 21 March 2024\n\n- 🐛 Add message to restart Obsidian; resolves [`#361`](https://github.com/ebullient/obsidian-task-collector/issues/361)\n- ⬆️ upgrade dependencies [`f530f76`](https://github.com/ebullient/obsidian-task-collector/commit/f530f76860605589a657b8bb77e622805f3ac683)\n- Bump eslint from 8.56.0 to 8.57.0 [`bc2ae75`](https://github.com/ebullient/obsidian-task-collector/commit/bc2ae75b01e366692074eb154032d61cd1b88715)\n- Bump @types/node from 20.11.24 to 20.11.28 [`691c147`](https://github.com/ebullient/obsidian-task-collector/commit/691c1479edcfa6fbf895321f6572b205f2b7d8c6)\n- Bump @codemirror/view from 6.25.1 to 6.26.0 [`6c2067c`](https://github.com/ebullient/obsidian-task-collector/commit/6c2067cc98476cc1d8b27407e99505f28ff80255)\n- Bump @codemirror/view from 6.24.1 to 6.25.1 [`8a6cdcd`](https://github.com/ebullient/obsidian-task-collector/commit/8a6cdcd14c2e0fdc6d66aa966e7f558d96eff4e9)\n- Bump typescript from 5.3.3 to 5.4.2 [`3eb8cc9`](https://github.com/ebullient/obsidian-task-collector/commit/3eb8cc9fbcce790be49fe6b0659a3ca527e6ceb8)\n- Bump @codemirror/state from 6.4.0 to 6.4.1 [`178035c`](https://github.com/ebullient/obsidian-task-collector/commit/178035c31ff2a76876c2b9e7d3dcdff5431e9fc9)\n- Bump @types/node from 20.11.19 to 20.11.24 [`52b5a26`](https://github.com/ebullient/obsidian-task-collector/commit/52b5a262c3dd2d234efae6a6031613600ef9ddc9)\n- Bump obsidian from 1.4.11 to 1.5.7 [`894efda`](https://github.com/ebullient/obsidian-task-collector/commit/894efda6be1f4b5863b594c8240ecd62996c94a1)\n- Bump @codemirror/view from 6.24.0 to 6.24.1 [`98d6305`](https://github.com/ebullient/obsidian-task-collector/commit/98d63059ff67ceb400b4d4c2ca9f03d2804acf67)\n- Bump dotenv from 16.4.4 to 16.4.5 [`79a00df`](https://github.com/ebullient/obsidian-task-collector/commit/79a00dff00cca373ce98d22aa3037d9f07e3fc3f)\n- 👷 update deps [`5bd8558`](https://github.com/ebullient/obsidian-task-collector/commit/5bd8558717bca022ca45ec9c0c64417099277ebe)\n- Update FUNDING.yml [`2f5878f`](https://github.com/ebullient/obsidian-task-collector/commit/2f5878f0b4ccf71edb3b822bc9e576f851811a45)\n\n#### [1.0.16](https://github.com/ebullient/obsidian-task-collector/compare/1.0.15...1.0.16)\n\n> 20 February 2024\n\n- Bump esbuild and esbuild-sass-plugin [`405fb06`](https://github.com/ebullient/obsidian-task-collector/commit/405fb067c7527ff7ead56c02bd9ff0dffed69e12)\n- Bump @typescript-eslint/eslint-plugin from 6.20.0 to 6.21.0 [`a946b35`](https://github.com/ebullient/obsidian-task-collector/commit/a946b35c055ed8925559c8da4b0f63ca45e644d1)\n- Bump @typescript-eslint/eslint-plugin from 6.19.1 to 6.20.0 [`bf81f3d`](https://github.com/ebullient/obsidian-task-collector/commit/bf81f3dfd0fcae5812a86b0b82415b98aca2ff06)\n- Bump @typescript-eslint/eslint-plugin from 6.19.0 to 6.19.1 [`f43da78`](https://github.com/ebullient/obsidian-task-collector/commit/f43da78d516fd1b2e71b544988709d820b1c9c24)\n- Bump @typescript-eslint/eslint-plugin from 6.18.1 to 6.19.0 [`516d7e6`](https://github.com/ebullient/obsidian-task-collector/commit/516d7e6f0285caace5924736f917d15fb75324d6)\n- Bump @typescript-eslint/eslint-plugin from 6.18.0 to 6.18.1 [`52c5098`](https://github.com/ebullient/obsidian-task-collector/commit/52c50985a220d2726ac2c948fe38304a22b1eeb3)\n- Bump esbuild from 0.19.11 to 0.19.12 [`c48a596`](https://github.com/ebullient/obsidian-task-collector/commit/c48a596dde8a687e6ebd685f4be5e2489793cebc)\n- Bump @typescript-eslint/parser from 6.19.0 to 6.20.0 [`5e1d0b5`](https://github.com/ebullient/obsidian-task-collector/commit/5e1d0b5cbc15b542296443b86893f30884dca38c)\n- Bump @typescript-eslint/parser from 6.18.0 to 6.19.0 [`91984f6`](https://github.com/ebullient/obsidian-task-collector/commit/91984f683a4e2a2a5c73460e46a7540c84a1683e)\n- Bump dotenv from 16.4.1 to 16.4.4 [`e196a02`](https://github.com/ebullient/obsidian-task-collector/commit/e196a0281d30746cd94f57b75d34365353c638e3)\n- Bump ts-jest from 29.1.1 to 29.1.2 [`0f252e2`](https://github.com/ebullient/obsidian-task-collector/commit/0f252e2894d23eb2c8542cb95eadc1f961d255e8)\n- Bump @types/node from 20.11.17 to 20.11.19 [`fec08d4`](https://github.com/ebullient/obsidian-task-collector/commit/fec08d4aee3979d31fb5c5b6a13abb2a88ffc36d)\n- Bump @types/jest from 29.5.11 to 29.5.12 [`edb0e26`](https://github.com/ebullient/obsidian-task-collector/commit/edb0e26aa2d385b4871bf91dee06981dfbae1eed)\n- Bump @types/node from 20.11.16 to 20.11.17 [`2307c3f`](https://github.com/ebullient/obsidian-task-collector/commit/2307c3fd412e8b41665de3d3fe51e797d588f059)\n- Bump @codemirror/view from 6.23.1 to 6.24.0 [`24f6c6b`](https://github.com/ebullient/obsidian-task-collector/commit/24f6c6b3c37b3e8bbda414eada07c0e6b2a178c5)\n- Bump @types/node from 20.11.5 to 20.11.16 [`6474cef`](https://github.com/ebullient/obsidian-task-collector/commit/6474cefe91dd4264ea78eab4aae517362f7b920a)\n- Bump prettier from 3.2.4 to 3.2.5 [`5243296`](https://github.com/ebullient/obsidian-task-collector/commit/524329682f9360cb4a287bab6bb9c8d8c2cd3d4d)\n- Bump dotenv from 16.3.2 to 16.4.1 [`e021d55`](https://github.com/ebullient/obsidian-task-collector/commit/e021d556d764d2235d42e515fda6ea00cb5b83e8)\n- Bump @codemirror/view from 6.23.0 to 6.23.1 [`c378d88`](https://github.com/ebullient/obsidian-task-collector/commit/c378d8836ec0d3232d3afb6e21863fdf21336a75)\n- Bump @types/node from 20.11.3 to 20.11.5 [`9e5bf1c`](https://github.com/ebullient/obsidian-task-collector/commit/9e5bf1c6e13408428dccad575f70271bab62492e)\n- Bump prettier from 3.2.2 to 3.2.4 [`e3acccc`](https://github.com/ebullient/obsidian-task-collector/commit/e3acccc2c7ace0e7de52e634e72fb61d74731e1b)\n- Bump dotenv from 16.3.1 to 16.3.2 [`dcee115`](https://github.com/ebullient/obsidian-task-collector/commit/dcee115ef60674c94bac95c7044c7b68456ec284)\n- Bump @types/node from 20.10.7 to 20.11.3 [`bfaffc5`](https://github.com/ebullient/obsidian-task-collector/commit/bfaffc511eacf2ad9c09503b24d96f5a6492b4a2)\n- Bump prettier from 3.1.1 to 3.2.2 [`64d84fb`](https://github.com/ebullient/obsidian-task-collector/commit/64d84fb1653e1684f21832cceede0e5a2548ca74)\n\n#### [1.0.15](https://github.com/ebullient/obsidian-task-collector/compare/1.0.14...1.0.15)\n\n> 9 January 2024\n\n- 🎨 Right click menu text. Resolves [`#387`](https://github.com/ebullient/obsidian-task-collector/issues/387)\n- Bump esbuild from 0.19.8 to 0.19.10 [`2d41b83`](https://github.com/ebullient/obsidian-task-collector/commit/2d41b83de98a40d2bea92e0845971d3506123bcd)\n- Bump esbuild from 0.19.10 to 0.19.11 [`8040ece`](https://github.com/ebullient/obsidian-task-collector/commit/8040ece4061a9cd74e9cc84ad2441409c5cf52fe)\n- Bump @typescript-eslint/eslint-plugin from 6.16.0 to 6.18.0 [`b6a334f`](https://github.com/ebullient/obsidian-task-collector/commit/b6a334f91b261a15e0ca45fe7867dc1e3e8c62ea)\n- Bump @typescript-eslint/eslint-plugin from 6.11.0 to 6.14.0 [`6fa3997`](https://github.com/ebullient/obsidian-task-collector/commit/6fa39970fec3fd2cc39a0376c859a5f7cf6088b8)\n- Bump @typescript-eslint/eslint-plugin from 6.14.0 to 6.16.0 [`fcd6601`](https://github.com/ebullient/obsidian-task-collector/commit/fcd66019906a608e994ca5984c80987f16cc2348)\n- Bump @typescript-eslint/parser from 6.14.0 to 6.15.0 [`ff60494`](https://github.com/ebullient/obsidian-task-collector/commit/ff6049476e500aa6b0df6377447f0c677cc413a6)\n- Bump @typescript-eslint/parser from 6.15.0 to 6.18.0 [`e63c1ab`](https://github.com/ebullient/obsidian-task-collector/commit/e63c1ab55a0bc04d702e4aeb92a67eacb5ee2b81)\n- Bump @typescript-eslint/parser from 6.13.2 to 6.14.0 [`9a9b0b8`](https://github.com/ebullient/obsidian-task-collector/commit/9a9b0b8786627cf44c0f214cd20bb3384ebdee12)\n- Bump @typescript-eslint/parser from 6.13.1 to 6.13.2 [`cffdd14`](https://github.com/ebullient/obsidian-task-collector/commit/cffdd148b5e911145ed12cb2ab2ef7a6c98b536f)\n- Bump @codemirror/view from 6.22.3 to 6.23.0 [`776275e`](https://github.com/ebullient/obsidian-task-collector/commit/776275e0e7da98a713a5bcb0ab30780d136456d6)\n- Bump eslint from 8.55.0 to 8.56.0 [`2ff05f7`](https://github.com/ebullient/obsidian-task-collector/commit/2ff05f779ec3bb45aa051685ccb8d0b8d6155f51)\n- Bump moment from 2.29.4 to 2.30.1 [`2e03235`](https://github.com/ebullient/obsidian-task-collector/commit/2e032358ab74448d0edab645d26284aaf1788ec4)\n- Bump @types/node from 20.10.6 to 20.10.7 [`2812fc4`](https://github.com/ebullient/obsidian-task-collector/commit/2812fc4aaf613cdf6412441f9921b506a78636a5)\n- Bump @types/node from 20.10.5 to 20.10.6 [`9e4fa54`](https://github.com/ebullient/obsidian-task-collector/commit/9e4fa54610b065b6e516cd8c320a3df8dc776104)\n- Bump esbuild-sass-plugin from 2.16.0 to 2.16.1 [`4aa995f`](https://github.com/ebullient/obsidian-task-collector/commit/4aa995fe67d2dfb7d5e7cead5968a50e9dda0fe4)\n- Bump @codemirror/state from 6.3.2 to 6.3.3 [`c976fbc`](https://github.com/ebullient/obsidian-task-collector/commit/c976fbc93c0ed39ccf8cda80c5bcce80c0acaa7a)\n- Bump @types/node from 20.10.0 to 20.10.5 [`01d4901`](https://github.com/ebullient/obsidian-task-collector/commit/01d4901fcc98278361e322199eabcbed903f61f6)\n- Bump @codemirror/view from 6.22.2 to 6.22.3 [`3c1f4b4`](https://github.com/ebullient/obsidian-task-collector/commit/3c1f4b42ae263313138a1e8edd6fdaea205875b2)\n- Bump @types/jest from 29.5.10 to 29.5.11 [`bca223e`](https://github.com/ebullient/obsidian-task-collector/commit/bca223ec0033238304c263c748f16808d1067c53)\n- Bump @codemirror/view from 6.22.1 to 6.22.2 [`4a2668b`](https://github.com/ebullient/obsidian-task-collector/commit/4a2668b02e55a0a37aef13bba18cbdebaa64a50d)\n- Bump prettier from 3.1.0 to 3.1.1 [`ef503fc`](https://github.com/ebullient/obsidian-task-collector/commit/ef503fc327d57d0dae4fde0f5a8427b5a47ed0a4)\n- Bump typescript from 5.3.2 to 5.3.3 [`673d55c`](https://github.com/ebullient/obsidian-task-collector/commit/673d55cb4a4948f33170886cc1d1c2cbae0d7ef9)\n\n#### [1.0.14](https://github.com/ebullient/obsidian-task-collector/compare/1.0.13...1.0.14)\n\n> 7 December 2023\n\n- Bump esbuild from 0.19.6 to 0.19.8 [`3d4bd48`](https://github.com/ebullient/obsidian-task-collector/commit/3d4bd48c8ac26c03207aa48b930b4e556399b530)\n- Bump esbuild from 0.19.5 to 0.19.6 [`8a87f8e`](https://github.com/ebullient/obsidian-task-collector/commit/8a87f8e006b936773c15d0961c6635f1583ff3a3)\n- Bump @typescript-eslint/parser from 6.10.0 to 6.11.0 [`de07a5d`](https://github.com/ebullient/obsidian-task-collector/commit/de07a5d71936d08b6266cf0ac3fae38855eb6073)\n- Bump @typescript-eslint/eslint-plugin from 6.9.1 to 6.11.0 [`3a5d521`](https://github.com/ebullient/obsidian-task-collector/commit/3a5d521a9ffce95e4ae1808350451338e5ae1c9b)\n- Bump @typescript-eslint/parser from 6.11.0 to 6.12.0 [`40fea47`](https://github.com/ebullient/obsidian-task-collector/commit/40fea477bbf1113374e674d4291798e2138bf41f)\n- Bump @typescript-eslint/parser from 6.12.0 to 6.13.1 [`1ce9de9`](https://github.com/ebullient/obsidian-task-collector/commit/1ce9de9eabe15ae61f25b069c49853863f0e7903)\n- Bump eslint from 8.54.0 to 8.55.0 [`95060e1`](https://github.com/ebullient/obsidian-task-collector/commit/95060e1018ecdeb7edb6dbd908dcf70ac97ec0df)\n- Bump eslint from 8.53.0 to 8.54.0 [`3e1a4f3`](https://github.com/ebullient/obsidian-task-collector/commit/3e1a4f322d2bea385e0631f6d0ac2b2a23a0fb1c)\n- Bump typescript from 5.2.2 to 5.3.2 [`5d8b98d`](https://github.com/ebullient/obsidian-task-collector/commit/5d8b98d267fe0dd0075c52a0a06a9eff8e839f0a)\n- Bump @codemirror/state from 6.3.1 to 6.3.2 [`76deac9`](https://github.com/ebullient/obsidian-task-collector/commit/76deac9846b1c1ed96985d9825486df968adf64b)\n- Bump @types/jest from 29.5.8 to 29.5.10 [`10532ed`](https://github.com/ebullient/obsidian-task-collector/commit/10532edc8b7ba99b68923975d16e7d4e827059b3)\n- Bump @types/node from 20.9.2 to 20.10.0 [`642ef67`](https://github.com/ebullient/obsidian-task-collector/commit/642ef67653fb06789b8f8b0edd2334a21969bd7a)\n- Bump @types/codemirror from 5.60.13 to 5.60.15 [`f1c3183`](https://github.com/ebullient/obsidian-task-collector/commit/f1c3183c36beff94dc0cdb59d6d70ddf00666ab8)\n- Bump @codemirror/view from 6.22.0 to 6.22.1 [`6de53f3`](https://github.com/ebullient/obsidian-task-collector/commit/6de53f312725e02a4c787839d7d4e926b34f624f)\n- Bump @types/node from 20.9.0 to 20.9.2 [`438eb0a`](https://github.com/ebullient/obsidian-task-collector/commit/438eb0a5ff0b48d9b77b147320a2a207cb6f9480)\n- 🎨 use this.app consistently [`bc45c4f`](https://github.com/ebullient/obsidian-task-collector/commit/bc45c4f00f15ed6937db9d10d53df2e38031de6f)\n\n#### [1.0.13](https://github.com/ebullient/obsidian-task-collector/compare/1.0.12...1.0.13)\n\n> 18 November 2023\n\n- 🎨 Add hideNotifications setting; Resolves [`#330`](https://github.com/ebullient/obsidian-task-collector/issues/330)\n- Bump jest and @types/jest [`6cc2201`](https://github.com/ebullient/obsidian-task-collector/commit/6cc22013f60e59e7a838f2b7032172992bad7cc8)\n- Bump jest and @types/jest [`c42e1dd`](https://github.com/ebullient/obsidian-task-collector/commit/c42e1dd7f42cc715645a26c5da89489365d10c95)\n- Bump esbuild and esbuild-sass-plugin [`8cd36eb`](https://github.com/ebullient/obsidian-task-collector/commit/8cd36eb2948c6bcfe9409f8a92000ab4d0413807)\n- Bump @typescript-eslint/eslint-plugin from 6.7.5 to 6.8.0 [`10ab304`](https://github.com/ebullient/obsidian-task-collector/commit/10ab30473fa389a1f3449d97fd9dc2074fde031b)\n- Bump esbuild from 0.19.4 to 0.19.5 [`f7ab869`](https://github.com/ebullient/obsidian-task-collector/commit/f7ab869eb9f153843fedfcf95d38bfcb61ee39d7)\n- Bump esbuild from 0.19.3 to 0.19.4 [`02f8d14`](https://github.com/ebullient/obsidian-task-collector/commit/02f8d147bd7ea68e33a24b5f7ef9dd37f8ea5bb3)\n- Bump esbuild from 0.19.2 to 0.19.3 [`a4c45fa`](https://github.com/ebullient/obsidian-task-collector/commit/a4c45fa449c618e07c17ae9434f85946439dbb0b)\n- Bump esbuild from 0.18.17 to 0.18.20 [`23f6470`](https://github.com/ebullient/obsidian-task-collector/commit/23f6470a4f6b782492606bb72f0709f7f352b8e6)\n- Bump @typescript-eslint/eslint-plugin from 6.7.2 to 6.7.3 [`0a9a8ba`](https://github.com/ebullient/obsidian-task-collector/commit/0a9a8ba21f16dba4a7e99a3137239135140e527c)\n- Bump @typescript-eslint/eslint-plugin from 6.7.4 to 6.7.5 [`36ceab7`](https://github.com/ebullient/obsidian-task-collector/commit/36ceab7895fb5f88e1cc125e6a5fb91e93048495)\n- Bump @typescript-eslint/eslint-plugin from 6.7.3 to 6.7.4 [`d3bad25`](https://github.com/ebullient/obsidian-task-collector/commit/d3bad2510ad76d83f5e82170af03b4c01f6ffd9b)\n- Bump @typescript-eslint/eslint-plugin from 6.4.0 to 6.4.1 [`4feae9b`](https://github.com/ebullient/obsidian-task-collector/commit/4feae9b0b5b29a7228ee88fa80d8d84c368f1f52)\n- Bump @babel/traverse from 7.22.5 to 7.23.2 [`23f40a9`](https://github.com/ebullient/obsidian-task-collector/commit/23f40a98428b1f91a5082345897630a139ab6a7a)\n- Bump @typescript-eslint/eslint-plugin from 6.3.0 to 6.4.0 [`7846487`](https://github.com/ebullient/obsidian-task-collector/commit/78464871e6580196c0972e8c69afd2a105323d9c)\n- Bump @typescript-eslint/eslint-plugin from 6.6.0 to 6.7.2 [`4259831`](https://github.com/ebullient/obsidian-task-collector/commit/4259831e26978b010098aa0bc35d8f433dd8eb00)\n- Bump @typescript-eslint/eslint-plugin from 6.4.1 to 6.6.0 [`16e503d`](https://github.com/ebullient/obsidian-task-collector/commit/16e503d91c8dcf3c57dff0ff57143ca1b43ca336)\n- Bump @typescript-eslint/eslint-plugin from 6.9.0 to 6.9.1 [`aa3b801`](https://github.com/ebullient/obsidian-task-collector/commit/aa3b801869b7b6249f9e8ce5f6dff538454ca338)\n- Bump @typescript-eslint/eslint-plugin from 6.8.0 to 6.9.0 [`1d1d27a`](https://github.com/ebullient/obsidian-task-collector/commit/1d1d27a69aadb17119f0e06447fbdac02cc6ff3a)\n- ✨  Skip section matching pattern [`4460a31`](https://github.com/ebullient/obsidian-task-collector/commit/4460a315335de22737a07a7ce7d07f77301e36ea)\n- Bump @typescript-eslint/parser from 6.3.0 to 6.4.0 [`8a8b3e5`](https://github.com/ebullient/obsidian-task-collector/commit/8a8b3e5b8befad597eccd72e6db7a01770a44535)\n- Bump jest-environment-jsdom from 29.6.4 to 29.7.0 [`e260598`](https://github.com/ebullient/obsidian-task-collector/commit/e26059854037c4b763af2e09b34d55ca645fba74)\n- Bump @typescript-eslint/parser from 6.7.5 to 6.10.0 [`b7eb9f7`](https://github.com/ebullient/obsidian-task-collector/commit/b7eb9f7e38f2b38a17925e0abc1b1cf4938ea050)\n- Bump @typescript-eslint/parser from 6.7.3 to 6.7.5 [`8fa7051`](https://github.com/ebullient/obsidian-task-collector/commit/8fa70517ce47a6db771665cc671f98cb055fb502)\n- Bump @typescript-eslint/parser from 6.4.0 to 6.7.3 [`d62687a`](https://github.com/ebullient/obsidian-task-collector/commit/d62687aca654a9091eed34d8e570c5b6bb033ecc)\n- Bump esbuild-sass-plugin from 2.15.0 to 2.16.0 [`18b583c`](https://github.com/ebullient/obsidian-task-collector/commit/18b583c265ed195efbaba3e44f2a6d1c0b9792d8)\n- Bump eslint from 8.51.0 to 8.52.0 [`bdd94b4`](https://github.com/ebullient/obsidian-task-collector/commit/bdd94b4f813d6557a432c9557e89c1ba9a673ea4)\n- Bump eslint from 8.52.0 to 8.53.0 [`01331f8`](https://github.com/ebullient/obsidian-task-collector/commit/01331f84d4ddfc4c4499d3837cee05e2fe97c658)\n- 👷 cleanup [`5072c8a`](https://github.com/ebullient/obsidian-task-collector/commit/5072c8a2e17ece9acfbcd3cd5438af3eaaf086d1)\n- Bump eslint from 8.48.0 to 8.49.0 [`ac45895`](https://github.com/ebullient/obsidian-task-collector/commit/ac4589517859261da2524aa6a4155ac87d19e21f)\n- Bump esbuild-sass-plugin from 2.10.0 to 2.11.0 [`9aeb4f9`](https://github.com/ebullient/obsidian-task-collector/commit/9aeb4f927c2834a0d7f7bc7d7cb04301c6c35889)\n- Bump @types/node from 20.8.3 to 20.8.6 [`b667e25`](https://github.com/ebullient/obsidian-task-collector/commit/b667e258159bbedcdae2795a463cd2836e79608f)\n- ⬆️ bump semver [`a067001`](https://github.com/ebullient/obsidian-task-collector/commit/a067001a1e6eec1de20b13360a1f3cf3e396bb6d)\n- Bump jest-environment-jsdom from 29.6.2 to 29.6.4 [`3ff21b0`](https://github.com/ebullient/obsidian-task-collector/commit/3ff21b05ec7e4df0bc6b45358154e3de58e19024)\n- Bump @types/codemirror from 5.60.8 to 5.60.9 [`720ac8b`](https://github.com/ebullient/obsidian-task-collector/commit/720ac8b1019cc05d448483a0e000dce0c931143b)\n- Bump @types/node from 20.8.7 to 20.8.9 [`0f8d0ee`](https://github.com/ebullient/obsidian-task-collector/commit/0f8d0eee0e67c2759bd1df76ac90f1ead11d0887)\n- Bump eslint from 8.50.0 to 8.51.0 [`930295e`](https://github.com/ebullient/obsidian-task-collector/commit/930295ed0b3a2ed02c3c6e2c045276a3e499684d)\n- Bump eslint from 8.49.0 to 8.50.0 [`229db3b`](https://github.com/ebullient/obsidian-task-collector/commit/229db3b7527e190a4e39c08038a27910d714431b)\n- Bump @codemirror/view from 6.16.0 to 6.17.1 [`d1f7b4f`](https://github.com/ebullient/obsidian-task-collector/commit/d1f7b4facd3aed5c3b7b6437c1a11f36d10cc2a0)\n- Bump eslint from 8.47.0 to 8.48.0 [`a7ea521`](https://github.com/ebullient/obsidian-task-collector/commit/a7ea521c640de1d5b39a8225022c0521f933a422)\n- Bump @types/codemirror from 5.60.12 to 5.60.13 [`06e3f72`](https://github.com/ebullient/obsidian-task-collector/commit/06e3f72e5bc885ebf25c1cc4a04c14650374a0d1)\n- Bump @types/node from 20.8.10 to 20.9.0 [`9695e14`](https://github.com/ebullient/obsidian-task-collector/commit/9695e14cefe63e50c27a156beb73586d57215dd1)\n- Bump @codemirror/view from 6.21.4 to 6.22.0 [`0aa87ce`](https://github.com/ebullient/obsidian-task-collector/commit/0aa87ce19094a6087f0f379f1425395b1bf081b8)\n- Bump prettier from 3.0.3 to 3.1.0 [`6a3ca0a`](https://github.com/ebullient/obsidian-task-collector/commit/6a3ca0adda8b3eee669f3c93d4a0cfc036a7e863)\n- Bump @types/jest from 29.5.7 to 29.5.8 [`193b180`](https://github.com/ebullient/obsidian-task-collector/commit/193b18051eb868527da3555a4525b4bbca46bab4)\n- Bump @types/node from 20.8.9 to 20.8.10 [`19045da`](https://github.com/ebullient/obsidian-task-collector/commit/19045da5f53d277454d3edd3688512af6d6219d1)\n- Bump @types/jest from 29.5.6 to 29.5.7 [`9b7f972`](https://github.com/ebullient/obsidian-task-collector/commit/9b7f972742621f8ca8c6b5a9d488a1c5229de010)\n- Bump @codemirror/state from 6.3.0 to 6.3.1 [`955d0fb`](https://github.com/ebullient/obsidian-task-collector/commit/955d0fb2163bd4b8123888879fde541965ed9917)\n- Bump @codemirror/view from 6.21.3 to 6.21.4 [`074dcec`](https://github.com/ebullient/obsidian-task-collector/commit/074dcec41d83824eaa269ffe064427b6b5bd87b8)\n- Bump @types/jest from 29.5.5 to 29.5.6 [`1f5a140`](https://github.com/ebullient/obsidian-task-collector/commit/1f5a14093ed7040f12d25723d089b85366233734)\n- Bump @types/node from 20.8.6 to 20.8.7 [`462e395`](https://github.com/ebullient/obsidian-task-collector/commit/462e39569945ef591e9e98a6f9975924b0a2f17a)\n- Bump @types/codemirror from 5.60.10 to 5.60.12 [`9791513`](https://github.com/ebullient/obsidian-task-collector/commit/97915139b1bbb48ddbe9521c521d887b67c7d382)\n- Bump @codemirror/state from 6.2.1 to 6.3.0 [`411ad51`](https://github.com/ebullient/obsidian-task-collector/commit/411ad516e42272c0957a2a25bcb415cfe2804b4c)\n- Bump @types/node from 20.6.2 to 20.8.3 [`a2880e9`](https://github.com/ebullient/obsidian-task-collector/commit/a2880e93d18320632a5bec1b1692413ea54e1e87)\n- Bump @codemirror/view from 6.21.1 to 6.21.3 [`e527d61`](https://github.com/ebullient/obsidian-task-collector/commit/e527d6144cba70b7707284e024b2904a719b1d3e)\n- Bump @codemirror/view from 6.20.2 to 6.21.1 [`c96b247`](https://github.com/ebullient/obsidian-task-collector/commit/c96b247fdeb8abc1d91c5ee3c450a40d8122d4b7)\n- Bump esbuild-sass-plugin from 2.14.0 to 2.15.0 [`471f3c9`](https://github.com/ebullient/obsidian-task-collector/commit/471f3c99e47fe1f996bc02de910036e8d8445794)\n- Bump @codemirror/view from 6.18.0 to 6.20.2 [`aef2079`](https://github.com/ebullient/obsidian-task-collector/commit/aef2079ba4d39e6ee1df1f16df62d617cda1128d)\n- Bump @types/codemirror from 5.60.9 to 5.60.10 [`cb3d6aa`](https://github.com/ebullient/obsidian-task-collector/commit/cb3d6aa7b7bcf86cd85121de85b31ed901106e38)\n- Bump obsidian from 1.4.4 to 1.4.11 [`7aee358`](https://github.com/ebullient/obsidian-task-collector/commit/7aee3584a5fcf6165632704182ce7ecf7b48d550)\n- Bump @types/node from 20.6.0 to 20.6.2 [`3aa2ac0`](https://github.com/ebullient/obsidian-task-collector/commit/3aa2ac0056918a17746e272ae9f56e2368aa8213)\n- Bump @types/node from 20.5.9 to 20.6.0 [`16b1b48`](https://github.com/ebullient/obsidian-task-collector/commit/16b1b485a822b02ff7f0832e63fec34584f8ef7a)\n- Bump @codemirror/view from 6.17.1 to 6.18.0 [`b9196f9`](https://github.com/ebullient/obsidian-task-collector/commit/b9196f9df4afd2fdbc8983b6e45c392f6f801be6)\n- Bump typescript from 5.1.6 to 5.2.2 [`4f22fba`](https://github.com/ebullient/obsidian-task-collector/commit/4f22fbab6d7a4d4464fccaf1c86501f23651f6f6)\n- Bump prettier from 3.0.2 to 3.0.3 [`9aab772`](https://github.com/ebullient/obsidian-task-collector/commit/9aab772ccb3e1068c3fd69a6911e8885338f57e3)\n- Bump @types/node from 20.5.0 to 20.5.9 [`6840f90`](https://github.com/ebullient/obsidian-task-collector/commit/6840f90653c4c75070b1f9a16db0d41b373623b6)\n- Bump prettier from 3.0.1 to 3.0.2 [`af282a2`](https://github.com/ebullient/obsidian-task-collector/commit/af282a2d80aa62ab1f8c45e7eb74deb5052fcb8c)\n- Bump obsidian from 1.4.0 to 1.4.4 [`30f6503`](https://github.com/ebullient/obsidian-task-collector/commit/30f65035e30d341d665e8f5e054e47a8f13a661e)\n- Bump tslib from 2.6.1 to 2.6.2 [`eb4005d`](https://github.com/ebullient/obsidian-task-collector/commit/eb4005d8f588066b69cfc7083c1dafdf3209922c)\n- 👷 sync manifest-beta [`f3a0a21`](https://github.com/ebullient/obsidian-task-collector/commit/f3a0a2112cc286301df0515eb36414d13894e82c)\n\n#### [1.0.12](https://github.com/ebullient/obsidian-task-collector/compare/1.0.11...1.0.12)\n\n> 16 August 2023\n\n- 🐛 Ensure the last line is moved. Fixes [`#262`](https://github.com/ebullient/obsidian-task-collector/issues/262)\n- ⬆️ Bump jest version [`9a3460d`](https://github.com/ebullient/obsidian-task-collector/commit/9a3460d0c53a8bc29e03095d9f77c390c1920146)\n- Bump jest from 29.5.0 to 29.6.1 [`0a2dc56`](https://github.com/ebullient/obsidian-task-collector/commit/0a2dc56cec5b05c75d59f4a4b552974bd99bd5a7)\n- Bump jest from 29.6.1 to 29.6.2 [`2fbfad1`](https://github.com/ebullient/obsidian-task-collector/commit/2fbfad1ce289d0f0cf1347a4e9b5c490f46de807)\n- Bump esbuild and esbuild-sass-plugin [`a82994c`](https://github.com/ebullient/obsidian-task-collector/commit/a82994c52d0d7340e8d6fcc6ef41ec51987ec511)\n- Bump @typescript-eslint/parser from 5.59.8 to 5.59.9 [`fe81c4d`](https://github.com/ebullient/obsidian-task-collector/commit/fe81c4d5a7c73405a3910456a060d5600e4a8cb5)\n- Bump @typescript-eslint/eslint-plugin from 5.59.8 to 5.59.9 [`7fb8831`](https://github.com/ebullient/obsidian-task-collector/commit/7fb88312cf78a58502365fd3c0b5b2b91f1895fb)\n- ✏️  Prettier. [`ade8d42`](https://github.com/ebullient/obsidian-task-collector/commit/ade8d42b6e59ecbc95ad99af2b1d843d06af7a42)\n- Bump @typescript-eslint/eslint-plugin from 5.60.1 to 6.0.0 [`73ece21`](https://github.com/ebullient/obsidian-task-collector/commit/73ece21f22401483c8376ef4e2fc624a69b8553d)\n- Bump @typescript-eslint/eslint-plugin from 6.0.0 to 6.1.0 [`eeddc44`](https://github.com/ebullient/obsidian-task-collector/commit/eeddc4462c51937592ca8cf59039a0ce758b7bf6)\n- Bump @typescript-eslint/eslint-plugin from 5.59.11 to 5.60.0 [`09d271c`](https://github.com/ebullient/obsidian-task-collector/commit/09d271c52049d4c8f1afa333023cadf5d0c0d165)\n- Bump @typescript-eslint/eslint-plugin from 5.59.9 to 5.59.11 [`25d60cd`](https://github.com/ebullient/obsidian-task-collector/commit/25d60cd8bcabd4ad19fe18091aea3bc5982129a2)\n- Bump @typescript-eslint/eslint-plugin from 6.2.0 to 6.2.1 [`efeb2f0`](https://github.com/ebullient/obsidian-task-collector/commit/efeb2f057fe919066ae59d3900fb3dbdb63b70a3)\n- Bump @typescript-eslint/parser from 6.2.0 to 6.2.1 [`db8c145`](https://github.com/ebullient/obsidian-task-collector/commit/db8c1452b314eb479ef39c299f26e0e40c27fa27)\n- Bump esbuild from 0.18.16 to 0.18.17 [`98bb666`](https://github.com/ebullient/obsidian-task-collector/commit/98bb6667e55fa16e174bbf6cd3514bcb8043fd2d)\n- Bump esbuild from 0.18.11 to 0.18.16 [`ef64f12`](https://github.com/ebullient/obsidian-task-collector/commit/ef64f12022d261de556091cf829e4be7bd355a1c)\n- Bump esbuild from 0.18.9 to 0.18.11 [`ddafa5f`](https://github.com/ebullient/obsidian-task-collector/commit/ddafa5fbcc8b88574d5c9307671eccb153879137)\n- Bump @typescript-eslint/parser from 5.59.11 to 5.60.0 [`564b131`](https://github.com/ebullient/obsidian-task-collector/commit/564b1310af4e849d9266fb4c52c816bcd76481cc)\n- Bump @typescript-eslint/parser from 5.59.9 to 5.59.11 [`afa25b6`](https://github.com/ebullient/obsidian-task-collector/commit/afa25b6d92c2b984a90048bf69ad6e8d0d718adf)\n- Bump @typescript-eslint/eslint-plugin from 6.2.1 to 6.3.0 [`2610f76`](https://github.com/ebullient/obsidian-task-collector/commit/2610f76bf97f645306ef65fbae85601157726ab0)\n- Bump @typescript-eslint/eslint-plugin from 6.1.0 to 6.2.0 [`ea29016`](https://github.com/ebullient/obsidian-task-collector/commit/ea290169000e69f0058a523bde2cb5414ca4bdb9)\n- Bump eslint from 8.45.0 to 8.47.0 [`0be8ee4`](https://github.com/ebullient/obsidian-task-collector/commit/0be8ee45aa12b9858c78478fa0ebdaa2aa2d7bd5)\n- Bump jest-environment-jsdom from 29.5.0 to 29.6.1 [`944f6b6`](https://github.com/ebullient/obsidian-task-collector/commit/944f6b6da1b801d1b063db9ace3fe6f8a860fd78)\n- Bump @typescript-eslint/parser from 5.60.1 to 6.1.0 [`dacc120`](https://github.com/ebullient/obsidian-task-collector/commit/dacc120768a0b8eb746119197b4b76e81a65d8d2)\n- Bump @typescript-eslint/parser from 6.2.1 to 6.3.0 [`c6fda83`](https://github.com/ebullient/obsidian-task-collector/commit/c6fda83f3c0e8a8a41b3e0abd1832bacbd7c649f)\n- Bump @typescript-eslint/parser from 6.1.0 to 6.2.0 [`34558e2`](https://github.com/ebullient/obsidian-task-collector/commit/34558e298a90eb288597e0847f0915b8ef8d2de2)\n- Bump jest-environment-jsdom from 29.6.1 to 29.6.2 [`4cc7fb0`](https://github.com/ebullient/obsidian-task-collector/commit/4cc7fb0928447885bd3a56327c4c5f60c4842683)\n- Bump eslint from 8.43.0 to 8.44.0 [`fa46d44`](https://github.com/ebullient/obsidian-task-collector/commit/fa46d445719ee6fbec49b9490d820205c1901590)\n- ⬆️ 🐛 Fix references to moment [`c5c37ad`](https://github.com/ebullient/obsidian-task-collector/commit/c5c37adfca474d83bff1391b6dc3c4573673f1cc)\n- Bump eslint from 8.42.0 to 8.43.0 [`955bfa4`](https://github.com/ebullient/obsidian-task-collector/commit/955bfa409bccdad8202113a19ea7fc202199e247)\n- Bump obsidian from 1.2.8 to 1.4.0 [`b89569a`](https://github.com/ebullient/obsidian-task-collector/commit/b89569a093d01792c8fdf8d7240b3bc7f33a68e8)\n- Bump dotenv from 16.1.4 to 16.3.1 [`daefbb9`](https://github.com/ebullient/obsidian-task-collector/commit/daefbb9469a9c13d640269def1995dfc2d03fa85)\n- Bump @codemirror/view from 6.13.2 to 6.14.0 [`968bc27`](https://github.com/ebullient/obsidian-task-collector/commit/968bc27c4a4925e46267e2121b69aa7f5560e92c)\n- Bump @types/node from 20.3.0 to 20.3.1 [`5cbfa4f`](https://github.com/ebullient/obsidian-task-collector/commit/5cbfa4f8576514f325de0051dbdc0c2dbfa92f4c)\n- Bump @codemirror/view from 6.13.0 to 6.13.2 [`54a3422`](https://github.com/ebullient/obsidian-task-collector/commit/54a3422e386384535a2b5f2d4b0dba9feb18ae03)\n- Bump @types/codemirror from 5.60.7 to 5.60.8 [`84bcaaf`](https://github.com/ebullient/obsidian-task-collector/commit/84bcaafd7ac0ee480797c47a74bf77c298a1697a)\n- Bump @types/node from 20.2.5 to 20.3.0 [`b5b46ac`](https://github.com/ebullient/obsidian-task-collector/commit/b5b46acfcc24e1873e90a5e45391cf87596d50d1)\n- Bump prettier from 2.8.8 to 3.0.0 [`c4a2497`](https://github.com/ebullient/obsidian-task-collector/commit/c4a249795f456661f0a8a5ed1cc2bd8f28076a1f)\n- Bump eslint from 8.44.0 to 8.45.0 [`d8d1c39`](https://github.com/ebullient/obsidian-task-collector/commit/d8d1c39d7d8a4ec6097a450964671de3c8cb3980)\n- Bump typescript from 4.9.5 to 5.1.6 [`005e08c`](https://github.com/ebullient/obsidian-task-collector/commit/005e08c1fb937de4a134f402ce8fe3caa13d482e)\n- Bump ts-jest from 29.1.0 to 29.1.1 [`eac6860`](https://github.com/ebullient/obsidian-task-collector/commit/eac68604e8409a95135b522ed00b540909a08b0c)\n- Bump @types/node from 20.4.4 to 20.5.0 [`9cc4408`](https://github.com/ebullient/obsidian-task-collector/commit/9cc44086085c8a27ca0a8340c14e7704fe880acd)\n- Bump @codemirror/view from 6.15.3 to 6.16.0 [`068fb33`](https://github.com/ebullient/obsidian-task-collector/commit/068fb3382529dbc027451db7d45e810e9fc33039)\n- Bump prettier from 3.0.0 to 3.0.1 [`a56477d`](https://github.com/ebullient/obsidian-task-collector/commit/a56477d055a4ed6545fb40816688f975d1cce4bb)\n- Bump tslib from 2.6.0 to 2.6.1 [`3ed7007`](https://github.com/ebullient/obsidian-task-collector/commit/3ed70077d1e80afcad951b9557e9d83992a24ee8)\n- Bump @codemirror/view from 6.14.1 to 6.15.3 [`951e5cc`](https://github.com/ebullient/obsidian-task-collector/commit/951e5cc499c2289fe705ce0b1a48cd5ba77e5102)\n- Bump @types/node from 20.4.2 to 20.4.4 [`390094f`](https://github.com/ebullient/obsidian-task-collector/commit/390094f7b29626f5cb90efc4a2a112814fad0ae7)\n- Bump @types/node from 20.4.1 to 20.4.2 [`036b93d`](https://github.com/ebullient/obsidian-task-collector/commit/036b93d138d1888511c617834d4a8621901308e8)\n- Bump @types/jest from 29.5.2 to 29.5.3 [`da4c455`](https://github.com/ebullient/obsidian-task-collector/commit/da4c4559411c58de4d4a27f98e587cb6604a82a8)\n- Bump @types/node from 20.3.3 to 20.4.1 [`045ef91`](https://github.com/ebullient/obsidian-task-collector/commit/045ef910fff6085f53f0c1ec18c52dfea1a985c3)\n- Bump @codemirror/view from 6.14.0 to 6.14.1 [`5960823`](https://github.com/ebullient/obsidian-task-collector/commit/596082368067665272d75dd48a94b01239296d0d)\n- Bump @types/node from 20.3.2 to 20.3.3 [`dd1b3bd`](https://github.com/ebullient/obsidian-task-collector/commit/dd1b3bdfa0edaf4757f0555923afe4cb85304b9e)\n- Bump tslib from 2.5.3 to 2.6.0 [`2a536f0`](https://github.com/ebullient/obsidian-task-collector/commit/2a536f08972afa44392f298e289471e6f2f012db)\n- Bump word-wrap from 1.2.3 to 1.2.4 [`2f5dbce`](https://github.com/ebullient/obsidian-task-collector/commit/2f5dbcee7679e43a211e3f9bc4a4d3870c0f4393)\n\n#### [1.0.11](https://github.com/ebullient/obsidian-task-collector/compare/1.0.10...1.0.11)\n\n> 6 June 2023\n\n- 🐛 Support numbered tasks. Fixes [`#212`](https://github.com/ebullient/obsidian-task-collector/issues/212)\n- Bump esbuild from 0.17.18 to 0.17.19 [`5fd1963`](https://github.com/ebullient/obsidian-task-collector/commit/5fd1963c6f113d840f5e00685f229b1e216f4e55)\n- Bump esbuild from 0.17.17 to 0.17.18 [`3b0e8fb`](https://github.com/ebullient/obsidian-task-collector/commit/3b0e8fb187f101e908062d3e3903a10b63c6959b)\n- Bump @typescript-eslint/eslint-plugin from 5.59.6 to 5.59.7 [`8d03001`](https://github.com/ebullient/obsidian-task-collector/commit/8d0300122e87a8925e7608d2876468df405e7ceb)\n- Bump @typescript-eslint/parser from 5.59.5 to 5.59.6 [`c2c8f4b`](https://github.com/ebullient/obsidian-task-collector/commit/c2c8f4b50c91692bf5f3d1694dc8f288527fd1a0)\n- Bump @typescript-eslint/eslint-plugin from 5.59.7 to 5.59.8 [`f3e096d`](https://github.com/ebullient/obsidian-task-collector/commit/f3e096dd4a6d4d1d2e40e6da5458bbe450fece49)\n- Bump @typescript-eslint/eslint-plugin from 5.59.1 to 5.59.2 [`9f44e17`](https://github.com/ebullient/obsidian-task-collector/commit/9f44e17befb316c772499c4f125ce7caadfe73d5)\n- Bump @typescript-eslint/eslint-plugin from 5.59.2 to 5.59.6 [`5cbb3bf`](https://github.com/ebullient/obsidian-task-collector/commit/5cbb3bf37e293fab199232d4ecf655b94600fbf1)\n- Bump @typescript-eslint/parser from 5.59.0 to 5.59.1 [`20a0eb8`](https://github.com/ebullient/obsidian-task-collector/commit/20a0eb834f957d43c787ab625edf71f9bbb5932a)\n- Bump @typescript-eslint/parser from 5.58.0 to 5.59.0 [`efdb28a`](https://github.com/ebullient/obsidian-task-collector/commit/efdb28a178f0dfb91e31a583a6c10ebfab979eb1)\n- Bump @typescript-eslint/eslint-plugin from 5.58.0 to 5.59.1 [`e329a87`](https://github.com/ebullient/obsidian-task-collector/commit/e329a876922dabad997f77da131e29020268d2b6)\n- Bump eslint from 8.39.0 to 8.41.0 [`b228df9`](https://github.com/ebullient/obsidian-task-collector/commit/b228df9fcba4e94a18fe934383400c6c19c76980)\n- Bump @typescript-eslint/parser from 5.59.6 to 5.59.8 [`8128162`](https://github.com/ebullient/obsidian-task-collector/commit/8128162dc118e601eaa7f46e59d8df3c09c6c350)\n- Bump @typescript-eslint/parser from 5.59.1 to 5.59.5 [`6f8bfa9`](https://github.com/ebullient/obsidian-task-collector/commit/6f8bfa968065afe96e849ce3d34ab9f75b290f4f)\n- Bump eslint from 8.41.0 to 8.42.0 [`bdaebd6`](https://github.com/ebullient/obsidian-task-collector/commit/bdaebd6da603cc3710e1170eea72cf891dee263f)\n- Bump eslint from 8.38.0 to 8.39.0 [`ddec5c6`](https://github.com/ebullient/obsidian-task-collector/commit/ddec5c6dbe51598b3024d1b4dab1905acb8f2a04)\n- Bump @types/codemirror from 0.0.108 to 5.60.7 [`3da028b`](https://github.com/ebullient/obsidian-task-collector/commit/3da028b68ed2b94934506c872b96ec917a85bb05)\n- Bump esbuild-sass-plugin from 2.8.0 to 2.9.0 [`7f3c440`](https://github.com/ebullient/obsidian-task-collector/commit/7f3c4408a8037f5b67362fdb3b3a6c5a7c1e3a34)\n- Bump dotenv from 16.0.3 to 16.1.4 [`2a74ba8`](https://github.com/ebullient/obsidian-task-collector/commit/2a74ba87c3bbf5ccc90c773f1372f0c71d96c2af)\n- Bump @codemirror/view from 6.12.0 to 6.13.0 [`d22404e`](https://github.com/ebullient/obsidian-task-collector/commit/d22404e3c43bcbd69ac1e73c82284240f2ad29c8)\n- Bump tslib from 2.5.2 to 2.5.3 [`21c1abc`](https://github.com/ebullient/obsidian-task-collector/commit/21c1abc6b85383fe45c087bce8e70a93c998af44)\n- Bump @types/node from 20.2.3 to 20.2.5 [`84126f8`](https://github.com/ebullient/obsidian-task-collector/commit/84126f889de78c29d66d8cf9ba1da542570c76d5)\n- Bump @codemirror/state from 6.2.0 to 6.2.1 [`08a18c0`](https://github.com/ebullient/obsidian-task-collector/commit/08a18c02aebb012cadfc6ed5b3df74f8a27c2df5)\n- Bump tslib from 2.5.0 to 2.5.2 [`624a5e7`](https://github.com/ebullient/obsidian-task-collector/commit/624a5e760ca7d6c73a7d973d49cecadf5cdf8c09)\n- Bump @types/node from 20.1.4 to 20.2.3 [`beab965`](https://github.com/ebullient/obsidian-task-collector/commit/beab9656f38d91d0b4f75a122fa542ac8d6c75c7)\n- Bump @codemirror/view from 6.11.2 to 6.12.0 [`22122a4`](https://github.com/ebullient/obsidian-task-collector/commit/22122a476be197aec42b44797162581a02b97441)\n- Bump @codemirror/view from 6.11.0 to 6.11.2 [`8e9bc76`](https://github.com/ebullient/obsidian-task-collector/commit/8e9bc7648d74823283165b638b91885785f30d7e)\n- Bump @types/node from 20.1.0 to 20.1.4 [`5e13965`](https://github.com/ebullient/obsidian-task-collector/commit/5e1396508d68f0ec3a55d926b8e41dd2fecd3d4f)\n- Bump obsidian from 1.2.5 to 1.2.8 [`7b901b0`](https://github.com/ebullient/obsidian-task-collector/commit/7b901b09496e07061eae868a0c0088e475387eea)\n- Bump @codemirror/view from 6.9.5 to 6.11.0 [`49b56ec`](https://github.com/ebullient/obsidian-task-collector/commit/49b56ec7c43891966b0aef686a4f41ab57b1ca87)\n- Bump @types/node from 18.16.3 to 20.1.0 [`2fb09aa`](https://github.com/ebullient/obsidian-task-collector/commit/2fb09aa298764b64dccf75949508fc12fee208aa)\n- Bump @types/node from 18.15.11 to 18.16.3 [`6629d38`](https://github.com/ebullient/obsidian-task-collector/commit/6629d386de60a9476cac6d66917050ae35cccb1d)\n- Bump obsidian from 1.2.3 to 1.2.5 [`f16d791`](https://github.com/ebullient/obsidian-task-collector/commit/f16d791ecade80efcdc246d3812a876a5e305f3d)\n- Bump prettier from 2.8.7 to 2.8.8 [`ccfec8e`](https://github.com/ebullient/obsidian-task-collector/commit/ccfec8ea68f6b659c49741f3b0b0a02cf50ecc0d)\n\n#### [1.0.10](https://github.com/ebullient/obsidian-task-collector/compare/1.0.9...1.0.10)\n\n> 19 April 2023\n\n- ⚡️ Stabilize click handling in LP mode [`f5aa44f`](https://github.com/ebullient/obsidian-task-collector/commit/f5aa44f902510b0d869f27d5c471f003e1ae19ab)\n- 📝 Update readme. [`9f032c6`](https://github.com/ebullient/obsidian-task-collector/commit/9f032c62dce732ab81dbc7c9339c9ca777828cf3)\n\n#### [1.0.9](https://github.com/ebullient/obsidian-task-collector/compare/1.0.8...1.0.9)\n\n> 18 April 2023\n\n- ✨ Modal pop-up on click in Live Preview [`f3d92a0`](https://github.com/ebullient/obsidian-task-collector/commit/f3d92a010a4c693c47552aa693e27ee1df80e186)\n\n#### [1.0.8](https://github.com/ebullient/obsidian-task-collector/compare/1.0.7...1.0.8)\n\n> 18 April 2023\n\n- 🐛  Fix Mark with Prev/Next commands. Fixes [`#178`](https://github.com/ebullient/obsidian-task-collector/issues/178)\n- Bump esbuild from 0.17.16 to 0.17.17 [`36429d0`](https://github.com/ebullient/obsidian-task-collector/commit/36429d0ebe005b510178cd41f5df8d4105d49094)\n- Bump obsidian from 1.1.1 to 1.2.3 [`951e0a4`](https://github.com/ebullient/obsidian-task-collector/commit/951e0a4642b04b08092f45856396d5cffae47da5)\n\n#### [1.0.7](https://github.com/ebullient/obsidian-task-collector/compare/1.0.6...1.0.7)\n\n> 16 April 2023\n\n- 🐛 ✨ Reset All command, Fix context menus and line continuation [`#115`](https://github.com/ebullient/obsidian-task-collector/issues/115) [`#114`](https://github.com/ebullient/obsidian-task-collector/issues/114) [`#162`](https://github.com/ebullient/obsidian-task-collector/issues/162) [`#161`](https://github.com/ebullient/obsidian-task-collector/issues/161)\n\n#### [1.0.6](https://github.com/ebullient/obsidian-task-collector/compare/1.0.5...1.0.6)\n\n> 16 April 2023\n\n- Bump esbuild from 0.16.16 to 0.17.16 [`3abf377`](https://github.com/ebullient/obsidian-task-collector/commit/3abf377e0fade29e5373d4abddf1fd8ce025d604)\n- Bump @typescript-eslint/parser from 5.55.0 to 5.57.0 [`825d612`](https://github.com/ebullient/obsidian-task-collector/commit/825d612d8ac7ab0158e7f007c8f4f7accbf24b46)\n- Bump @typescript-eslint/parser from 5.54.0 to 5.54.1 [`7b1bab1`](https://github.com/ebullient/obsidian-task-collector/commit/7b1bab16729d216e1fb98e5c1f91e391d45c60e7)\n- Bump @typescript-eslint/parser from 5.49.0 to 5.50.0 [`7f38a41`](https://github.com/ebullient/obsidian-task-collector/commit/7f38a41e1dd2a7ef42f7ceea19900dda6245e237)\n- Bump @typescript-eslint/eslint-plugin from 5.49.0 to 5.50.0 [`c5c5107`](https://github.com/ebullient/obsidian-task-collector/commit/c5c5107b721236b04d9e5733646fc47d1e93a190)\n- Bump @typescript-eslint/eslint-plugin from 5.57.0 to 5.57.1 [`1f3bbaa`](https://github.com/ebullient/obsidian-task-collector/commit/1f3bbaad4528be5d53f69d6771e29ca5e07e0a56)\n- Bump @typescript-eslint/eslint-plugin from 5.55.0 to 5.57.0 [`37b68c8`](https://github.com/ebullient/obsidian-task-collector/commit/37b68c80506c1ec35e6b4a2191e9feef667ab12d)\n- Bump @typescript-eslint/eslint-plugin from 5.54.0 to 5.54.1 [`c91f1c5`](https://github.com/ebullient/obsidian-task-collector/commit/c91f1c545408918a8c4954cc4e739895fd228598)\n- Bump @typescript-eslint/eslint-plugin from 5.52.0 to 5.53.0 [`27b4ef6`](https://github.com/ebullient/obsidian-task-collector/commit/27b4ef6ff877ede7611563cbb61b4dacfbb5ac49)\n- Bump @typescript-eslint/eslint-plugin from 5.51.0 to 5.52.0 [`0e9e383`](https://github.com/ebullient/obsidian-task-collector/commit/0e9e383530be87e46e41f89a6137a73fb5ae37f8)\n- Bump @typescript-eslint/eslint-plugin from 5.54.1 to 5.55.0 [`d24ae1c`](https://github.com/ebullient/obsidian-task-collector/commit/d24ae1cffbf9b9d4ab81ef86985cdbe0d7b18f6a)\n- Bump @typescript-eslint/eslint-plugin from 5.53.0 to 5.54.0 [`a34f46c`](https://github.com/ebullient/obsidian-task-collector/commit/a34f46cc273bdc42f704cdd5fe9d54ae4e329bee)\n- Bump @typescript-eslint/eslint-plugin from 5.50.0 to 5.51.0 [`e2a935b`](https://github.com/ebullient/obsidian-task-collector/commit/e2a935be6a3cd01a050166eecabe01f0360d2fc7)\n- ✅ Test mark cycles; update task collection tests [`c5b6ba0`](https://github.com/ebullient/obsidian-task-collector/commit/c5b6ba00f7c42f7f11893a84b909bab45d3e041f)\n- Bump @typescript-eslint/parser from 5.54.1 to 5.55.0 [`14cfa73`](https://github.com/ebullient/obsidian-task-collector/commit/14cfa7397867ffc42e7f4a79110df96a64b66787)\n- Bump @typescript-eslint/parser from 5.53.0 to 5.54.0 [`a9f4a26`](https://github.com/ebullient/obsidian-task-collector/commit/a9f4a267b17e05bfe8f5b654984e29433b375609)\n- Bump @typescript-eslint/parser from 5.50.0 to 5.51.0 [`103026d`](https://github.com/ebullient/obsidian-task-collector/commit/103026d8e4b271ceb5f5094c46823c9eb0634d5d)\n- Bump eslint from 8.35.0 to 8.36.0 [`707c1da`](https://github.com/ebullient/obsidian-task-collector/commit/707c1daba48800c273129e1d0d137b3521dcd13b)\n- Bump @typescript-eslint/parser from 5.57.0 to 5.58.0 [`f1cc02b`](https://github.com/ebullient/obsidian-task-collector/commit/f1cc02b0660adaf59f6d14443245cf71ebc4f162)\n- Bump @typescript-eslint/parser from 5.51.0 to 5.53.0 [`43e4632`](https://github.com/ebullient/obsidian-task-collector/commit/43e4632392085851224ea79cb7b2412f51ec2f42)\n- Bump eslint from 8.36.0 to 8.37.0 [`ef351f2`](https://github.com/ebullient/obsidian-task-collector/commit/ef351f2e024ae65d69e221029c77b266c7dfd9a5)\n- Bump eslint from 8.34.0 to 8.35.0 [`c524eb4`](https://github.com/ebullient/obsidian-task-collector/commit/c524eb46bda59e0946a15e74d59c9830d6282dd7)\n- Bump eslint from 8.37.0 to 8.38.0 [`2123396`](https://github.com/ebullient/obsidian-task-collector/commit/2123396f9046cd0bb487e464cc9b764f2a3dda95)\n- Bump @types/node from 18.15.10 to 18.15.11 [`e36ab8e`](https://github.com/ebullient/obsidian-task-collector/commit/e36ab8e7be8637bcb51a43e8c53d479318f08572)\n- Bump prettier from 2.8.5 to 2.8.7 [`d48e64c`](https://github.com/ebullient/obsidian-task-collector/commit/d48e64c34f5f2d8cb4e1e9b99c09836861fceeb7)\n- Bump @types/node from 18.15.3 to 18.15.10 [`a5db799`](https://github.com/ebullient/obsidian-task-collector/commit/a5db7998753f794ba3060ff8ed72a8991f10ef00)\n- Bump @types/node from 18.15.1 to 18.15.3 [`28a9701`](https://github.com/ebullient/obsidian-task-collector/commit/28a97016ad02fc845b03e68cabcce86003785ebb)\n- Bump prettier from 2.8.4 to 2.8.5 [`007a76b`](https://github.com/ebullient/obsidian-task-collector/commit/007a76b4c03e70976a14a9a9326869720c709a33)\n- Bump @types/node from 18.14.6 to 18.15.1 [`a46f479`](https://github.com/ebullient/obsidian-task-collector/commit/a46f4797ea39a8e044c66f6becceb6d14f292c88)\n- Bump @types/node from 18.14.2 to 18.14.6 [`dd78ef3`](https://github.com/ebullient/obsidian-task-collector/commit/dd78ef3d2308c817ccf0287f8a986fe9e6fc656f)\n- Bump @types/node from 18.14.0 to 18.14.2 [`88314fb`](https://github.com/ebullient/obsidian-task-collector/commit/88314fb6f3061f94466be1e5874d123f2a864317)\n- Bump @types/node from 18.13.0 to 18.14.0 [`f8217e9`](https://github.com/ebullient/obsidian-task-collector/commit/f8217e9b8812bd7d0f2d346d702884e9bfccdc26)\n- Bump eslint from 8.33.0 to 8.34.0 [`98e1374`](https://github.com/ebullient/obsidian-task-collector/commit/98e1374342fb56efc4089df22190887744a0ed8e)\n- Bump prettier from 2.8.3 to 2.8.4 [`b6fd16a`](https://github.com/ebullient/obsidian-task-collector/commit/b6fd16a1e634b6057e32a370e97a9f1de1a771af)\n- Bump @types/node from 18.11.19 to 18.13.0 [`ab6ce69`](https://github.com/ebullient/obsidian-task-collector/commit/ab6ce6915909769778c79f30f66170dae018f08e)\n- Bump @types/node from 18.11.18 to 18.11.19 [`16b632e`](https://github.com/ebullient/obsidian-task-collector/commit/16b632ea08c5f70e7a384d2c53b143371f163beb)\n- Bump typescript from 4.9.4 to 4.9.5 [`31b88f8`](https://github.com/ebullient/obsidian-task-collector/commit/31b88f8348cc906aedb9c62af523cb098828985b)\n\n#### [1.0.5](https://github.com/ebullient/obsidian-task-collector/compare/1.0.4...1.0.5)\n\n> 3 February 2023\n\n#### [1.0.4](https://github.com/ebullient/obsidian-task-collector/compare/1.0.3...1.0.4)\n\n> 3 February 2023\n\n- ✨ Feat: Mark nonlist items via settings #113 [`#122`](https://github.com/ebullient/obsidian-task-collector/pull/122)\n- Bump @typescript-eslint/parser from 5.48.0 to 5.48.1 [`3789ce5`](https://github.com/ebullient/obsidian-task-collector/commit/3789ce5a24e755d0f7620ee3ea2fb1773e82b9dd)\n- Bump @typescript-eslint/eslint-plugin from 5.48.2 to 5.49.0 [`03ad9d2`](https://github.com/ebullient/obsidian-task-collector/commit/03ad9d2732f8d97f03be7a6adceb5662d29f5322)\n- Bump @typescript-eslint/eslint-plugin from 5.48.1 to 5.48.2 [`9b1dadb`](https://github.com/ebullient/obsidian-task-collector/commit/9b1dadb0f00728c5e441fe860f74eb54a8577ad7)\n- Bump @typescript-eslint/eslint-plugin from 5.48.0 to 5.48.1 [`6f00ee7`](https://github.com/ebullient/obsidian-task-collector/commit/6f00ee7edc432332e6763cab0159aad890f84f3f)\n- Bump @typescript-eslint/parser from 5.48.1 to 5.49.0 [`54e73d4`](https://github.com/ebullient/obsidian-task-collector/commit/54e73d473d8b40c66cf3643eacd4a2b38a855268)\n- 🐛 Fix iteration of task sections [`64e44c1`](https://github.com/ebullient/obsidian-task-collector/commit/64e44c10f201610213ffb70a549d8ac8c759f912)\n- Bump tslib from 2.4.1 to 2.5.0 [`ba503e9`](https://github.com/ebullient/obsidian-task-collector/commit/ba503e90efa32f3816d7c7f128d717f50513846f)\n- Bump eslint from 8.32.0 to 8.33.0 [`c35ef33`](https://github.com/ebullient/obsidian-task-collector/commit/c35ef33ba8ac0162def8351c3711f5ce56b83615)\n- Bump prettier from 2.8.2 to 2.8.3 [`9bcdc31`](https://github.com/ebullient/obsidian-task-collector/commit/9bcdc319f626af833d0983fb5ddbc4c70089c102)\n- Bump eslint from 8.31.0 to 8.32.0 [`37bc3b5`](https://github.com/ebullient/obsidian-task-collector/commit/37bc3b578b9e8fa933bc6c53bb0f90a829f2e7ac)\n- 📝 Update manifest description [`10c4be0`](https://github.com/ebullient/obsidian-task-collector/commit/10c4be02d1287c19ae4ddaad1d271ee2f56a5067)\n\n#### [1.0.3](https://github.com/ebullient/obsidian-task-collector/compare/1.0.2...1.0.3)\n\n> 15 January 2023\n\n#### [1.0.2](https://github.com/ebullient/obsidian-task-collector/compare/1.0.1...1.0.2)\n\n> 13 January 2023\n\n- 📝 Update docs and wording for settings [`f9e6702`](https://github.com/ebullient/obsidian-task-collector/commit/f9e6702149cb5bfb5a5557eb3732c2ff3710d8ad)\n- 🐛 Use `## Example` for Area heading placeholder [`e3c29b6`](https://github.com/ebullient/obsidian-task-collector/commit/e3c29b661041ebbef0107a01dc7eaa841cd3383a)\n\n#### [1.0.1](https://github.com/ebullient/obsidian-task-collector/compare/1.0.0...1.0.1)\n\n> 12 January 2023\n\n- 🐛 Restore cursor/selection. Fixes [`#62`](https://github.com/ebullient/obsidian-task-collector/issues/62)\n- ♻️ 🐛 ✨ Cycle through marks; clean up icons [`2dd60e0`](https://github.com/ebullient/obsidian-task-collector/commit/2dd60e03e0e7143fee48ae0194813938e352dfe4)\n- ♻️ 🐛 Debounce settings; clean up menus and debug [`3566e05`](https://github.com/ebullient/obsidian-task-collector/commit/3566e054bea797650938696f28dfb0c80742ccc4)\n\n### [1.0.0](https://github.com/ebullient/obsidian-task-collector/compare/0.8.2...1.0.0)\n\n> 11 January 2023\n\n- 🔥 ♻️ ✨ Group-oriented append/remove settings [`fb91979`](https://github.com/ebullient/obsidian-task-collector/commit/fb91979b32596720131230d6ca3c4a64d9edfe90)\n- Bump esbuild from 0.16.12 to 0.16.16 [`af8e537`](https://github.com/ebullient/obsidian-task-collector/commit/af8e5374dc69eaba8ba3c414e5008f354b8d9bb2)\n- Bump prettier from 2.8.1 to 2.8.2 [`b1848cf`](https://github.com/ebullient/obsidian-task-collector/commit/b1848cfc1d933f6b5320360d6160a6bb3e87a0b2)\n\n#### [0.8.2](https://github.com/ebullient/obsidian-task-collector/compare/0.8.1...0.8.2)\n\n> 8 January 2023\n\n- 🐛 Fix construction of completion expressions. Resolves [`#99`](https://github.com/ebullient/obsidian-task-collector/issues/99)\n- Bump esbuild from 0.15.18 to 0.16.4 [`3a007d8`](https://github.com/ebullient/obsidian-task-collector/commit/3a007d8ba8bb6614f03abdb0421474e92ec143a1)\n- Bump esbuild from 0.13.12 to 0.15.13 [`da1d98e`](https://github.com/ebullient/obsidian-task-collector/commit/da1d98e5c370118814b79d7a8c53a04dca94aa26)\n- Bump esbuild from 0.16.10 to 0.16.12 [`0bf9343`](https://github.com/ebullient/obsidian-task-collector/commit/0bf9343bdc81995e3060f0520de3274fe15a98c5)\n- Bump esbuild from 0.16.9 to 0.16.10 [`2bdbab5`](https://github.com/ebullient/obsidian-task-collector/commit/2bdbab55c52f7405b5c038892bd132e5412abcea)\n- Bump esbuild from 0.16.4 to 0.16.9 [`5a81ccb`](https://github.com/ebullient/obsidian-task-collector/commit/5a81ccb28b49dada1717aa2fb1a052c80a28f3fb)\n- Bump esbuild from 0.15.16 to 0.15.18 [`354d5b7`](https://github.com/ebullient/obsidian-task-collector/commit/354d5b71d928cf23d744ec7520113d7d9ccdd4e7)\n- Bump esbuild from 0.15.15 to 0.15.16 [`5659ace`](https://github.com/ebullient/obsidian-task-collector/commit/5659ace66511a7690ad9a1ca2d575635ce874ea8)\n- Bump esbuild from 0.15.13 to 0.15.15 [`2046bdd`](https://github.com/ebullient/obsidian-task-collector/commit/2046bdde108f4a5cf77c82e66c1f480662941f54)\n- Bump obsidian from 0.12.17 to 0.16.3 [`da46561`](https://github.com/ebullient/obsidian-task-collector/commit/da46561a6a080d4843f6ee2c8ac6f02ad2d56140)\n- Bump moment from 2.29.1 to 2.29.4 [`0458bb4`](https://github.com/ebullient/obsidian-task-collector/commit/0458bb4ba2a1496ae50463909cdb998c86507b20)\n- Bump json5 from 2.2.0 to 2.2.3 [`9d2d220`](https://github.com/ebullient/obsidian-task-collector/commit/9d2d220265d9eb56b7a5c2b9745ddaf9d3235718)\n- Bump node-fetch from 2.6.6 to 2.6.7 [`d06b5ac`](https://github.com/ebullient/obsidian-task-collector/commit/d06b5acf4a7780f9c7620b1399385bb508b1991d)\n- Bump prettier from 2.5.1 to 2.7.1 [`e761f60`](https://github.com/ebullient/obsidian-task-collector/commit/e761f60e2c89a96df99adbb21b0f0c15d4b8b2ad)\n- Bump minimist from 1.2.5 to 1.2.7 [`fab58c3`](https://github.com/ebullient/obsidian-task-collector/commit/fab58c3f21ba9214d702fb439053c1adafd57234)\n- Bump @types/node from 18.11.17 to 18.11.18 [`4abb8a9`](https://github.com/ebullient/obsidian-task-collector/commit/4abb8a95d43205f9f1ee68d90abd97095c2ed242)\n- Bump obsidian from 0.16.3 to 1.1.1 [`5ab8339`](https://github.com/ebullient/obsidian-task-collector/commit/5ab8339a231e34ff797effb6c0712387c4f2ea85)\n- Bump @types/node from 18.11.13 to 18.11.17 [`3604a06`](https://github.com/ebullient/obsidian-task-collector/commit/3604a0650ee3ff98deef8f2192dec798b4e523f7)\n- Bump typescript from 4.9.3 to 4.9.4 [`5ea8210`](https://github.com/ebullient/obsidian-task-collector/commit/5ea8210f0b9c867a6ccd2be5031c9f0d924ed1b2)\n- Bump @types/node from 18.11.10 to 18.11.13 [`23c76d3`](https://github.com/ebullient/obsidian-task-collector/commit/23c76d33190bd4a3f817d6710a80409726b2fbc5)\n- Bump prettier from 2.8.0 to 2.8.1 [`15f0306`](https://github.com/ebullient/obsidian-task-collector/commit/15f03066e61e01132a77d3b15f0d594ebd329867)\n- Bump @types/node from 18.11.9 to 18.11.10 [`a2ff35c`](https://github.com/ebullient/obsidian-task-collector/commit/a2ff35c6928eadcde4e05bc74b6f2197cada436b)\n- Bump prettier from 2.7.1 to 2.8.0 [`0374c1d`](https://github.com/ebullient/obsidian-task-collector/commit/0374c1dbd6c77f49c78bad857c39010b0041bed0)\n- Bump typescript from 4.8.4 to 4.9.3 [`ada95a5`](https://github.com/ebullient/obsidian-task-collector/commit/ada95a59e9d286e5379078e5f1fe17bf68d42b7a)\n- Bump builtin-modules from 3.2.0 to 3.3.0 [`7d4f4b2`](https://github.com/ebullient/obsidian-task-collector/commit/7d4f4b2ee0d5a27d219e8c405d565c964c8eea96)\n- Bump tslib from 2.3.1 to 2.4.1 [`9d40d36`](https://github.com/ebullient/obsidian-task-collector/commit/9d40d36b146edea96f4f47da23aa04d99c515c8b)\n- Bump @types/node from 14.17.21 to 18.11.9 [`b09b0f1`](https://github.com/ebullient/obsidian-task-collector/commit/b09b0f1b7995f9281e5df99e4f1fd0aaee8b903b)\n- Bump typescript from 4.4.4 to 4.8.4 [`e0b3533`](https://github.com/ebullient/obsidian-task-collector/commit/e0b353378505bfc8645c7fd5586ac11754f5001a)\n- 🔧 GH Actions dependencies [`f65120c`](https://github.com/ebullient/obsidian-task-collector/commit/f65120cfe0abcea7aeb772392ca266eabd251827)\n- Bump minimatch from 3.0.4 to 3.1.2 [`3b117c4`](https://github.com/ebullient/obsidian-task-collector/commit/3b117c43ee3bb296d80dec25f6bbd7b0df5f56b3)\n- 🔧 Dependabot updates [`2202b32`](https://github.com/ebullient/obsidian-task-collector/commit/2202b32e7ac9d3bf106bae4b398cbed53c8a889c)\n- 🐛 Fix checking sub-items in preview mode [`a55090f`](https://github.com/ebullient/obsidian-task-collector/commit/a55090f2907c2cb42857b91afd1f7084f8c40746)\n\n#### [0.8.1](https://github.com/ebullient/obsidian-task-collector/compare/0.8.0...0.8.1)\n\n> 9 September 2022\n\n- 🎨 icon size for 0.16 [`a9f6c7d`](https://github.com/ebullient/obsidian-task-collector/commit/a9f6c7d4f7e40939661465eecf08b922039986e4)\n\n#### [0.8.0](https://github.com/ebullient/obsidian-task-collector/compare/0.7.8...0.8.0)\n\n> 27 April 2022\n\n- ✨ ♻️ Support append/reset for all marks [`8631974`](https://github.com/ebullient/obsidian-task-collector/commit/8631974fd1de1c3e8663d3d4a8b940d6f121fdbf)\n- ✨ Add setting: use modal on left-click of checkbox [`1666223`](https://github.com/ebullient/obsidian-task-collector/commit/16662231a29f67e5388bb55cd7d6c65744e5d6e0)\n- 🎨 Define API: query task markers, invoke the modal. [`d8ae11a`](https://github.com/ebullient/obsidian-task-collector/commit/d8ae11ae42cccbe7449cc4867c74df064c562790)\n\n#### [0.7.8](https://github.com/ebullient/obsidian-task-collector/compare/0.7.7...0.7.8)\n\n> 23 April 2022\n\n- 🐛 Preserve strict line-ending spaces [`1e26920`](https://github.com/ebullient/obsidian-task-collector/commit/1e26920bb118fdee0e5c92789cf72b9670c436d0)\n- ✨ Mark tasks in callouts [`319239e`](https://github.com/ebullient/obsidian-task-collector/commit/319239efc9f01f0770802003a416af3d4c09bb23)\n- ✨ Allow 'X' to be disabled/hidden [`0986d39`](https://github.com/ebullient/obsidian-task-collector/commit/0986d396032168a0970f93f97b9e6d0a3dec1977)\n- 🐛  Pop-up modal spacing (again) [`b1a436b`](https://github.com/ebullient/obsidian-task-collector/commit/b1a436b7bba7d1fbe63299d021943b05643c25fc)\n\n#### [0.7.7](https://github.com/ebullient/obsidian-task-collector/compare/0.7.6...0.7.7)\n\n> 23 March 2022\n\n- 🐛 Errant selection of line. Resolves [`#40`](https://github.com/ebullient/obsidian-task-collector/issues/40)\n- 🎨 whitespace [`8ab9b11`](https://github.com/ebullient/obsidian-task-collector/commit/8ab9b11716ce5172e602b8439796d92253716029)\n- Update li style to match changes to default theme [`196d5f1`](https://github.com/ebullient/obsidian-task-collector/commit/196d5f116fa7020b69593fc3635d2d3c0d0905a1)\n\n#### [0.7.6](https://github.com/ebullient/obsidian-task-collector/compare/0.7.5...0.7.6)\n\n> 1 February 2022\n\n- 🐛 Use additional specifiers in pop-up [`880fa10`](https://github.com/ebullient/obsidian-task-collector/commit/880fa10e29b0b3a4a1cdfe69d1d074cd95bce823)\n\n#### [0.7.5](https://github.com/ebullient/obsidian-task-collector/compare/0.7.4...0.7.5)\n\n> 17 January 2022\n\n- 🐛 Use a minimum config string [`f899275`](https://github.com/ebullient/obsidian-task-collector/commit/f899275d7a150221d292885ee4441b04cbb7ed4a)\n\n#### [0.7.4](https://github.com/ebullient/obsidian-task-collector/compare/0.7.3...0.7.4)\n\n> 17 January 2022\n\n- 🐛 Cope with empty incomplete tasks setting [`76d85ef`](https://github.com/ebullient/obsidian-task-collector/commit/76d85ef62a2591dadc3de873ad1f3bc04e9151a8)\n\n#### [0.7.3](https://github.com/ebullient/obsidian-task-collector/compare/0.7.2...0.7.3)\n\n> 14 January 2022\n\n- ✨ Use bksp to remove checkbox [`04dcaf8`](https://github.com/ebullient/obsidian-task-collector/commit/04dcaf8b0d842228eea8941eef12d8bba1ba3d60)\n\n#### [0.7.2](https://github.com/ebullient/obsidian-task-collector/compare/0.7.1...0.7.2)\n\n> 14 January 2022\n\n- 🐛 revert change (read instead of cachedRead) [`b7e11d6`](https://github.com/ebullient/obsidian-task-collector/commit/b7e11d6924f00aa764616db76cb9788e70446f2e)\n\n#### [0.7.1](https://github.com/ebullient/obsidian-task-collector/compare/0.7.0...0.7.1)\n\n> 14 January 2022\n\n- ✨ Convert and Mark ordinary list items [`36c15a0`](https://github.com/ebullient/obsidian-task-collector/commit/36c15a0845cd36d96c27864cc131469064e83e5f)\n\n#### [0.7.0](https://github.com/ebullient/obsidian-task-collector/compare/0.6.6...0.7.0)\n\n> 13 January 2022\n\n- ✨ Complete tasks in preview mode [`80f9575`](https://github.com/ebullient/obsidian-task-collector/commit/80f95751eb7c358cde5b14163c50b84ce1906e93)\n- add prettier to npm scripts [`11561eb`](https://github.com/ebullient/obsidian-task-collector/commit/11561eb95c83c74de6f6fd64073c8af19b3048e2)\n- run prettier on plugin files [`1a64a26`](https://github.com/ebullient/obsidian-task-collector/commit/1a64a26db5d6dfd61be7115f972d03e27534e229)\n- ✅ Update tests; Mark either complete/incomplete [`3c8e576`](https://github.com/ebullient/obsidian-task-collector/commit/3c8e576e7a1a2f80e20c14c9f759086983571a6d)\n- 🔧 json whitespace [`d4a31bf`](https://github.com/ebullient/obsidian-task-collector/commit/d4a31bffe592cdd490c350f2d567716b897a5d2b)\n\n#### [0.6.6](https://github.com/ebullient/obsidian-task-collector/compare/0.6.5...0.6.6)\n\n> 10 January 2022\n\n- 🐛 support block-ids with numbers 🤦‍♀️ [`19da48c`](https://github.com/ebullient/obsidian-task-collector/commit/19da48c0f85599f23a9957bb66b75ccdc74babc9)\n\n#### [0.6.5](https://github.com/ebullient/obsidian-task-collector/compare/0.6.4...0.6.5)\n\n> 9 January 2022\n\n- 🚸 Rename/Reorder settings [`04d3f0b`](https://github.com/ebullient/obsidian-task-collector/commit/04d3f0b64a3a9bcf9253a21373425425330e36e7)\n\n#### [0.6.4](https://github.com/ebullient/obsidian-task-collector/compare/0.6.3...0.6.4)\n\n> 9 January 2022\n\n- ✨💥 Modal to select item mark [`a38d683`](https://github.com/ebullient/obsidian-task-collector/commit/a38d683ecdcddc80f3e21508fc83d109ff6f598c)\n- 🚸 Preserve trailing block ids with completion date [`ab17500`](https://github.com/ebullient/obsidian-task-collector/commit/ab17500db96e9656387d3d240cc161a0c87d5a22)\n\n#### [0.6.3](https://github.com/ebullient/obsidian-task-collector/compare/0.6.2...0.6.3)\n\n> 7 January 2022\n\n- Fix processing of date formats containing square brackets [`#18`](https://github.com/ebullient/obsidian-task-collector/pull/18)\n- feat: optional remove checkbox when moving tasks [`#14`](https://github.com/ebullient/obsidian-task-collector/pull/14)\n- ✨ Run commands that mark all tasks in reader mode [`205e2a1`](https://github.com/ebullient/obsidian-task-collector/commit/205e2a19a227d61194fefcad0a114d93db685df6)\n- 🐛 Handle moment formats containing [] [`145468b`](https://github.com/ebullient/obsidian-task-collector/commit/145468bfe2c2b1f66d9da7c406d805b3e57e7cb0)\n- 🚸 Revise setting descriptions [`8093632`](https://github.com/ebullient/obsidian-task-collector/commit/80936322be42d81614749edde682e8182f57b777)\n- 🚸 quieter logs [`005f81d`](https://github.com/ebullient/obsidian-task-collector/commit/005f81d5def3f3b23e4fca670dc40387fc37e8d8)\n\n#### [0.6.2](https://github.com/ebullient/obsidian-task-collector/compare/0.6.1...0.6.2)\n\n> 7 January 2022\n\n- fix: recognize tab indentation for sub-bullets [`#12`](https://github.com/ebullient/obsidian-task-collector/pull/12)\n- Rename files; right-click menu behavior for reset [`#9`](https://github.com/ebullient/obsidian-task-collector/pull/9)\n- I make ridiculous things. ;) [`d91c38c`](https://github.com/ebullient/obsidian-task-collector/commit/d91c38c3d2f610fa5555337a4c5e334bd6a367d8)\n- fix: recognise tabs identation for sub-notes [`d57c2bf`](https://github.com/ebullient/obsidian-task-collector/commit/d57c2bff29c2bcaccea1a583ab38d389f9f32818)\n- Update FUNDING.yml [`f3cb183`](https://github.com/ebullient/obsidian-task-collector/commit/f3cb183b4221a8890a4912c1c3867a9bfa177e5e)\n\n#### [0.6.1](https://github.com/ebullient/obsidian-task-collector/compare/0.6.0...0.6.1)\n\n> 9 October 2021\n\n- Mark/Reset all in selection [`#6`](https://github.com/ebullient/obsidian-task-collector/issues/6)\n\n#### [0.6.0](https://github.com/ebullient/obsidian-task-collector/compare/0.5.0...0.6.0)\n\n> 9 October 2021\n\n- Mobile does not like negative lookbehind [`3e86da4`](https://github.com/ebullient/obsidian-task-collector/commit/3e86da423cf3023fc4f1d9f6b50d6620a11f1b9a)\n- Additional notes re: versions [`6d83c78`](https://github.com/ebullient/obsidian-task-collector/commit/6d83c78d38f5ddfbf50a69fa4cf38f588360b8d2)\n\n#### [0.5.0](https://github.com/ebullient/obsidian-task-collector/compare/0.4.0...0.5.0)\n\n> 7 October 2021\n\n- Reset single task. Resolves [`#4`](https://github.com/ebullient/obsidian-task-collector/issues/4)\n- Parse hours and minutes; test updates; resolves [`#2`](https://github.com/ebullient/obsidian-task-collector/issues/2)\n- Release 0.5.0 with reset single task support [`5e3cd4b`](https://github.com/ebullient/obsidian-task-collector/commit/5e3cd4be134d6ef6f9ee705fbf545840f4ffb75c)\n- Add a note about icons [`f89cd2e`](https://github.com/ebullient/obsidian-task-collector/commit/f89cd2e312fa44b711e1785715dc6e4ca67ff5aa)\n\n#### [0.4.0](https://github.com/ebullient/obsidian-task-collector/compare/0.3.0...0.4.0)\n\n> 7 October 2021\n\n- Tests [`8138adb`](https://github.com/ebullient/obsidian-task-collector/commit/8138adb44bb24b35a22b7f21780df8ca1148d660)\n- eslint [`a822c60`](https://github.com/ebullient/obsidian-task-collector/commit/a822c60385a81b02c7d2188418f4c6a95a81d192)\n- mark/clear all; context-menu icons; refactoring [`55c75dc`](https://github.com/ebullient/obsidian-task-collector/commit/55c75dc43e6c0624f31191d098179f3fc2c45150)\n- Release 0.4.0 [`94b0755`](https://github.com/ebullient/obsidian-task-collector/commit/94b0755005c749ce61dc5061d394630c0903a10a)\n- Readme + menu fix [`2c1de83`](https://github.com/ebullient/obsidian-task-collector/commit/2c1de8382998cab60a6745701e4d1d4faa44c9b8)\n- Clarifications [`492a751`](https://github.com/ebullient/obsidian-task-collector/commit/492a751eaf8bc5a263089f443f6be7cf19871fd9)\n- find missing dep for ci [`8d8540f`](https://github.com/ebullient/obsidian-task-collector/commit/8d8540f096d591da5271a7281816d8a1e6616cb7)\n- Add shields.io badges [`290bf15`](https://github.com/ebullient/obsidian-task-collector/commit/290bf150a89bceb2cc1890929a763f98c26a5c35)\n\n#### [0.3.0](https://github.com/ebullient/obsidian-task-collector/compare/0.2...0.3.0)\n\n> 29 September 2021\n\n- Rename plugin; Right-click menu items [`3ca0a4d`](https://github.com/ebullient/obsidian-task-collector/commit/3ca0a4d9ca95261c372e9461c016ca600db4b823)\n- Add support for canceled items [`bf6c39c`](https://github.com/ebullient/obsidian-task-collector/commit/bf6c39c98010cd02336b2f0f853c5f67137eea6d)\n\n#### [0.2](https://github.com/ebullient/obsidian-task-collector/compare/0.1...0.2)\n\n> 29 September 2021\n\n- add repository; commit package-lock [`d88a0c7`](https://github.com/ebullient/obsidian-task-collector/commit/d88a0c7ec867aa89ad6022792ebeb96ac6547f53)\n- Rename plugin; Right-click menu items [`91c4a97`](https://github.com/ebullient/obsidian-task-collector/commit/91c4a972514702f3b595ff7accd4ed345281d790)\n\n#### 0.1\n\n> 29 September 2021\n\n- Functional, but no tests [`5bb74f1`](https://github.com/ebullient/obsidian-task-collector/commit/5bb74f12f74ce1c2b07e203376e2e041c555c714)\n- Initial commit [`300d072`](https://github.com/ebullient/obsidian-task-collector/commit/300d072bf8801f451e8992c836479863a42a2e29)\n- Create LICENSE [`b4209d2`](https://github.com/ebullient/obsidian-task-collector/commit/b4209d281ef8667b1cd135950b1630acb492068c)\n- CI [`0c9fb18`](https://github.com/ebullient/obsidian-task-collector/commit/0c9fb184a3e6ba62a46e5ffd8f61d0ed1f930d8b)\n- Add note about behavior of nested elements [`441f176`](https://github.com/ebullient/obsidian-task-collector/commit/441f176788d4fb770b7ee53d6b4543dce8587d6f)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Erin Schnabel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Task Collector\n\n[![GitHub tag (Latest by date)](https://img.shields.io/github/v/tag/ebullient/obsidian-task-collector)](https://github.com/ebullient/obsidian-task-collector/releases) ![GitHub all releases](https://img.shields.io/github/downloads/ebullient/obsidian-task-collector/total?color=success)\n\nYet another plugin to manage completed tasks, but this one has a task-completion modal to go with it!\n\n- [Configuration reference](docs/README.md)\n- [Commands](#commands)\n- [Marking tasks](#marking-tasks)\n- [Task mark selection](#task-mark-selection)\n\n## Installation\n\n1. Go to **Community Plugins** in your [Obsidian](https://www.obsidian.md) settings and **disable** Safe Mode\n2. Click on **Browse** and search for \"task collector\"\n3. Click install\n4. Use the toggle on the community plugins tab to enable the plugin.\n\n## TL;DR for marking tasks\n\n1. Open Plugin settings\n2. Edit the task marks for the `default` group to include any characters you use for incomplete tasks.\n3. Edit the task marks for the `complete` group to include any characters you use for completed tasks.\n4. _(optional)_ Scroll down to find **[Menus and modals](docs/README.md#menus-and-modals]**, and enable additional context menus.\n\n![Task Completion](https://user-images.githubusercontent.com/808713/148706433-34d21845-a441-428d-a24c-380c6db457c7.gif)\n\n## Commands\n\nTask Collector registers a few commands by default:\n\n### (TC) Mark task\n\nThis is a hot-key bindable command for edit-mode that opens a modal dialog for [task mark selection](#task-mark-selection).\n\nThat snappy completion status you wanted is just a few taps away!\n\nFor Reading and Live Preview modes, see [menus and modals](docs/README.md#menus-and-modals).\n\n### (TC) Collect tasks\n\nTask Collector can gather and regroup different kinds of tasks into different areas within a note.\n\n1. [Enable \"Task collection\"](docs/README.md#general-options)\n2. For the group of tasks that you would like to gather:\n    - Set an Area heading\n\nSee [Task collection](docs/TaskCollection.md) for details.\n\n### '(TC) Mark previous' and '(TC) Mark previous'\n\nIf you configure a [Task mark cycle](docs/README.md#general-options), two commands will be registered that allow you to cycle forward or backward through the mark sequence.\n\n## Marking tasks\n\nWhen Task Collector marks a task:  \n\n1. _(optional)_ Matched text appended by the previous mark will be removed\n2. The task will be marked with the new mark\n3. _(optional)_ The remove pattern configured for the new mark will be applied to remove text from the task.\n4. If there is an append date format, a formatted time stamp will be appended to the task.  \n\n## Task mark selection\n\nUse the `(TC) Mark task` command or right-click context menu (if enabled) to select a task mark using a quick pop-up modal.\n\nThe modal contains marks configured in [task groups](docs/README.md#task-groups).\n\n- The top row contains marks for [completed tasks](#completed-tasks).\n- All other configured marks appear in the next row (or rows, as the collection will wrap).\n\n**Notes**:\n\n- Select a value with your mouse, or the keyboard.\n- The selected value will determine follow-on actions, see [marking tasks](#marking-tasks).\n- Any character you choose will work. If it doesn't match a configured mark, the behavior from the `default` group will apply.\n\n> [!TIP]\n> Is the pop-up not showing what you expect? Review the marks defined in your [task groups](docs/README.md#task-groups).\n\n ---\n\n## Why `mark`?\n\nThere are not enough words. I chose _mark_, because it can mean the symbol itself (a mark), and also active action (you mark the task). The checkbox is the outer thing. Sometimes these characters indicate the status of a task, and some folks use these to mean something else entirely. So, `mark` it is.\n\nNaming things is hard.\n\n### Completed tasks\n\nWhy is there is a toggle for completed tasks if these are all just marks?\n\nThe complete toggle is essentially an indicator.\n\n- In the mark selection dialog, those used to complete tasks appear in the top row. All others are (sorted) in the bottom row(s).\n\n- Task Collector has an API that other plugins or your own scripts can use to determine if a mark indicates a complete task or not: `app.plugins.plugins[\"obsidian-task-collector\"]\n\n---\n\n## Credits\n\n- [Completed Area Plugin](https://github.com/DahaWong/obsidian-completed) -- general premise of moving completed tasks to a different area within the note (delimited by a heading).\n- [JeppeKlitgaard/ObsidianTweaks](https://github.com/JeppeKlitgaard/ObsidianTweaks/) -- simple/clear event triggers\n- [ivan-lednev/obsidian-task-archiver](https://github.com/ivan-lednev/obsidian-task-archiver) -- Treatment of sub-elements\n- [Darakah/obsidian-timelines](https://github.com/Darakah/obsidian-timelines) -- Editor select/replace\n- [Customizable Sidebar](https://github.com/phibr0/obsidian-customizable-sidebar) -- GH Action\n- [Dataview](https://github.com/blacksmithgu/obsidian-dataview) -- Jest/Testing\n\n<a href=\"https://www.buymeacoffee.com/ebullient\" target=\"_blank\"><img src=\"https://cdn.buymeacoffee.com/buttons/v2/default-blue.png\" alt=\"Buy Me A Coffee\" style=\"height: 60px !important;width: 217px !important;\" ></a>\n"
  },
  {
    "path": "biome.json",
    "content": "{\n  \"$schema\": \"https://biomejs.dev/schemas/2.0.4/schema.json\",\n  \"vcs\": {\n    \"enabled\": false,\n    \"clientKind\": \"git\",\n    \"useIgnoreFile\": true\n  },\n  \"files\": {\n    \"ignoreUnknown\": false,\n    \"includes\": [\"src/**\", \"test/**\", \"!test/mocks/**\"]\n  },\n  \"formatter\": {\n    \"enabled\": true,\n    \"indentWidth\": 4,\n    \"useEditorconfig\": true\n  },\n  \"assist\": {\n    \"actions\": {\n      \"source\": {\n        \"organizeImports\": \"on\"\n      }\n    }\n  },\n  \"linter\": {\n    \"enabled\": true,\n    \"rules\": {\n      \"recommended\": true,\n      \"style\": {\n        \"noParameterAssign\": \"error\",\n        \"useAsConstAssertion\": \"error\",\n        \"useDefaultParameterLast\": \"error\",\n        \"useEnumInitializers\": \"error\",\n        \"useSelfClosingElements\": \"error\",\n        \"useSingleVarDeclarator\": \"error\",\n        \"noUnusedTemplateLiteral\": \"error\",\n        \"useNumberNamespace\": \"error\",\n        \"noInferrableTypes\": \"error\",\n        \"noUselessElse\": \"error\"\n      }\n    }\n  },\n  \"javascript\": {\n    \"formatter\": {\n      \"quoteStyle\": \"double\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/AppendingText.md",
    "content": "# Append text anywhere\n\nYou can define a [task group](README.md#task-groups) named `text` to create a string you can append to any line of text (not just tasks).\n\nThis can be useful if you want to configure a quick append string that matches the date format you use for your tasks.\n\nAppending a date and removing matching text work in the same way for both tasks and general text, however, the appended date text is not removed if that line of text is later converted into a task.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Task Collector Configuration\n\nThis doc will give a high level overview of how Task Collector works, and its core configuration elements. \n\n- [General Options](#general-options)\n- [Task Groups](#task-groups)\n- [Menus and modals](#menus-and-modals)\n\nFor readability, we've broken some things into their own docs:\n\n- [Task Collection](TaskCollection.md)\n- [Appending text to lines (not tasks)](AppendingText.md)\n\nOther references: \n\n- [Why marks?](../README.md#why-mark)\n- [Task mark selection](../README.md#task-mark-selection)\n- [Marking tasks](../README.md#marking-tasks)\n\n---\n\n## General options\n\n- **Toggle: Task collection**  \n    This option enables [Task collection](TaskCollection.md). Additional settings will be available if this is enabled.\n    - *default*: disabled  \n\n<!-- -->\n- **Define task mark cycle** _(optional)_  \n    Use this option to specify a string of marks that can be iterated sequentially using `(TC) Mark previous` and `(TC) Mark next` commands.\n    - *default*: (empty string, disabled)\n    - **Notes**:\n      - `(TC) Mark previous` and `(TC) Mark next` are not registered unless or until a task mark cycle is defined.\n\n<!-- -->\n- **Toggle: Convert non-list lines**  (✨ 1.0.4)\n    Convert lines that are not tasks into tasks when you mark them.\n    - *default*: disabled\n\n<!-- -->\n- **Skip matching sections** _(optional)_  (✨ 1.0.13)\n    When collecting tasks, skip any sections that match the specified pattern.\n    - *default*: (empty string, disabled)\n    - **Notes**:\n        - *Be careful!* Test your expression before using it. There are several [online](https://www.regextester.com/) [tools](https://regex.observepoint.com/) that can help.\n\n<!-- -->\n\n---\n\n## Task groups\n\nOne or more task marks can be configured together in a group.\n\n- `default` is a special group that can not be deleted. Any mark that is not included in any other group uses the default group settings.\n- `text` is a special name used for [appending text](AppendingText.md).\n\n### Task group configuration\n\n- **Group name** _(required)_  \n    Each group should have a unique name. The names are significant only as an identifier.\n  - **Toggle: Complete**  \n        Enable this toggle if this group of marks indicate [completed tasks](../README.md#completed-tasks).\n\n<!-- -->\n- **Task marks** _(required)_  \n    Tasks are specified as a contiguous string: ` !/?r`\n    - **Notes**:\n      - A mark can only belong to one group. \n      - A `space` is a valid character. If a space is present, `⎵` is drawn (CSS) as an eye-catcher.\n      - The string will sort itself as you add characters to it.\n\n<!-- -->  \n- **Append date to selected task(s)**  _(optional)_\n    - *default*: (empty string, disabled)\n    - *example*: `[(]YYYY-MM-DD[)]`, results in `(2021-09-27)`\n    - **Notes**:\n      - When a [moment.js date format](https://momentjs.com/docs/#/displaying/format/) is specified, the current date/time will be appended to the task text.\n      - Use square brackets to surround content that is not part of the format string. \n      - When working with dataview-friendly annotations, for example, your format string should look something like this: `[[completion::]YYYY-MM-DD[]]`.\n      - See [marking tasks](../README.md#marking-tasks)\n\n<!-- -->  \n- **Remove text matching pattern**  \n    Remove text matching this [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) from the task text. \n    - *default*: (empty string, disabled)\n    - *example*: `#(task|todo)` (remove #task or #todo tags)\n    - **Notes**:\n        - *Be careful!* Test your expression before using it. There are several [online](https://www.regextester.com/) [tools](https://regex.observepoint.com/) that can help.\n        - Aside from an immediate \"undo\", this is not a reversible operation.\n        - See [marking tasks](../README.md#marking-tasks)\n\n<!-- -->    \n- **Toggle: Register '(TC) Mark with... ' command**  \n    Register a command _for each mark in the group_.  \n    - *default*: `false`\n    - **Notes**:\n      - Commands can be bound to hot-keys... \n\n<!-- -->    \n- **Toggle: Add '(TC) Mark with... ' menu item**  \n    Register a right-click context menu item _for each mark in the group_. \n    - *default*: `false`\n\n<!-- -->    \n- **Area heading** _(if [task collection](TaskCollection.md) is enabled)_  \n    Matching marked items will be inserted under the specified heading (most recent at the top).  \n    - *default*: (empty string, disabled)\n    - **Notes**:\n        - The area heading must be defined to enable collection for the group.\n        - See [task collection](TaskCollection.md) for details.\n\n<!-- -->    \n- **Toggle: Remove checkbox**  _(if [task collection](TaskCollection.md) is enabled)_  \n    Remove the checkbox marked tasks during the move to the configured area. \n    - *default*: false\n\n---\n\n## Menus and modals\n\n- **Toggle: Prompt on checkbox click in Reading or Live preview**:  \n    When true, the [Task mark selection dialog](../README.md#task-mark-selection) will be opened when you click on a checkbox in Reading or Live Preview mode.\n    - *default*: `false`\n    - **Notes**:\n      - This may conflict with a similar handler from the [Tasks](https://obsidian-tasks-group.github.io/obsidian-tasks/) plugin. Use one or the other, not both.\n\n<!-- -->    \n- **Toggle: Add '(TC) Mark task' menu item** (✨ 0.6.4)\n    Add an item to the right-click menu in edit mode to mark the task _on the current line (or within the current selection)_. It will open the [Task mark selection dialog](../README.md#task-mark-selection).\n    - *default*: `false`\n\n<!-- -->    \n- **Toggle: Add '(TC) Collect tasks' menu item** _(if [task collection](docs/../TaskCollection.md) is enabled)_  \n    Add an item to the right-click menu to collect tasks in the current note.\n    - *default*: `false`\n\n"
  },
  {
    "path": "docs/TaskCollection.md",
    "content": "# Task collection\n\nTask collection works on the tasks within a note. \n\nTasks that match a mark configured for collection will be gathered into the designated area of the note.\n\nThe `(TC) Collect Tasks` command or menu item triggers the following process:\n\n1. Any missing Area headings will be added to the end of the document.\n2. The document will be scanned from top to bottom to isolate discovered Area headings and their target sections.\n3. General note content is scanned from top to bottom for tasks that should be collected. Each discovered task is moved to a block of pending tasks associated with the closest matching Area header ([where do tasks go](#where-do-tasks-go)?).\n4. Collection target sections are then scanned (in order of appearance in the doc) for tasks that belong in a different section. Those are then added to the block of pending tasks for the closest matching Area header.\n5. Finally, the document is reassembled. Area headings and their target sections are placed back into the document, with the pending block for each appearing immediately under the section header (most recent will be first).\n \n \nIf you walk through the above with the following example, you should end up with the tasks in the same order:\n\n<table>\n<tr><th>Before</th><th>After</th></tr>\n<tr><td><pre><code>\n- [ ] i1\n- [x] one\n- [>] two\n&#20;\n## To Do\n&#20;\n## Log\n- [ ] i2\n- [x] three\n- [>] four\n&#20;\n## Deferred\n- [ ] i3\n- [x] five\n- [>] six\n</code></pre></td><td><pre><code>\n## To Do\n- [ ] i1\n- [ ] i2\n- [ ] i3\n&#20;\n## Log\n- [x] one\n- [x] five\n- [x] three\n&#20;\n## Deferred\n- [>] two\n- [>] four\n- [>] six\n</code></pre></td></tr>\n</table>\n\nTo test out this example: \n\n1. Enable task collection\n2. Set the Area header for the `default` group to `## To Do`\n3. Set the Area header for the `complete` group to `## Log`\n4. Create a new group, `deferred`, that supports one mark (`>`) and uses `## Deferred` as an area header. \n\nPut the initial text in a note, and run `(TC) Collect Tasks`.\n\n## Where do tasks go\n \nYou can have more than one heading of each type in the note.\n\nIf there are multiple sections of the same name, tasks that are discovered will be moved to the next (following) matching section. If a section is not found between the task and the end of the note, the search resumes from the top.\n    \n**Notes**:  \n\n- Marked tasks will carry along their sub items (nested lists, text, or quotes). \n- If a marked item has an unmarked child task, the child (and any text following) *will remain in the original location*.\n\n## Required configuration\n\n- Task collection must be [enabled](README.md#general-options)\n\n- An **Area heading** must be defined for a [task group](README.md#task-groups)  \n\n    Specifying an area heading enables collection for tasks that use any of the marks in that group.\n    \n    - A new heading will be created at the end of the note if it does not exist.\n    - Tasks with marks matching this group between this header and the next header or `---` will be ignored when collecting tasks (as they are already where they belong). \n\n## Optional configuration\n\n- **Remove checkbox** for a [task group](README.md#task-groups)  \n    Remove the checkbox marked tasks during the move to the configured area.\n    \n    This transforms tasks into normal list items. Task Collector will not be able to reset these items. They also will not appear in task searches or queries.\n\n"
  },
  {
    "path": "esbuild.config.mjs",
    "content": "import esbuild from \"esbuild\";\nimport process from \"node:process\";\nimport { builtinModules } from \"node:module\";\nimport { sassPlugin } from 'esbuild-sass-plugin'\n\nconst banner = `/*\nTHIS IS A GENERATED/BUNDLED FILE BY ESBUILD\nif you want to view the source, please visit the github repository of this plugin\n*/\n`;\n\nconst prod = (process.argv[2] === 'production');\nconst dir = prod || !process.env.OUTDIR ? \"./build\" : process.env.OUTDIR;\n\nconst parameters = {\n    banner: {\n        js: banner,\n    },\n    entryPoints: ['src/main.ts', 'src/styles.scss'],\n    bundle: true,\n    external: [\n        'obsidian',\n        'electron',\n        \"codemirror\",\n        '@codemirror/language',\n        '@codemirror/state',\n        '@codemirror/view',\n        'moment',\n        ...builtinModules\n    ],\n    format: 'cjs',\n    logLevel: 'info',\n    target: 'es2020',\n    treeShaking: true,\n    sourcemap: prod ? false : 'inline',\n    minify: prod,\n    outdir: dir,\n    plugins: [\n        sassPlugin()\n    ]\n};\n\nif (prod) {\n    await esbuild.build(parameters).catch((x) => {\n        if (x.errors) {\n            console.error(x.errors);\n        } else {\n            console.error(x);\n        }\n        process.exit(1)\n    });\n} else {\n    const ctx = await esbuild.context(parameters);\n    await ctx.watch()\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "// eslint.config.mjs\nimport globals from \"globals\";\nimport tsparser from \"@typescript-eslint/parser\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport obsidianmd from \"eslint-plugin-obsidianmd\";\n\nexport default defineConfig([\n    ...obsidianmd.configs.recommended,\n    globalIgnores([\n        \"test/\",\n        \"*.js\",\n        \"*.mjs\",\n        \"package.json\"\n    ]),\n    {\n        files: [\"src/**/*.ts\"],\n        languageOptions: {\n            parser: tsparser,\n            parserOptions: {\n                project: \"./tsconfig.json\"\n            },\n            globals: { ...globals.node, ...globals.browser },\n        },\n        // Optional project overrides\n        rules: {\n            \"obsidianmd/ui/sentence-case\": [\n                \"warn\",\n                {\n                    brands: [\"Task Collector\", \"xX\", \"YYYY-MM-DD\", \"Live Preview\", \"Obsidian\", \"Reading\", \" #(todo|task)\"],\n                    acronyms: [\"TC\"],\n                    enforceCamelCaseLower: true,\n                },\n            ],\n        },\n    },\n]);\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n    preset: \"ts-jest\",\n    testEnvironment: 'jsdom',\n    moduleDirectories: ['node_modules', 'src', 'test'],\n    moduleNameMapper: {\n        \"^moment-obsidian$\": \"<rootDir>/node_modules/obsidian/node_modules/moment/moment.js\",\n        \"^obsidian$\": \"<rootDir>/test/mocks/obsidian.ts\"\n    },\n    setupFiles: ['./test/setup.ts'],\n    transform: {\n        \"^.+\\\\.tsx?$\": [\"ts-jest\", { diagnostics: { ignoreCodes: [151002] } }],\n    },\n}\n"
  },
  {
    "path": "manifest-beta.json",
    "content": "{\n\t\"id\": \"obsidian-task-collector\",\n\t\"name\": \"Task Collector (TC)\",\n\t\"version\": \"1.2.2\",\n\t\"minAppVersion\": \"1.13.0\",\n\t\"description\": \"Change task status and collect tasks within a document using hotkeys and context menus.\",\n\t\"author\": \"ebullient\",\n\t\"authorUrl\": \"https://github.com/ebullient\",\n  \"fundingUrl\": \"https://buymeacoffee.com/ebullient\",\n\t\"isDesktopOnly\": false\n}\n"
  },
  {
    "path": "manifest.json",
    "content": "{\n\t\"id\": \"obsidian-task-collector\",\n\t\"name\": \"Task Collector (TC)\",\n\t\"version\": \"1.2.2\",\n\t\"minAppVersion\": \"1.13.0\",\n\t\"description\": \"Change task status and collect tasks within a document using hotkeys and context menus.\",\n\t\"author\": \"ebullient\",\n\t\"authorUrl\": \"https://github.com/ebullient\",\n  \"fundingUrl\": \"https://buymeacoffee.com/ebullient\",\n\t\"isDesktopOnly\": false\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"obsidian-task-collector\",\n  \"version\": \"1.2.2\",\n  \"private\": true,\n  \"description\": \"Manage completed tasks within a document in Obsidian (https://obsidian.md)\",\n  \"main\": \"main.js\",\n  \"engines\": {\n    \"node\": \">=22\"\n  },\n  \"scripts\": {\n    \"dev\": \"node --env-file-if-exists=.env esbuild.config.mjs\",\n    \"fix\": \"npx @biomejs/biome check --write ./src\",\n    \"format\": \"npx @biomejs/biome format ./src\",\n    \"eslint\": \"npx eslint\",\n    \"lint\": \"npx @biomejs/biome lint ./src\",\n    \"prebuild\": \"npx @biomejs/biome check ./src && npm run test\",\n    \"build\": \"node esbuild.config.mjs production\",\n    \"postbuild\": \"cp -v manifest.json README.md build\",\n    \"test\": \"npx jest\",\n    \"preversion\": \"npm run build\",\n    \"version\": \"auto-changelog -p\",\n    \"brat-notes\": \"run() { auto-changelog --stdout --hide-credit --hide-empty-releases --template .github/changelog.hbs -v $1 --starting-version $1  > release-notes.md; }; run\"\n  },\n  \"keywords\": [\n    \"obsidian\",\n    \"obsidian-md\",\n    \"obsidian-plugin\",\n    \"obsidian-md-plugin\"\n  ],\n  \"author\": \"ebullient\",\n  \"repository\": \"github.com:ebullient/obsidian-task-collector\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@biomejs/biome\": \"2.4.9\",\n    \"@types/jest\": \"^30.0.0\",\n    \"@types/moment\": \"^2.13.0\",\n    \"@types/node\": \"^25.5.0\",\n    \"@typescript-eslint/parser\": \"^8.57.2\",\n    \"auto-changelog\": \"^2.5.0\",\n    \"esbuild\": \"^0.27.4\",\n    \"esbuild-sass-plugin\": \"^3.7.0\",\n    \"eslint-plugin-obsidianmd\": \"^0.2.9\",\n    \"jest\": \"^30.3.0\",\n    \"jest-environment-jsdom\": \"^30.3.0\",\n    \"jest-util\": \"^30.3.0\",\n    \"obsidian\": \"^1.12.3\",\n    \"ts-jest\": \"^29.4.9\",\n    \"tslib\": \"^2.8.1\",\n    \"typescript\": \"6.0.2\"\n  },\n  \"dependencies\": {\n    \"@codemirror/language\": \"https://github.com/lishid/cm-language\"\n  },\n  \"overrides\": {\n    \"glob\": \"13.0.6\",\n    \"minimatch\": \"10.2.3\"\n  },\n  \"auto-changelog\": {\n    \"backfillLimit\": false,\n    \"commitLimit\": false,\n    \"ignoreCommitPattern\": \"(🔖|🔨|🧹|changelog|release|Update README).*\",\n    \"replaceText\": {\n      \"([;.,] [rR]esolves) #\\\\d+\\\\.?\\\\s*$\": \"$1\",\n      \"([;.,] [Ff]ixes) #\\\\d+\\\\.?\\\\s*$\": \"$1\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/@types/api.d.ts",
    "content": "export interface API {\n    /**\n     * Return completed task values as a string.\n     * Will be \"x\", \"xX\", \"x-\", or \"xX-\"\n     */\n    getCompletedTaskValues(): string;\n\n    /**\n     * Return incomplete task values as a string.\n     * Minimally \" \", but could be any other combination of characters, like \" >?/123!\"\n     */\n    getIncompleteTaskValues(): string;\n\n    /**\n     * Return true if the provided value marks a completed (or canceled) item.\n     */\n    isComplete(value: string): boolean;\n\n    /**\n     * Return true if the provided value marks a canceled item (-).\n     */\n    isCanceled(value: string): boolean;\n\n    /**\n     * Async method that displays a modal menu containing potential task\n     * completion candidates.\n     *\n     * Returns the selected \"mark\" (character) as a string.\n     */\n    getMark(): Promise<string>;\n}\n"
  },
  {
    "path": "src/@types/settings.d.ts",
    "content": "export type TaskCollectorSettings = {\n    groups: Record<string, ManipulationSettings>;\n    collectionEnabled: boolean;\n    previewClickModal: boolean;\n    markCycle: string;\n    markCycleRemoveTask: boolean;\n    skipSectionMatch: string;\n    contextMenu: {\n        markTask: boolean;\n        resetTask: boolean;\n        resetAllTasks: boolean;\n        collectTasks: boolean;\n    };\n    debug: boolean;\n    convertEmptyLines: boolean;\n    hideNotifications: boolean;\n    version: TcVersion;\n};\n\nexport type ManipulationSettings = {\n    name: string;\n    marks: string;\n    complete: boolean;\n    removeExpr: string;\n    appendDateFormat: string;\n    collection?: CollectionSettings;\n    registerCommand: boolean;\n    useContextMenu: boolean;\n};\n\nexport type CollectionSettings = {\n    areaHeading: string;\n    removeCheckbox: boolean;\n};\n\nexport type TcVersion = {\n    major: number;\n    minor: number;\n    patch: number;\n};\n\nexport type TaskCollectorCache = {\n    useContextMenu: boolean; // task line context menu items\n    marks: Record<string, ManipulationSettings>; // (char, settings)\n    removeExpr: Record<string, RegExp>; // (settings name, removeTextRegex)\n    undoExpr: Record<string, RegExp>; // (settings name, undoRegex)\n    skipSectionExpr: RegExp | null;\n    completedMarks: string; // marks that should be treated as \"complete\"\n    incompleteMarks: string; // marks that should be treated as \"incomplete\"\n    areaHeadings: string[]; // configured area headings\n    headingToMark: Record<string, string>; // heading to string of marks\n};\n\nexport type LegacySettings = {\n    completedAreaHeader?: string;\n    removeExpression?: string;\n    appendDateFormat?: string;\n    appendRemoveAllTasks?: boolean;\n    incompleteTaskValues?: string;\n    onlyLowercaseX?: boolean;\n    supportCanceledTasks?: boolean;\n    previewOnClick?: boolean;\n    rightClickComplete?: boolean;\n    rightClickMark?: boolean;\n    rightClickMove?: boolean;\n    rightClickResetTask?: boolean;\n    rightClickResetAll?: boolean;\n    rightClickToggleAll?: boolean;\n    completedAreaRemoveCheckbox?: boolean;\n    // Task Marker migration fields\n    cycleTaskValues?: string;\n    incompleteTaskValuesRow2?: string;\n    appendTextFormatMarkRow2?: string;\n    appendTextFormatMark?: string;\n    appendTextFormatAppend?: string;\n    rightClickAppend?: boolean;\n    version?: TcVersion;\n};\n\nexport type TcSectionBlock = {\n    existing: string[];\n    newTasks: string[];\n};\n\nexport type TcSection = {\n    blocks: TcSectionBlock[];\n};\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { TaskCollectorPlugin } from \"./taskcollector-Plugin\";\n\nexport default TaskCollectorPlugin;\n"
  },
  {
    "path": "src/moment.ts",
    "content": "import { moment } from \"obsidian\";\n\ntype MomentFn = (...args: unknown[]) => moment.Moment;\n\nexport const momentFn =\n    \"default\" in moment\n        ? (moment as unknown as { default: MomentFn }).default\n        : (moment as unknown as MomentFn);\n"
  },
  {
    "path": "src/styles.scss",
    "content": ".task-collector-settings {\n  dt,\n  .setting-item.tc-create-task-group {\n    padding: 0.75em 0 0 0;\n    border-top: 1px solid var(--background-modifier-border);\n  }\n\n  .task-group-name {\n    input[type=\"text\"]:disabled {\n      background-color: var(--background-primary);\n    }\n  }\n\n  button.data-value-error {\n    background-color: var(--background-modifier-error);\n    color: var(--text-on-accent);\n  }\n\n  input[type=\"text\"].data-value-error {\n    border-color: var(--text-error);\n    text-decoration: underline;\n  }\n\n  .task-marks {\n    input[type=\"text\"] {\n      font-family: var(--font-monospace);\n    }\n\n    .setting-item-control[marks*=\" \"] {\n      position: relative;\n    }\n    .setting-item-control[marks*=\" \"]::after {\n      content: \"␣\";\n      position: absolute;\n      left: 5px;\n    }\n  }\n  .setting-item {\n    border-top: none;\n  }\n\n  .regex-test-setting.regex-hidden {\n    display: none;\n  }\n}\n\n#taskcollector-modal {\n\n  .modal-close-button {\n    display: none;\n  }\n  .modal {\n    padding: 10px;\n    min-width: 200px;\n    max-width: 300px;\n\n    .markdown-preview-view {\n      padding: 5px;\n\n      ul {\n        display: flex;\n        flex-wrap: wrap;\n        --gap: 3px;\n        --square: 45px;\n        margin: calc(-1 * var(--gap)) calc(1 * var(--gap));\n        margin-block-start: 0;\n        margin-block-end: 0;\n        padding-inline-start: 0;\n\n        > li {\n          margin: var(--gap);\n          display: block;\n          width: var(--square);\n          height: var(--square);\n          background-color: var(--background-primary);\n          border: 1px solid var(--background-modifier-border);\n          border-radius: 2px;\n          text-indent: unset;\n          line-height: var(--square);\n          text-align: center;\n\n          > span {\n            font-family: var(--font-monospace);\n          }\n        }\n        > li::before {\n          display: none;\n        }\n        > li.task-list-item {\n          text-decoration: none;\n\n          input.task-list-item-checkbox {\n            margin-right: 4px;\n            margin-left: unset;\n          }\n        }\n      }\n\n      nav {\n        display: flex;\n        flex-wrap: wrap;\n        justify-content: space-around;\n\n        span {\n          display: block;\n          font-size: 0.8em;\n          color: var(--text-muted);\n        }\n      }\n    }\n  }\n  .modal-content {\n    background-color: var(--background-secondary);\n    margin-top: 0px;\n  }\n}\n"
  },
  {
    "path": "src/taskcollector-Api.ts",
    "content": "import type { App } from \"obsidian\";\nimport type { API } from \"./@types/api\";\nimport type { TaskCollector } from \"./taskcollector-TaskCollector\";\nimport { promptForMark } from \"./taskcollector-TaskMarkModal\";\n\nexport class TaskCollectorApi implements API {\n    app: App;\n    taskCollector: TaskCollector;\n\n    constructor(app: App, taskCollector: TaskCollector) {\n        this.app = app;\n        this.taskCollector = taskCollector;\n    }\n\n    getCompletedTaskValues(): string {\n        return this.taskCollector.cache.completedMarks;\n    }\n\n    getIncompleteTaskValues(): string {\n        return this.taskCollector.cache.incompleteMarks;\n    }\n\n    getMark(): Promise<string> {\n        return promptForMark(this.app, this.taskCollector);\n    }\n\n    isComplete(value: string): boolean {\n        return this.getCompletedTaskValues().contains(value);\n    }\n    isCanceled(value: string): boolean {\n        return value === \"-\";\n    }\n}\n"
  },
  {
    "path": "src/taskcollector-Constants.ts",
    "content": "import type {\n    ManipulationSettings,\n    TaskCollectorCache,\n    TaskCollectorSettings,\n} from \"./@types/settings\";\n\nexport const TEXT_ONLY_MARK = \"Ø\";\nexport const TEXT_ONLY_NAME = \"text\";\nexport const DEFAULT_NAME = \"default\";\nexport const COMPLETE_NAME = \"complete\";\n\nexport const DEFAULT_SETTINGS: TaskCollectorSettings = {\n    groups: {\n        default: {\n            name: DEFAULT_NAME,\n            marks: \" \",\n            complete: false,\n            removeExpr: \"\",\n            appendDateFormat: \"\",\n            registerCommand: false,\n            useContextMenu: false,\n        },\n        complete: {\n            name: COMPLETE_NAME,\n            marks: \"xX\",\n            complete: true,\n            removeExpr: \"\",\n            appendDateFormat: \"\",\n            registerCommand: false,\n            useContextMenu: false,\n        },\n    },\n    markCycle: \"\",\n    markCycleRemoveTask: false,\n    collectionEnabled: false,\n    previewClickModal: true,\n    contextMenu: {\n        markTask: true,\n        resetTask: false,\n        resetAllTasks: false,\n        collectTasks: true,\n    },\n    debug: false,\n    convertEmptyLines: false,\n    hideNotifications: false,\n    skipSectionMatch: \"\",\n    version: {\n        major: 0,\n        minor: 0,\n        patch: 0,\n    },\n};\n\nexport const GROUP_DEFAULT: ManipulationSettings = {\n    name: DEFAULT_NAME,\n    marks: \"\",\n    complete: false,\n    removeExpr: \"\",\n    appendDateFormat: \"\",\n    registerCommand: false,\n    useContextMenu: false,\n};\n\nexport const GROUP_COMPLETE: ManipulationSettings = {\n    name: COMPLETE_NAME,\n    marks: \"xX\",\n    complete: true,\n    removeExpr: \"\",\n    appendDateFormat: \"\",\n    registerCommand: false,\n    useContextMenu: false,\n};\n\nexport const DEFAULT_COLLECTION = {\n    areaHeading: \"## Log\",\n    removeCheckbox: false,\n};\n\nexport const CACHE_DEFAULT: TaskCollectorCache = {\n    useContextMenu: false,\n    completedMarks: \"\",\n    incompleteMarks: \"\",\n    skipSectionExpr: null,\n    marks: {},\n    removeExpr: {},\n    undoExpr: {},\n    areaHeadings: [],\n    headingToMark: {},\n};\n\nexport const DEFAULT_SETTINGS_0 = {\n    completedAreaHeader: \"## Log\",\n    removeExpression: \"\",\n    appendDateFormat: \"\",\n    appendRemoveAllTasks: false,\n    incompleteTaskValues: \" \",\n    onlyLowercaseX: false,\n    supportCanceledTasks: true,\n    previewOnClick: false,\n    rightClickComplete: false,\n    rightClickMark: false,\n    rightClickMove: false,\n    rightClickResetTask: false,\n    rightClickResetAll: false,\n    rightClickToggleAll: false,\n    completedAreaRemoveCheckbox: false,\n};\n"
  },
  {
    "path": "src/taskcollector-Data.ts",
    "content": "import type {\n    LegacySettings,\n    ManipulationSettings,\n    TaskCollectorSettings,\n    TcVersion,\n} from \"./@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS,\n    DEFAULT_SETTINGS_0,\n    GROUP_DEFAULT,\n    TEXT_ONLY_MARK,\n    TEXT_ONLY_NAME,\n} from \"./taskcollector-Constants\";\nimport type { TaskCollectorPlugin } from \"./taskcollector-Plugin\";\n\nexport const Data = {\n    constructSettings,\n    createSettingsGroup,\n    sanitize,\n    sanitizeMarks,\n    moveGroup,\n};\n\n/**\n * Sort and remove duplicate characters from string\n * @param marks\n * @returns\n */\nfunction sanitizeMarks(marks: string): string {\n    const tmp = Array.from(new Set(marks));\n    tmp.sort();\n    return tmp.join(\"\").replace(TEXT_ONLY_MARK, \"\");\n}\n\nfunction sanitize(tcp: TaskCollectorPlugin, settings: TaskCollectorSettings) {\n    tcp.tc.logDebug(\"sanitize begin\", settings);\n    let dirty = false;\n\n    // Make sure characters in cycle are unique\n    if (settings.markCycle) {\n        settings.markCycle = [...new Set(settings.markCycle)].join(\"\");\n        settings.markCycle.replace(\"§\", \"\");\n        if (settings.markCycleRemoveTask) {\n            settings.markCycle += \"§\";\n        }\n    }\n\n    // resolve groups with a key / mts.name mismatch\n    for (const [name, mts] of Object.entries(settings.groups)) {\n        if (name !== mts.name) {\n            dirty = true; // ensure name & mts.name agree\n            if (settings.groups[mts.name]) {\n                tcp.tc.logDebug(\n                    `Group named ${mts.name} already exists. Reverting group name to ${name}`,\n                );\n                mts.name = name;\n            } else {\n                // move to the new name\n                moveGroup(tcp, settings.groups, name, mts.name);\n            }\n        }\n    }\n\n    if (hasMark(settings.groups[TEXT_ONLY_NAME])) {\n        dirty = true; // ensure text only group has no marks\n        settings.groups[TEXT_ONLY_NAME].marks = TEXT_ONLY_MARK;\n    }\n\n    // check for multiple groups with empty marks\n    const textOnlyGroups = Object.entries(settings.groups).filter(\n        ([_, mts]) => !hasMark(mts),\n    );\n    if (textOnlyGroups.length > 1) {\n        dirty = true;\n        tcp.tc.logDebug(\n            `There can be only one group for text-only settings (${TEXT_ONLY_NAME}).`,\n        );\n        if (!settings.groups[TEXT_ONLY_NAME]) {\n            // There is no text only group. Use the first found\n            tcp.tc.logDebug(\n                `Configuration: renamed group ${textOnlyGroups[0][1].name} to ${TEXT_ONLY_NAME}.`,\n            );\n            moveGroup(\n                tcp,\n                settings.groups,\n                textOnlyGroups[0][1].name,\n                TEXT_ONLY_NAME,\n            );\n        }\n        let used = \"\";\n        let nextMark: string;\n        // filter from the top (post-move)\n        for (const [name, mts] of Object.entries(settings.groups)) {\n            if (!hasMark(mts) && name !== TEXT_ONLY_NAME) {\n                [used, nextMark] = nextRandom(used);\n                settings.groups[name].marks = nextMark;\n            }\n        }\n    } else if (\n        textOnlyGroups.length === 1 &&\n        textOnlyGroups[0][1].name !== TEXT_ONLY_NAME\n    ) {\n        // Make sure the text only group has the required name\n        tcp.tc.logDebug(\n            `Configuration: renamed group ${textOnlyGroups[0][1].name} to ${TEXT_ONLY_NAME}.`,\n        );\n        moveGroup(\n            tcp,\n            settings.groups,\n            textOnlyGroups[0][1].name,\n            TEXT_ONLY_NAME,\n        );\n    }\n\n    // The text-only group is not subject to task collection\n    if (settings.groups[TEXT_ONLY_NAME]) {\n        if (settings.groups[TEXT_ONLY_NAME].collection) {\n            settings.groups[TEXT_ONLY_NAME].collection = undefined;\n        }\n    }\n\n    if (dirty) {\n        tcp.tc.notify(\n            \"(TC) Configuration settings were modified. See console for details.\",\n        );\n    }\n    tcp.tc.logDebug(\"sanitize end\", settings);\n}\n\n/**\n * Ensure loaded settings are valid for this version of the plugin\n * (will have trouble back-porting)\n * @param orig Original object (any)\n * @returns Properly formed TaskCollectorSettings\n */\nasync function constructSettings(\n    tcp: TaskCollectorPlugin,\n    orig: LegacySettings,\n): Promise<TaskCollectorSettings> {\n    return orig.version\n        ? await adaptSettings(tcp, orig)\n        : await migrateSettings(tcp, orig);\n}\n\n/**\n * Incremental update of 1.x TaskCollectorSettings\n * @param obj Partial TaskCollectorSettings\n * @returns Properly formed TaskCollectorSettings\n */\nasync function adaptSettings(\n    tcp: TaskCollectorPlugin,\n    obj: LegacySettings,\n): Promise<TaskCollectorSettings> {\n    const settings: TaskCollectorSettings = {\n        ...DEFAULT_SETTINGS,\n        ...(obj as Partial<TaskCollectorSettings>),\n    };\n\n    sanitize(tcp, settings);\n\n    const version = toVersion(tcp.manifest.version);\n    if (compareVersion(version, settings.version) === 0) {\n        return settings;\n    }\n\n    // Save the version and modified config\n    settings.version = version;\n    await tcp.saveData(settings);\n    return settings;\n}\n\nasync function migrateSettings(\n    tcp: TaskCollectorPlugin,\n    orig: LegacySettings,\n): Promise<TaskCollectorSettings> {\n    const old: Required<LegacySettings> = {\n        ...DEFAULT_SETTINGS_0,\n        version: { major: 0, minor: 0, patch: 0 },\n        cycleTaskValues: \"\",\n        incompleteTaskValuesRow2: \"\",\n        appendTextFormatMarkRow2: \"\",\n        appendTextFormatMark: \"\",\n        appendTextFormatAppend: \"\",\n        rightClickAppend: false,\n        ...orig,\n    };\n    const settings = JSON.parse(\n        JSON.stringify(DEFAULT_SETTINGS),\n    ) as TaskCollectorSettings; // deep copy\n\n    // Menus and Modals\n\n    settings.previewClickModal = old.previewOnClick;\n    settings.contextMenu.markTask = old.rightClickMark;\n    settings.contextMenu.resetTask = old.rightClickResetTask;\n    settings.groups[COMPLETE_NAME].useContextMenu = old.rightClickComplete;\n\n    // Groups and marks\n\n    let marks = \"x\";\n    if (!old.onlyLowercaseX) {\n        marks += \"X\";\n    }\n    if (old.supportCanceledTasks) {\n        marks += \"-\";\n    }\n    settings.groups[COMPLETE_NAME].marks = sanitizeMarks(marks);\n    settings.groups[COMPLETE_NAME].appendDateFormat = old.appendDateFormat;\n    settings.groups[COMPLETE_NAME].removeExpr = old.removeExpression;\n\n    marks = old.incompleteTaskValues;\n    settings.groups[DEFAULT_NAME].marks = sanitizeMarks(marks);\n    if (old.appendRemoveAllTasks) {\n        settings.groups[DEFAULT_NAME].appendDateFormat = old.appendDateFormat;\n        settings.groups[DEFAULT_NAME].removeExpr = old.removeExpression;\n    }\n\n    // Task Collector default\n    settings.collectionEnabled = true;\n    settings.contextMenu.collectTasks = old.rightClickMove || false;\n\n    // Task Marker\n\n    if (orig.cycleTaskValues) {\n        settings.markCycle = orig.cycleTaskValues;\n        if (orig.incompleteTaskValuesRow2) {\n            createSettingsGroup(settings.groups, \"group-2\", {\n                marks: sanitizeMarks(orig.incompleteTaskValuesRow2),\n                appendDateFormat: orig.appendTextFormatMarkRow2,\n            });\n        }\n        if (orig.appendTextFormatMark) {\n            settings.groups[DEFAULT_NAME].appendDateFormat =\n                orig.appendTextFormatMark;\n        }\n        if (orig.appendTextFormatAppend) {\n            createSettingsGroup(settings.groups, TEXT_ONLY_NAME, {\n                marks: TEXT_ONLY_MARK,\n                appendDateFormat: orig.appendTextFormatAppend,\n                useContextMenu: orig.rightClickAppend,\n            });\n        }\n        tcp.tc.logDebug(\"groups\", settings.groups);\n\n        // Task Marker default\n        settings.collectionEnabled = false;\n    }\n\n    // Task collection\n    if (settings.collectionEnabled) {\n        settings.groups[COMPLETE_NAME].collection = {\n            areaHeading: old.completedAreaHeader,\n            removeCheckbox: old.completedAreaRemoveCheckbox,\n        };\n    }\n\n    // Save modified config\n    settings.version = toVersion(tcp.manifest.version);\n    tcp.tc.logDebug(\"migrated settings\", settings);\n    await tcp.saveData(settings);\n    return settings;\n}\n\nfunction createSettingsGroup(\n    groups: Record<string, ManipulationSettings>,\n    name: string,\n    data: Partial<ManipulationSettings>,\n): void {\n    groups[name] = {\n        ...GROUP_DEFAULT,\n        name,\n        ...data,\n    };\n    if (groups[name].marks === \"\") {\n        groups[name].marks = TEXT_ONLY_MARK;\n    }\n}\n\nfunction toVersion(version: string): TcVersion {\n    const v = version.split(\".\");\n    return {\n        major: Number(v[0]),\n        minor: Number(v[1]),\n        patch: Number(v[2]),\n    };\n}\n\nfunction compareVersion(v1: TcVersion, v2: TcVersion): number {\n    if (v1.major === v2.major) {\n        if (v1.minor === v2.minor) {\n            return v1.patch - v2.patch;\n        }\n        return v1.minor - v2.minor;\n    }\n    return v1.major - v2.major;\n}\n\nfunction hasMark(group: ManipulationSettings) {\n    return group && group.marks !== \"\" && group.marks !== TEXT_ONLY_MARK;\n}\n\nfunction moveGroup(\n    tcp: TaskCollectorPlugin,\n    groups: Record<string, ManipulationSettings>,\n    oldName: string,\n    newName: string,\n) {\n    if (!groups || !oldName || !newName || newName === oldName) {\n        return;\n    }\n    if (groups[newName]) {\n        tcp.tc.logDebug(`Can not move group, ${newName} already exists`);\n    } else {\n        groups[oldName].name = newName;\n        groups[newName] = groups[oldName];\n        delete groups[oldName];\n    }\n}\n\nfunction nextRandom(used: string): string[] {\n    let next = used;\n    let i = 0;\n    do {\n        const mark = String.fromCharCode(\n            0x2654 + Math.random() * (0x2667 - 0x2654 + 1),\n        );\n        if (next.indexOf(mark) < 0) {\n            next += mark;\n            return [next, mark];\n        }\n        i++;\n    } while (i < 10);\n    return [next, String.fromCharCode(0x24e7)];\n}\n"
  },
  {
    "path": "src/taskcollector-Plugin.ts",
    "content": "import type { Extension } from \"@codemirror/state\";\nimport { type EditorView, ViewPlugin } from \"@codemirror/view\";\nimport {\n    type Command,\n    type Editor,\n    type EditorPosition,\n    type EventRef,\n    type MarkdownFileInfo,\n    type MarkdownPostProcessor,\n    MarkdownPreviewRenderer,\n    MarkdownView,\n    Menu,\n    Plugin,\n    type TFile,\n} from \"obsidian\";\nimport type { API } from \"./@types/api\";\nimport type { LegacySettings } from \"./@types/settings\";\nimport { TaskCollectorApi } from \"./taskcollector-Api\";\nimport { TEXT_ONLY_MARK } from \"./taskcollector-Constants\";\nimport { Data } from \"./taskcollector-Data\";\nimport { TaskCollectorSettingsTab } from \"./taskcollector-SettingsTab\";\nimport { Direction, TaskCollector } from \"./taskcollector-TaskCollector\";\nimport { promptForMark } from \"./taskcollector-TaskMarkModal\";\n\ndeclare module \"obsidian\" {\n    interface App {\n        commands: {\n            commands: {\n                [id: string]: Command;\n            };\n            removeCommand(id: string): void;\n            executeCommandById: (id: string) => void;\n        };\n        plugins: {\n            plugins: {\n                \"obsidian-task-collector\": {\n                    api: API;\n                };\n            };\n        };\n    }\n\n    interface MarkdownPostProcessorContext {\n        containerEl: HTMLElement;\n    }\n}\n\ninterface Selection {\n    start: EditorPosition;\n    end?: EditorPosition;\n    lines: number[];\n}\n\nexport class TaskCollectorPlugin extends Plugin {\n    tc: TaskCollector;\n    handlersRegistered = false;\n    commandsRegistered = false;\n\n    editTaskContextMenu: EventRef | null;\n    postProcessor: MarkdownPostProcessor | null;\n\n    /** CodeMirror 6 extensions. Tracked via array to allow for dynamic updates. */\n    private cmExtension: Extension[] = [];\n\n    /** External-facing plugin API. */\n    public api: API;\n\n    async onload(): Promise<void> {\n        console.debug(`loading Task Collector (TC) v${this.manifest.version}`);\n\n        this.tc = new TaskCollector();\n        this.addSettingTab(\n            new TaskCollectorSettingsTab(this.app, this, this.tc),\n        );\n        await this.loadSettings();\n\n        // Live Preview: register input handler\n        if (this.tc.settings.previewClickModal) {\n            this.cmExtension.push(inlinePlugin(this, this.tc));\n            this.registerEditorExtension(this.cmExtension);\n        }\n\n        this.registerCommands();\n        this.registerHandlers();\n\n        this.api = new TaskCollectorApi(this.app, this.tc);\n        this.app.plugins.plugins[\"obsidian-task-collector\"].api = this.api;\n    }\n\n    async markInCycle(direction: Direction, lines?: number[]): Promise<void> {\n        const activeFile = this.app.workspace.getActiveFile();\n        if (activeFile) {\n            await this.app.vault.process(activeFile, (source): string => {\n                return this.tc.markInCycle(source, direction, lines);\n            });\n        }\n    }\n\n    async editLines(mark: string, lines?: number[]): Promise<void> {\n        const activeFile = this.app.workspace.getActiveFile();\n        if (activeFile) {\n            await this.editLinesInFile(activeFile, mark, lines);\n        }\n    }\n\n    async editLinesInFile(\n        file: TFile,\n        mark: string,\n        lines?: number[],\n    ): Promise<void> {\n        await this.app.vault.process(file, (source): string => {\n            return this.tc.markSelectedTask(source, mark, lines);\n        });\n    }\n\n    async collectTasks(): Promise<void> {\n        const activeFile = this.app.workspace.getActiveFile();\n        if (activeFile) {\n            await this.app.vault.process(activeFile, (source): string => {\n                return this.tc.moveAllTasks(source);\n            });\n        }\n    }\n\n    async resetAllTasks(): Promise<void> {\n        const activeFile = this.app.workspace.getActiveFile();\n        if (activeFile) {\n            await this.app.vault.process(activeFile, (source): string => {\n                return this.tc.resetAllTasks(source);\n            });\n        }\n    }\n\n    getCurrentLinesFromEditor(editor: Editor): Selection {\n        this.tc.logDebug(\n            \"from: %o, to: %o, anchor: %o, head: %o, general: %o\",\n            editor.getCursor(\"from\"),\n            editor.getCursor(\"to\"),\n            editor.getCursor(\"anchor\"),\n            editor.getCursor(\"head\"),\n            editor.getCursor(),\n        );\n\n        let start: EditorPosition;\n        let end: EditorPosition | undefined;\n        const lines: number[] = [];\n        if (editor.somethingSelected()) {\n            start = editor.getCursor(\"from\");\n            end = editor.getCursor(\"to\");\n            for (let i = start.line; i <= end.line; i++) {\n                lines.push(i);\n            }\n        } else {\n            start = editor.getCursor();\n            lines.push(start.line);\n        }\n        return {\n            start,\n            end,\n            lines,\n        };\n    }\n\n    buildContextMenu(\n        menu: Menu,\n        info: MarkdownView | MarkdownFileInfo,\n        selection: Selection,\n    ): void {\n        if (this.tc.settings.contextMenu.markTask) {\n            menu.addItem((item) =>\n                item\n                    .setTitle(\"(TC) Mark task\")\n                    .setIcon(\"check-square\")\n                    .onClick(async () => {\n                        this.tc.logDebug(\"Mark task\", menu, info, selection);\n                        const mark = await promptForMark(this.app, this.tc);\n                        if (mark) {\n                            await this.editLines(mark, selection.lines);\n                            this.restoreCursor(selection, info.editor);\n                        }\n                    }),\n            );\n            if (this.tc.settings.markCycle) {\n                menu.addItem((item) =>\n                    item\n                        .setTitle(\"(TC) Mark with next\")\n                        .setIcon(\"forward\")\n                        .onClick(async () => {\n                            this.tc.logDebug(\n                                \"Mark with next\",\n                                menu,\n                                info,\n                                selection,\n                            );\n                            await this.markInCycle(\n                                Direction.NEXT,\n                                selection.lines,\n                            );\n                            this.restoreCursor(selection, info.editor);\n                        }),\n                );\n\n                menu.addItem((item) =>\n                    item\n                        .setTitle(\"(TC) Mark with previous\")\n                        .setIcon(\"reply\")\n                        .onClick(async () => {\n                            this.tc.logDebug(\n                                \"Mark with previous\",\n                                menu,\n                                info,\n                                selection,\n                            );\n                            await this.markInCycle(\n                                Direction.PREV,\n                                selection.lines,\n                            );\n                            this.restoreCursor(selection, info.editor);\n                        }),\n                );\n            }\n        }\n        // dynamic/optional menu items\n        for (const [k, ms] of Object.entries(this.tc.cache.marks)) {\n            if (ms.useContextMenu) {\n                menu.addItem((item) =>\n                    item\n                        .setTitle(\n                            k === TEXT_ONLY_MARK\n                                ? \"(TC) Append text\"\n                                : `(TC) Change to '[${k}]' (${ms.name})`,\n                        )\n                        .setIcon(\"check-circle\")\n                        .onClick(async () => {\n                            this.tc.logDebug(\n                                `Change to '${k}'`,\n                                menu,\n                                info,\n                                selection,\n                            );\n                            await this.editLines(k, selection.lines);\n                            this.restoreCursor(selection, info.editor);\n                        }),\n                );\n            }\n        }\n\n        if (this.tc.settings.contextMenu.resetAllTasks) {\n            menu.addItem((item) =>\n                item\n                    .setTitle(\"(TC) Reset all tasks\")\n                    .setIcon(\"blocks\")\n                    .onClick(async () => {\n                        this.tc.logDebug(\"Reset all tasks\", menu, info);\n                        await this.resetAllTasks();\n                        this.restoreCursor(selection, info.editor);\n                    }),\n            );\n        }\n\n        if (\n            this.tc.settings.collectionEnabled &&\n            this.tc.settings.contextMenu.collectTasks\n        ) {\n            menu.addItem((item) =>\n                item\n                    .setTitle(\"(TC) Collect tasks\")\n                    .setIcon(\"tornado\")\n                    .onClick(async () => {\n                        await this.collectTasks();\n                        this.restoreCursor(selection, info.editor);\n                    }),\n            );\n        }\n    }\n\n    restoreCursor(selection: Selection, editor: Editor) {\n        if (selection.lines.length > 1) {\n            editor.setSelection(selection.start, selection.end);\n        } else {\n            editor.setCursor(selection.start);\n        }\n    }\n\n    registerCommands(): void {\n        if (!this.commandsRegistered) {\n            this.tc.logDebug(\"register commands\");\n            this.commandsRegistered = true;\n\n            const markTaskCommand: Command = {\n                id: \"task-collector-mark\",\n                name: \"Mark task\",\n                icon: \"check-square\",\n                editorCallback: async (\n                    editor: Editor,\n                    _view: MarkdownView | MarkdownFileInfo,\n                ) => {\n                    const mark = await promptForMark(this.app, this.tc);\n                    if (mark) {\n                        const selection =\n                            this.getCurrentLinesFromEditor(editor);\n                        await this.editLines(mark, selection.lines);\n                        this.restoreCursor(selection, editor);\n                    }\n                },\n            };\n            this.addCommand(markTaskCommand);\n\n            const resetAllTaskCommand: Command = {\n                id: \"task-collector-reset-all-tasks\",\n                name: \"Reset all tasks\",\n                icon: \"blocks\",\n                callback: async () => {\n                    await this.resetAllTasks();\n                },\n            };\n            this.addCommand(resetAllTaskCommand);\n\n            if (this.tc.settings.collectionEnabled) {\n                const moveAllTaskCommand: Command = {\n                    id: \"task-collector-move-completed-tasks\",\n                    name: \"Collect tasks\",\n                    icon: \"tornado\",\n                    callback: async () => {\n                        await this.collectTasks();\n                    },\n                };\n                this.addCommand(moveAllTaskCommand);\n            }\n\n            if (this.tc.settings.markCycle) {\n                const markWithNextCommand: Command = {\n                    id: \"task-collector-mark-next\",\n                    name: \"Mark with next\",\n                    icon: \"forward\",\n                    editorCallback: async (\n                        editor: Editor,\n                        view: MarkdownView | MarkdownFileInfo,\n                    ) => {\n                        this.tc.logDebug(\n                            `${markWithNextCommand.id}: callback`,\n                            editor,\n                            view,\n                        );\n                        const selection =\n                            this.getCurrentLinesFromEditor(editor);\n                        await this.markInCycle(Direction.NEXT, selection.lines);\n                        this.restoreCursor(selection, editor);\n                    },\n                };\n                this.addCommand(markWithNextCommand);\n\n                const markWithPrevCommand: Command = {\n                    id: \"task-collector-mark-prev\",\n                    name: \"Mark with previous\",\n                    icon: \"reply\",\n                    editorCallback: async (\n                        editor: Editor,\n                        view: MarkdownView | MarkdownFileInfo,\n                    ) => {\n                        this.tc.logDebug(\n                            `${markWithPrevCommand.id}: callback`,\n                            editor,\n                            view,\n                        );\n                        const selection =\n                            this.getCurrentLinesFromEditor(editor);\n                        await this.markInCycle(Direction.PREV, selection.lines);\n                        this.restoreCursor(selection, editor);\n                    },\n                };\n                this.addCommand(markWithPrevCommand);\n            }\n\n            // Per-group/mark commands\n            for (const [k, ms] of Object.entries(this.tc.cache.marks)) {\n                if (ms.registerCommand) {\n                    const command: Command = {\n                        id: `task-collector-mark-task-${k}`,\n                        name:\n                            k === TEXT_ONLY_MARK\n                                ? \"Append text\"\n                                : `Mark with '${k}'`,\n                        icon:\n                            k === TEXT_ONLY_MARK ? \"list-plus\" : \"check-circle\",\n                        editorCallback: async (\n                            editor: Editor,\n                            view: MarkdownView | MarkdownFileInfo,\n                        ) => {\n                            const selection =\n                                this.getCurrentLinesFromEditor(editor);\n                            this.tc.logDebug(\n                                `${command.id}: callback`,\n                                selection,\n                                editor,\n                                view,\n                            );\n                            await this.editLines(k, selection.lines);\n                            this.restoreCursor(selection, editor);\n                        },\n                    };\n                    this.addCommand(command);\n                }\n            }\n        }\n    }\n\n    unregisterCommands(): void {\n        this.tc.logDebug(\"unregister commands\");\n        this.commandsRegistered = false;\n\n        const oldCommands = Object.keys(this.app.commands.commands).filter(\n            (p) => p.startsWith(\"task-collector-\"),\n        );\n        for (const command of oldCommands) {\n            this.app.commands.removeCommand(command);\n        }\n    }\n\n    registerHandlers(): void {\n        if (!this.handlersRegistered) {\n            this.tc.logDebug(\"register handlers\");\n            this.handlersRegistered = true;\n\n            // Source / Live Preview mode: register context menu\n            if (this.tc.cache.useContextMenu) {\n                this.editTaskContextMenu = this.app.workspace.on(\n                    \"editor-menu\",\n                    async (menu, editor, info) => {\n                        //get line selections here\n                        this.buildContextMenu(\n                            menu,\n                            info,\n                            this.getCurrentLinesFromEditor(editor),\n                        );\n                    },\n                );\n                this.registerEvent(this.editTaskContextMenu);\n            }\n\n            // Reading mode: register post-processor\n            if (\n                this.tc.cache.useContextMenu ||\n                this.tc.settings.previewClickModal\n            ) {\n                this.postProcessor = (el, ctx) => {\n                    const checkboxes = el.querySelectorAll<HTMLInputElement>(\n                        \".task-list-item-checkbox\",\n                    );\n                    const section = ctx.getSectionInfo(el);\n\n                    if (!checkboxes.length || !ctx.sourcePath || !section) {\n                        return;\n                    }\n                    const targetFile = this.app.vault.getFileByPath(\n                        ctx.sourcePath,\n                    );\n\n                    this.tc.logDebug(\n                        \"markdown postprocessor\",\n                        el,\n                        ctx,\n                        section,\n                        checkboxes,\n                        targetFile,\n                    );\n\n                    // Reset the parent element for embedded elements...\n                    let parent: HTMLElement = ctx.containerEl;\n                    while (\n                        parent &&\n                        !parent.classList.contains(\"markdown-reading-view\")\n                    ) {\n                        if (parent.classList.contains(\"markdown-embed\")) {\n                            break;\n                        }\n                        parent = parent.parentNode as HTMLElement;\n                    }\n\n                    let { lineStart } = section;\n\n                    if (parent.hasAttribute(\"src\")) {\n                        // If the parent is an embedded element, we need to adjust the line number\n                        const src = parent.getAttribute(\"src\");\n                        const blockRef = src.split(\"#^\")[1];\n                        const header = src.split(\"#\")[1];\n                        const metadata =\n                            this.app.metadataCache.getFileCache(targetFile);\n                        if (blockRef) {\n                            const block = metadata.blocks[blockRef];\n                            if (block) {\n                                lineStart += block.position.start.line;\n                            }\n                        } else if (header) {\n                            const heading = metadata.headings.find(\n                                (h) => h.heading === header,\n                            );\n                            if (heading) {\n                                lineStart += heading.position.start.line;\n                            }\n                        }\n                    }\n\n                    for (const checkbox of Array.from(checkboxes)) {\n                        const line =\n                            Number(lineStart) + Number(checkbox.dataset.line);\n\n                        this.tc.logDebug(\"checkbox\", checkbox, line);\n                        checkbox.setAttribute(\"data-tc-line\", line.toString());\n                        const parent = checkbox.parentElement;\n\n                        if (this.tc.cache.useContextMenu && parent) {\n                            this.registerDomEvent(\n                                parent,\n                                \"contextmenu\",\n                                (ev) => {\n                                    const view =\n                                        this.app.workspace.getActiveViewOfType(\n                                            MarkdownView,\n                                        );\n                                    if (view) {\n                                        const menu = new Menu();\n                                        this.buildContextMenu(menu, view, {\n                                            start: {\n                                                line,\n                                                ch: 0,\n                                            },\n                                            lines: [line],\n                                        });\n                                        menu.showAtMouseEvent(ev);\n                                    }\n                                },\n                            );\n                        }\n\n                        if (this.tc.settings.previewClickModal) {\n                            // reading mode\n                            this.registerDomEvent(\n                                checkbox,\n                                \"click\",\n                                async (ev) => {\n                                    ev.stopImmediatePropagation();\n                                    ev.preventDefault();\n                                    const mark = await promptForMark(\n                                        this.app,\n                                        this.tc,\n                                    );\n                                    if (mark) {\n                                        checkbox.checked = mark !== \" \";\n                                        checkbox.parentElement.dataset.task =\n                                            mark;\n                                        await this.editLinesInFile(\n                                            targetFile,\n                                            mark,\n                                            [line],\n                                        );\n                                    }\n                                },\n                            );\n                        }\n                    }\n                };\n                this.registerMarkdownPostProcessor(this.postProcessor);\n            }\n        }\n    }\n\n    unregisterHandlers(): void {\n        this.tc.logDebug(\"unregister handlers\");\n        this.handlersRegistered = false;\n\n        if (this.editTaskContextMenu) {\n            this.app.workspace.offref(this.editTaskContextMenu);\n            this.editTaskContextMenu = null;\n        }\n\n        if (this.postProcessor) {\n            MarkdownPreviewRenderer.unregisterPostProcessor(this.postProcessor);\n            this.postProcessor = null;\n        }\n    }\n\n    onunload(): void {\n        this.unregisterCommands();\n        this.unregisterHandlers();\n    }\n\n    async loadSettings(): Promise<void> {\n        const obj = Object.assign(\n            {},\n            (await this.loadData()) as LegacySettings,\n        );\n        this.tc.init(await Data.constructSettings(this, obj));\n    }\n\n    async saveSettings(): Promise<void> {\n        await this.saveData(this.tc.settings);\n\n        if (this.handlersRegistered) {\n            this.unregisterHandlers();\n            this.registerHandlers();\n        }\n        if (this.commandsRegistered) {\n            this.unregisterCommands();\n            this.registerCommands();\n        }\n    }\n}\n\nexport function inlinePlugin(tcp: TaskCollectorPlugin, tc: TaskCollector) {\n    return ViewPlugin.fromClass(\n        class {\n            private readonly view: EditorView;\n            private readonly eventHandler: (ev: MouseEvent) => void;\n            private readonly tcp: TaskCollectorPlugin;\n\n            constructor(view: EditorView) {\n                this.view = view;\n                this.tcp = tcp;\n\n                this.eventHandler = (ev: MouseEvent) => {\n                    void (async () => {\n                        const { target } = ev;\n                        const activeFile =\n                            this.tcp.app.workspace.getActiveFile();\n                        if (\n                            !activeFile ||\n                            !(target instanceof HTMLInputElement) ||\n                            target.type !== \"checkbox\" ||\n                            target.classList.contains(\"metadata-input-checkbox\")\n                        ) {\n                            return;\n                        }\n                        tcp.tc.logDebug(\n                            \"TC ViewPlugin: click\",\n                            target,\n                            target.classList,\n                        );\n                        ev.stopImmediatePropagation();\n                        ev.preventDefault();\n\n                        const mark = await promptForMark(this.tcp.app, tc);\n                        if (!mark) {\n                            return;\n                        }\n                        await this.tcp.app.vault.process(\n                            activeFile,\n                            (source): string => {\n                                const position = this.view.posAtDOM(target);\n                                const line = view.state.doc.lineAt(position);\n                                const i = source.split(\"\\n\").indexOf(line.text);\n\n                                tc.logDebug(\n                                    \"TC ViewPlugin: mark task\",\n                                    activeFile.path,\n                                    mark,\n                                    line,\n                                    i,\n                                );\n\n                                if (tcp.tc.anyTaskMark.test(line.text)) {\n                                    return tc.markSelectedTask(source, mark, [\n                                        i,\n                                    ]);\n                                }\n                                const offset = Number(target.dataset.line);\n                                return tc.markSelectedTask(source, mark, [\n                                    i + offset,\n                                ]);\n                            },\n                        );\n                    })();\n                };\n\n                this.view.dom.addEventListener(\"click\", this.eventHandler);\n                tcp.tc.logDebug(\"TC ViewPlugin: create click handler\");\n            }\n\n            destroy() {\n                this.view.dom.removeEventListener(\"click\", this.eventHandler);\n                tcp.tc.logDebug(\"TC ViewPlugin: destroy click handler\");\n            }\n        },\n    );\n}\n"
  },
  {
    "path": "src/taskcollector-SettingsTab.ts",
    "content": "import {\n    type App,\n    type ButtonComponent,\n    debounce,\n    Notice,\n    PluginSettingTab,\n    Setting,\n} from \"obsidian\";\nimport type {\n    CollectionSettings,\n    ManipulationSettings,\n    TaskCollectorSettings,\n} from \"./@types/settings\";\nimport type TaskCollectorPlugin from \"./main\";\nimport { momentFn } from \"./moment\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_COLLECTION,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS,\n    TEXT_ONLY_MARK,\n    TEXT_ONLY_NAME,\n} from \"./taskcollector-Constants\";\nimport { Data } from \"./taskcollector-Data\";\nimport { _regex, type TaskCollector } from \"./taskcollector-TaskCollector\";\n\nexport class TaskCollectorSettingsTab extends PluginSettingTab {\n    plugin: TaskCollectorPlugin;\n    tc: TaskCollector;\n    newSettings: TaskCollectorSettings;\n    groupList: HTMLDListElement;\n    markInputCache: Record<string, Set<HTMLInputElement>> = {};\n    otherInputCache: Record<string, HTMLInputElement> = {};\n    saveButton: HTMLElement;\n\n    constructor(\n        app: App,\n        plugin: TaskCollectorPlugin,\n        taskCollector: TaskCollector,\n    ) {\n        super(app, plugin);\n        this.plugin = plugin;\n        this.tc = taskCollector;\n        this.icon = \"tornado\";\n        this.newSettings = DEFAULT_SETTINGS;\n    }\n\n    async save() {\n        Data.sanitize(this.plugin, this.newSettings);\n        if (this.tc.isDirty(this.newSettings)) {\n            if (this.tc.handlerChanged(this.newSettings)) {\n                new Notice(\n                    \"Updated Live Preview settings; restart Obsidian to apply changes.\",\n                );\n            }\n            this.tc.init(this.newSettings);\n            await this.plugin.saveSettings();\n            this.tc.notify(\"(TC) Configuration saved\");\n        }\n    }\n\n    /** Save on exit */\n    hide(): void {\n        void this.save();\n    }\n\n    /** Show/validate setting changes */\n    display(): void {\n        this.newSettings = JSON.parse(\n            JSON.stringify(this.tc.settings),\n        ) as TaskCollectorSettings;\n        this.drawElements();\n    }\n\n    drawElements(): void {\n        this.containerEl.empty();\n        this.containerEl.addClass(\"task-collector-settings\");\n\n        new Setting(this.containerEl).setHeading().setName(\"Task Collector\");\n\n        new Setting(this.containerEl)\n            .setName(\"Save settings\")\n            .setClass(\"task-collector-save-reset\")\n            .addButton((button) =>\n                button\n                    .setIcon(\"reset\")\n                    .setTooltip(\n                        \"Reset to previously saved (or generated) values\",\n                    )\n                    .onClick(() => {\n                        this.newSettings = JSON.parse(\n                            JSON.stringify(this.tc.settings),\n                        ) as TaskCollectorSettings;\n                        this.display();\n                        const message = \"(TC) Configuration reset\";\n                        this.tc.notify(message);\n                    }),\n            )\n            .addButton((button) => {\n                button\n                    .setIcon(\"save\")\n                    .setTooltip(\"Save current values\")\n                    .onClick(async () => {\n                        await this.save();\n                    });\n                this.saveButton = button.buttonEl;\n            });\n\n        new Setting(this.containerEl)\n            .setName(\"Task collection\")\n            .setDesc(\n                \"Enable task collection (additional task group settings when enabled)\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.collectionEnabled)\n                    .onChange(async (value) => {\n                        const redraw =\n                            value !== this.newSettings.collectionEnabled;\n                        this.newSettings.collectionEnabled = value;\n                        if (redraw) {\n                            this.drawElements();\n                        }\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Define task mark cycle\")\n            .setDesc(\n                \"Specify characters (as a string) for previous/next commands. Use the button to include checkbox removal in the cycle.\",\n            )\n            .addText((input) =>\n                input\n                    .setPlaceholder(\"\")\n                    .setValue(this.newSettings.markCycle.replace(\"§\", \"\"))\n                    .onChange(async (value) => {\n                        this.newSettings.markCycle = [...new Set(value)].join(\n                            \"\",\n                        );\n                    }),\n            )\n            .addExtraButton((button) => {\n                const el = button\n                    .setTooltip(\n                        `Include checkbox removal in the cycle: ${this.newSettings.markCycleRemoveTask}`,\n                    )\n                    .setIcon(\"cross-in-box\")\n                    .onClick(() => {\n                        this.newSettings.markCycleRemoveTask =\n                            !this.newSettings.markCycleRemoveTask;\n                        el.classList.toggle(\n                            \"is-active\",\n                            this.newSettings.markCycleRemoveTask,\n                        );\n                        button.setTooltip(\n                            `Include checkbox removal in the cycle: ${this.newSettings.markCycleRemoveTask}`,\n                        );\n                    }).extraSettingsEl;\n                el.classList.toggle(\n                    \"is-active\",\n                    this.newSettings.markCycleRemoveTask,\n                );\n            });\n\n        new Setting(this.containerEl)\n            .setName(\"Convert non-list lines\")\n            .setDesc(\"Converts non-list lines when marking tasks\")\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.convertEmptyLines)\n                    .onChange(async (value) => {\n                        this.newSettings.convertEmptyLines = value;\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Skip matching sections\")\n            .setDesc(\n                \"When collecting tasks, skip content of sections that match the specified pattern\",\n            )\n            .addText((input) =>\n                input\n                    .setPlaceholder(\"\")\n                    .setValue(this.newSettings.skipSectionMatch)\n                    .onChange(async (value) => {\n                        this.newSettings.skipSectionMatch = value;\n                    }),\n            );\n\n        new Setting(this.containerEl).setHeading().setName(\"Task groups\");\n\n        this.containerEl.createEl(\"p\", {\n            text:\n                \"Task collector configures tasks in groups. \" +\n                \"Each group can be associated with one or more task marks ('x' or '>'). \" +\n                \"The default group configuration will apply to any mark not otherwise assigned to a group.\",\n        });\n\n        this.containerEl.createEl(\"p\", {\n            text:\n                \"Marks that you define within the following groups appear in the selection modal. \" +\n                \"Those marks that 'complete' a task will appear in the top row.\",\n        });\n\n        this.groupList = this.containerEl.createEl(\"dl\");\n        this.showTaskGroups();\n\n        new Setting(this.containerEl)\n            .setClass(\"tc-create-task-group\")\n            .addButton((button: ButtonComponent) =>\n                button\n                    .setTooltip(\"Add a new task group\")\n                    .setButtonText(\"+\")\n                    .onClick(() => {\n                        const name = `group-${\n                            Object.values(this.newSettings.groups).length\n                        }`;\n                        Data.createSettingsGroup(\n                            this.newSettings.groups,\n                            name,\n                            {},\n                        );\n                        this.showTaskGroups();\n                    }),\n            );\n\n        new Setting(this.containerEl).setHeading().setName(\"Menus and modals\");\n\n        this.containerEl.createEl(\"p\", {\n            text:\n                \"Task Collector creates commands that can be bound to hotkeys or accessed using slash commands for marking tasks. \" +\n                \"The following settings add right click context menu items for those commands.\",\n        });\n\n        new Setting(this.containerEl)\n            .setName(\"Click handling: prompt when the checkbox is clicked\")\n            .setDesc(\n                \"When you click a checkbox, display a panel that allows you to select (with mouse or keyboard) the value to assign.\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.previewClickModal)\n                    .onChange(async (value) => {\n                        this.newSettings.previewClickModal = value;\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Add '(TC) Mark task' menu item\")\n            .setDesc(\n                \"Add an item to the right-click menu to mark the task on the current line (or within the current selection). This menu item will trigger a quick pop-up modal to select the desired mark value.\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.contextMenu.markTask)\n                    .onChange(async (value) => {\n                        this.newSettings.contextMenu.markTask = value;\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Add `(TC) Collect tasks` menu item\")\n            .setDesc(\n                \"Add an item to the right-click menu to collect tasks (based on task configuration).\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.contextMenu.collectTasks)\n                    .onChange(async (value) => {\n                        this.newSettings.contextMenu.collectTasks = value;\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Add '(TC) Reset all tasks' command and menu item\")\n            .setDesc(\n                \"Add a command and an item to the right-click menu to reset/clear all tasks in the current file.\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.contextMenu.resetAllTasks)\n                    .onChange(async (value) => {\n                        this.newSettings.contextMenu.resetAllTasks = value;\n                    }),\n            );\n\n        new Setting(this.containerEl).setHeading().setName(\"Other settings\");\n\n        new Setting(this.containerEl)\n            .setName(\"Hide notifications\")\n            .setDesc(\n                \"Hide pop-up notification messages (messages will be logged in the developer console)\",\n            )\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.hideNotifications)\n                    .onChange(async (value) => {\n                        this.newSettings.hideNotifications = value;\n                    }),\n            );\n\n        new Setting(this.containerEl)\n            .setName(\"Debug\")\n            .setDesc(\"Enable debug messages\")\n            .addToggle((toggle) =>\n                toggle\n                    .setValue(this.newSettings.debug)\n                    .onChange(async (value) => {\n                        this.newSettings.debug = value;\n                    }),\n            );\n    }\n\n    showTaskGroups() {\n        this.markInputCache = {};\n        this.otherInputCache = {};\n        this.groupList.empty();\n        this.clearButtonErrors();\n\n        // default always comes first\n        this.createGroupItem(this.newSettings.groups[DEFAULT_NAME]);\n\n        // any/everything else\n        for (const mts of Object.values(this.newSettings.groups)) {\n            if (mts.name !== DEFAULT_NAME) {\n                this.createGroupItem(mts);\n            }\n        }\n    }\n\n    createGroupItem(mts: ManipulationSettings) {\n        const dt = this.groupList.createEl(\"dt\");\n        const itemEl = this.groupList.createEl(\"dd\");\n        let testSetting: Setting;\n\n        const nameSetting = new Setting(dt)\n            .setName(\"Group name\")\n            .setDesc(\"Name for this group\")\n            .setClass(\"task-group-name\");\n        if (mts.name === TEXT_ONLY_NAME) {\n            nameSetting.addExtraButton((b) => {\n                b.setIcon(\"info\")\n                    .setTooltip(\n                        \"This is a special group that supports appending text to arbitrary lines of text\",\n                    )\n                    .setDisabled(true);\n            });\n        }\n        nameSetting.addText((text) => {\n            text.setPlaceholder(COMPLETE_NAME)\n                .setValue(mts.name)\n                .setDisabled(mts.name === DEFAULT_NAME)\n                .onChange(\n                    debounce(\n                        (value) => {\n                            const target = this.newSettings.groups[value];\n                            if (!value) {\n                                text.inputEl.addClass(\"data-value-error\");\n                                text.inputEl.setAttribute(\n                                    \"aria-label\",\n                                    \"A group name is required.\",\n                                );\n                            } else if (target && target !== mts) {\n                                text.inputEl.addClass(\"data-value-error\");\n                                text.inputEl.setAttribute(\n                                    \"aria-label\",\n                                    \"This name is already used by another group\",\n                                );\n                            } else {\n                                text.inputEl.removeClass(\"data-value-error\");\n                                text.inputEl.removeAttribute(\"aria-label\");\n                                Data.moveGroup(\n                                    this.plugin,\n                                    this.newSettings.groups,\n                                    mts.name,\n                                    value,\n                                );\n                                if (value === TEXT_ONLY_NAME) {\n                                    mts.marks = TEXT_ONLY_MARK;\n                                    // we just created the text group, redraw / rebuild cache\n                                    this.drawElements();\n                                }\n                            }\n                            this.testForErrors();\n                        },\n                        50,\n                        true,\n                    ),\n                );\n        });\n        nameSetting.addExtraButton((b) => {\n            b.setIcon(mts.name === DEFAULT_NAME ? \"info\" : \"trash\")\n                .setTooltip(\n                    mts.name === DEFAULT_NAME\n                        ? \"Default task settings\"\n                        : \"Delete this group\",\n                )\n                .setDisabled(mts.name === DEFAULT_NAME)\n                .onClick(async () => {\n                    delete this.newSettings.groups[mts.name];\n                    this.showTaskGroups();\n                });\n        });\n        if (mts.name === DEFAULT_NAME) {\n            nameSetting.controlEl.addClass(\"default-group\");\n        } else if (mts.name === TEXT_ONLY_NAME) {\n            nameSetting.controlEl.addClass(\"text-only-group\");\n        }\n\n        if (mts.name !== TEXT_ONLY_NAME) {\n            const taskMarks = new Setting(itemEl)\n                .setName(\"Task marks\")\n                .setClass(\"task-marks\");\n\n            if (mts.name !== DEFAULT_NAME) {\n                taskMarks.addToggle((t) => {\n                    t.setValue(mts.complete);\n                    t.setTooltip(\n                        \"If enabled, this group represents completed items. Completed items appear in the top row of the selection menu.\",\n                    ).onChange(async (value) => {\n                        mts.complete = value;\n                    });\n                });\n                taskMarks.setDesc(\n                    \"Set marks associated with this group as a string, for example: '>?!'. Use a space for unmarked tasks. \" +\n                        \"Enable the toggle if this group represents completed tasks.\",\n                );\n            } else {\n                taskMarks.setDesc(\n                    \"Set marks associated with this group as a string, for example: '>?!'. Use a space for unmarked tasks. \",\n                );\n            }\n\n            taskMarks.addText((input) => {\n                input.setPlaceholder(\"xX\").onChange(\n                    debounce(\n                        (value) => {\n                            const newMarks = Data.sanitizeMarks(value);\n                            if (newMarks !== value) {\n                                input.inputEl.value = newMarks;\n                            }\n                            if (newMarks !== mts.marks) {\n                                this.removeMarks(mts.marks, input.inputEl);\n\n                                mts.marks = newMarks;\n                                taskMarks.controlEl.setAttribute(\n                                    \"marks\",\n                                    mts.marks,\n                                );\n\n                                this.findDuplicates(input.inputEl);\n                            }\n                        },\n                        50,\n                        true,\n                    ),\n                );\n                // sanitize and display initial value\n                mts.marks = Data.sanitizeMarks(mts.marks);\n                input.setValue(mts.marks);\n                taskMarks.controlEl.setAttribute(\"marks\", mts.marks);\n                this.findDuplicates(input.inputEl);\n            });\n        }\n\n        new Setting(itemEl)\n            .setName(`Append date to ${this.getDescription(mts)}`)\n            .setDesc(\n                `Append today's date in the given moment.js format to the end of the ${this.getDescription(\n                    mts,\n                )}`,\n            )\n            .addMomentFormat((momentFormat) => {\n                momentFormat\n                    .setPlaceholder(\"YYYY-MM-DD\")\n                    .setValue(mts.appendDateFormat)\n                    .onChange(\n                        debounce(\n                            (value) => {\n                                try {\n                                    // Try formatting \"now\" with the specified format string\n                                    const now = momentFn().format(value);\n                                    momentFormat.inputEl.removeClass(\n                                        \"data-value-error\",\n                                    );\n                                    momentFormat.inputEl.setAttribute(\n                                        \"aria-label\",\n                                        now,\n                                    );\n                                    mts.appendDateFormat = value;\n                                } catch (e) {\n                                    momentFormat.inputEl.addClass(\n                                        \"data-value-error\",\n                                    );\n                                    momentFormat.inputEl.setAttribute(\n                                        \"aria-label\",\n                                        \"An error occurred parsing this moment string. See log for details.\",\n                                    );\n                                    console.error(\n                                        `Error parsing specified date format for ${mts.name}: ${value}`,\n                                        e,\n                                    );\n                                }\n                                this.testForErrors();\n                            },\n                            200,\n                            true,\n                        ),\n                    );\n            });\n\n        new Setting(itemEl)\n            .setName(\n                `Remove text matching pattern from ${this.getDescription(mts)}`,\n            )\n            .setDesc(\n                `Text matching this regular expression will be removed from ${this.getDescription(\n                    mts,\n                )}. Be careful! Test your expression first. The global flag ('g') is used for a per-line match.`,\n            )\n            .addText((text) =>\n                text\n                    .setPlaceholder(\" #(todo|task)\")\n                    .setValue(mts.removeExpr)\n                    .onChange(\n                        debounce(\n                            (value) => {\n                                if (!value) {\n                                    testSetting.settingEl.addClass(\n                                        \"regex-hidden\",\n                                    );\n                                    return;\n                                }\n                                testSetting.settingEl.removeClass(\n                                    \"regex-hidden\",\n                                );\n                                try {\n                                    // try compiling the regular expression\n                                    const regex =\n                                        _regex.tryRemoveTextRegex(value);\n                                    mts.removeExpr = value;\n\n                                    // Visual feedback for valid regex\n                                    text.inputEl.removeClass(\n                                        \"data-value-error\",\n                                    );\n                                    if (value) {\n                                        // Check for likely over-escaping (e.g., \\\\\\\\d instead of \\\\d)\n                                        const hasDoubleEscapes =\n                                            /\\\\\\\\[dswDSW]|\\\\\\\\[{}[\\]]/u.test(\n                                                value,\n                                            );\n                                        if (hasDoubleEscapes) {\n                                            text.inputEl.addClass(\n                                                \"data-value-error\",\n                                            );\n                                            text.inputEl.setAttribute(\n                                                \"aria-label\",\n                                                `Warning: Pattern may be over-escaped. Use \\\\d not \\\\\\\\d, \\\\{ not \\\\\\\\{, etc. Current: /${regex?.source}/g`,\n                                            );\n                                        } else {\n                                            text.inputEl.setAttribute(\n                                                \"aria-label\",\n                                                `Valid regex: /${regex?.source || value}/g`,\n                                            );\n                                        }\n                                    } else {\n                                        text.inputEl.removeAttribute(\n                                            \"aria-label\",\n                                        );\n                                    }\n\n                                    this.tc.logDebug(\n                                        \"remove regex\",\n                                        mts.name,\n                                        mts.removeExpr,\n                                    );\n\n                                    // Update test field if it exists\n                                    const testInput =\n                                        this.otherInputCache[\n                                            `removeExpr-test-${mts.name}`\n                                        ];\n                                    if (testInput && regex && testInput.value) {\n                                        this.updateTestResult(\n                                            testInput,\n                                            regex,\n                                            mts.name,\n                                        );\n                                    }\n                                } catch (e) {\n                                    // Visual feedback for invalid regex\n                                    text.inputEl.addClass(\"data-value-error\");\n                                    const msg =\n                                        e instanceof Error\n                                            ? e.message\n                                            : String(e);\n                                    text.inputEl.setAttribute(\n                                        \"aria-label\",\n                                        `Invalid regex: ${msg}`,\n                                    );\n                                    console.error(\n                                        `Error parsing specified text replacement regular expression for ${mts.name}: ${value}`,\n                                        e,\n                                    );\n                                }\n                                this.testForErrors();\n                            },\n                            50,\n                            true,\n                        ),\n                    ),\n            );\n\n        // Add a test field below the regex input\n        testSetting = new Setting(itemEl)\n            .setClass(\"regex-test-setting\")\n            .setDesc(\n                \"Test your regex: Enter sample text to see what will be removed\",\n            )\n            .addText((testInput) => {\n                testInput.setPlaceholder(\"- [ ] something #todo\").onChange(\n                    debounce(\n                        (value) => {\n                            this.otherInputCache[\n                                `removeExpr-test-${mts.name}`\n                            ] = testInput.inputEl;\n                            if (mts.removeExpr) {\n                                try {\n                                    const regex = _regex.tryRemoveTextRegex(\n                                        mts.removeExpr,\n                                    );\n                                    if (regex && value) {\n                                        this.updateTestResult(\n                                            testInput.inputEl,\n                                            regex,\n                                            mts.name,\n                                        );\n                                    } else {\n                                        testInput.inputEl.removeAttribute(\n                                            \"aria-label\",\n                                        );\n                                    }\n                                } catch (e) {\n                                    testInput.inputEl.setAttribute(\n                                        \"aria-label\",\n                                        \"Cannot test: regex is invalid\",\n                                    );\n                                    console.debug(\"Invalid regex\", e);\n                                }\n                            }\n                        },\n                        100,\n                        true,\n                    ),\n                );\n\n                // Cache the input element for updates when regex changes\n                this.otherInputCache[`removeExpr-test-${mts.name}`] =\n                    testInput.inputEl;\n            });\n\n        if (!mts.removeExpr) {\n            testSetting.settingEl.addClass(\"regex-hidden\");\n        }\n\n        new Setting(itemEl)\n            .setName(\"Register '(TC) Mark with... ' command\")\n            .setDesc(\n                mts.name === TEXT_ONLY_NAME\n                    ? \"A command will be registered to append text to selected lines\"\n                    : \"A command will be registered for each mark in the group.\",\n            )\n            .addToggle((toggle) =>\n                toggle.setValue(mts.registerCommand).onChange((value) => {\n                    mts.registerCommand = value;\n                }),\n            );\n\n        new Setting(itemEl)\n            .setName(\"Add '(TC) Mark with... ' menu item\")\n            .setDesc(\n                \"A right-click menu item will be added for each mark in the group.\",\n            )\n            .addToggle((toggle) =>\n                toggle.setValue(mts.useContextMenu).onChange(async (value) => {\n                    mts.useContextMenu = value;\n                }),\n            );\n\n        if (this.newSettings.collectionEnabled && mts.name !== TEXT_ONLY_NAME) {\n            if (!mts.collection) {\n                mts.collection = JSON.parse(\n                    JSON.stringify(DEFAULT_COLLECTION),\n                ) as CollectionSettings;\n            }\n            new Setting(itemEl)\n                .setName(\"Area heading\")\n                .setClass(\"area-heading\")\n                .setDesc(\n                    \"Marked tasks will be collected and moved under the specified heading. Task collection for a group only occurs when an area heading is configured.\",\n                )\n                .addText((text) =>\n                    text\n                        .setPlaceholder(\"## Example\")\n                        .setValue(mts.collection.areaHeading)\n                        .onChange(async (value) => {\n                            mts.collection.areaHeading = value;\n                        }),\n                );\n            new Setting(itemEl)\n                .setName(\"Remove checkbox\")\n                .setClass(\"remove-checkbox\")\n                .setDesc(\"When a task is collected, remove the checkbox\")\n                .addToggle((toggle) =>\n                    toggle\n                        .setValue(mts.collection.removeCheckbox)\n                        .onChange(async (value) => {\n                            mts.collection.removeCheckbox = value;\n                        }),\n                );\n        }\n    }\n\n    private removeMarks(oldValue: string, input: HTMLInputElement) {\n        const marks = oldValue ? oldValue.split(\"\") : [];\n        this.tc.logDebug(\n            `removeMarks begin: '${oldValue}'`,\n            this.markInputCache,\n        );\n\n        if (input.hasClass(\"no-marks-defined\")) {\n            input.removeClass(\"no-marks-defined\");\n            input.removeClass(\"data-value-error\");\n            input.removeAttribute(\"aria-label\");\n        }\n\n        for (const x of marks) {\n            this.tc.logDebug(\n                `(TC): remove mark '${x}'`,\n                this.markInputCache[x],\n            );\n            if (this.markInputCache[x]) {\n                const set = this.markInputCache[x];\n                set.delete(input);\n                this.tryRemoveConflict(x, input);\n\n                // if there is only one element left in the array,\n                // remove the current character from the list of conflicts\n                if (set.size === 1) {\n                    for (const i of set) {\n                        this.tryRemoveConflict(x, i);\n                    }\n                }\n            }\n        }\n\n        this.tc.logDebug(`removeMarks end: '${oldValue}'`, this.markInputCache);\n    }\n\n    private findDuplicates(input: HTMLInputElement) {\n        const marks = input.value ? input.value.split(\"\") : [];\n        this.tc.logDebug(\n            `findDuplicates begin: '${input.value}'`,\n            marks,\n            input,\n            this.markInputCache,\n        );\n\n        // add input element into the cache (new marks)\n        for (const x of marks) {\n            if (this.markInputCache[x]) {\n                const set = this.markInputCache[x];\n                set.add(input);\n\n                if (set.size > 1) {\n                    // we have a conflict over a defined task mark\n                    for (const i of set) {\n                        this.trySetConflict(x, i);\n                    }\n                    console.error(\n                        `(TC) More then one group uses task mark ${this.showMark(\n                            x,\n                        )}`,\n                    );\n                }\n            } else {\n                // no conflict, all is well.\n                this.markInputCache[x] = new Set();\n                this.markInputCache[x].add(input);\n            }\n        }\n\n        if (marks.length === 0) {\n            input.addClass(\"no-marks-defined\");\n            input.addClass(\"data-value-error\");\n            input.setAttribute(\n                \"aria-label\",\n                this.newSettings.groups[TEXT_ONLY_NAME]\n                    ? \"Must define one or more marks for this group.\"\n                    : `Must define one or more marks for this group. Change the name to '${TEXT_ONLY_NAME}' for special text-only behavior.`,\n            );\n            this.tc.logDebug(\n                `findDuplicates end (empty): '${input.value}'`,\n                input,\n                this.markInputCache,\n            );\n        }\n\n        this.tc.logDebug(\n            `findDuplicates end: '${input.value}'`,\n            input,\n            this.markInputCache,\n        );\n        this.testForErrors();\n    }\n\n    private trySetConflict(mark: string, input: HTMLInputElement) {\n        const existing = input.getAttribute(\"conflict\") || \"\";\n        const conflict = Data.sanitizeMarks(existing + mark);\n\n        input.setAttribute(\"conflict\", conflict);\n        input.addClass(\"data-value-error\");\n        input.setAttribute(\n            \"aria-label\",\n            `More than one task group uses ${this.showMark(conflict)}`,\n        );\n        this.tc.logDebug(\n            `conflicts for '${input.value}': '${this.showMark(conflict)}'`,\n        );\n    }\n\n    private tryRemoveConflict(mark: string, input: HTMLInputElement) {\n        if (!input.hasAttribute(\"conflict\")) {\n            return;\n        }\n        const remaining = input.getAttribute(\"conflict\").replace(mark, \"\");\n        if (remaining.length === 0) {\n            // all conflicting marks have been removed\n            input.removeAttribute(\"conflict\");\n            input.removeClass(\"data-value-error\");\n            input.removeAttribute(\"aria-label\");\n        } else {\n            input.removeAttribute(\"conflict\");\n            this.trySetConflict(remaining, input);\n        }\n    }\n\n    private getDescription(mts: ManipulationSettings) {\n        return mts.name === TEXT_ONLY_NAME\n            ? \"selected lines of text\"\n            : \"selected task(s)\";\n    }\n\n    private showMark(x: string) {\n        return x === TEXT_ONLY_MARK ? \"(empty)\" : x;\n    }\n\n    private clearButtonErrors() {\n        // Modal create or reset\n        this.saveButton.removeClass(\"data-value-error\");\n        this.saveButton.removeAttribute(\"aria-label\");\n    }\n\n    private testForErrors() {\n        const hasMarkErrors = Object.values(this.markInputCache)\n            .flatMap((s) => Array.from(s.values()))\n            .find((i) => i.hasClass(\"data-value-error\"));\n        const hasMomentErrors = Object.values(this.otherInputCache).find((i) =>\n            i.hasClass(\"data-value-error\"),\n        );\n\n        if (hasMarkErrors || hasMomentErrors) {\n            this.saveButton.addClass(\"data-value-error\");\n            this.saveButton.setAttribute(\n                \"aria-label\",\n                \"There are configuration errors. Correct those before saving.\",\n            );\n        } else {\n            this.saveButton.removeClass(\"data-value-error\");\n            this.saveButton.removeAttribute(\"aria-label\");\n        }\n    }\n\n    private updateTestResult(\n        testInput: HTMLInputElement,\n        regex: RegExp,\n        groupName: string,\n    ) {\n        const testText = testInput.value;\n        if (!testText) {\n            testInput.removeAttribute(\"aria-label\");\n            return;\n        }\n\n        // Test what will be removed by the regex\n        const match = testText.match(regex);\n        if (match) {\n            const removed = match[0];\n            const result = testText.replace(new RegExp(regex.source, \"g\"), \"\");\n            testInput.setAttribute(\n                \"aria-label\",\n                `Will remove: \"${removed}\" → Result: \"${result}\"`,\n            );\n            this.tc.logDebug(\n                `removeExpr test for ${groupName}`,\n                `Input: \"${testText}\"`,\n                `Matched: \"${removed}\"`,\n                `Result: \"${result}\"`,\n            );\n        } else {\n            testInput.setAttribute(\n                \"aria-label\",\n                \"No match - nothing will be removed from this text\",\n            );\n            this.tc.logDebug(\n                `removeExpr test for ${groupName}`,\n                `Input: \"${testText}\"`,\n                \"No match\",\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/taskcollector-TaskCollector.ts",
    "content": "import { Notice } from \"obsidian\";\nimport type {\n    ManipulationSettings,\n    TaskCollectorCache,\n    TaskCollectorSettings,\n    TcSection,\n} from \"./@types/settings\";\nimport { momentFn } from \"./moment\";\nimport {\n    CACHE_DEFAULT,\n    DEFAULT_NAME,\n    TEXT_ONLY_MARK,\n    TEXT_ONLY_NAME,\n} from \"./taskcollector-Constants\";\nimport { Data } from \"./taskcollector-Data\";\n\nconst DATE_FORMATTING_TOKENS = /^(Y|D|M|H|h|m)+$/;\nconst ALL_FORMATTING_TOKENS =\n    /(\\[[^[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;\n\nexport enum Direction {\n    PREV = \"PREV\",\n    NEXT = \"NEXT\",\n}\n\nexport class TaskCollector {\n    settings: TaskCollectorSettings;\n    cache: TaskCollectorCache;\n\n    anyListItem = new RegExp(/^([\\s>]*(?:-|\\+|\\*|\\d+\\.) )([^\\\\[].*)$/);\n    anyTaskMark = new RegExp(/^([\\s>]*(?:-|\\+|\\*|\\d+\\.) \\[)(.)(\\] .*)$/);\n    anyText = new RegExp(/^([\\s>]*)(.*)$/);\n    blockQuote = new RegExp(/^(\\s*>[\\s>]*)(.*)$/);\n    blockRef = new RegExp(/^(.*?)( \\^[A-Za-z0-9-]+)?$/);\n    continuation = new RegExp(/^( {2,}|\\t)/);\n    stripTask = new RegExp(/^([\\s>]*(?:-|\\+|\\*|\\d+\\.)) \\[.\\] (.*)$/);\n\n    init(settings: TaskCollectorSettings): void {\n        this.settings = settings;\n        this.cache = JSON.parse(\n            JSON.stringify(CACHE_DEFAULT),\n        ) as TaskCollectorCache;\n\n        this.cache.useContextMenu =\n            settings.contextMenu.markTask ||\n            settings.contextMenu.resetTask ||\n            settings.contextMenu.collectTasks ||\n            settings.contextMenu.resetAllTasks;\n\n        for (const v of Object.values(settings.groups)) {\n            this.cacheTaskSettings(v, this.cache);\n        }\n\n        // Store sorted unique list of completion area headings\n        if (this.settings.collectionEnabled) {\n            this.cache.areaHeadings = [\n                ...Object.keys(this.cache.headingToMark),\n            ];\n            this.cache.areaHeadings.sort();\n        }\n        this.cache.completedMarks = Data.sanitizeMarks(\n            this.cache.completedMarks,\n        );\n        this.cache.incompleteMarks = Data.sanitizeMarks(\n            this.cache.incompleteMarks,\n        );\n        this.cache.skipSectionExpr = trySkipSectionRegex(\n            settings.skipSectionMatch,\n        );\n\n        this.logDebug(\"configuration read\", this.settings, this.cache);\n    }\n\n    handlerChanged(newSettings: TaskCollectorSettings) {\n        return (\n            this.settings.previewClickModal !== newSettings.previewClickModal\n        );\n    }\n\n    isDirty(newSettings: TaskCollectorSettings) {\n        return JSON.stringify(this.settings) !== JSON.stringify(newSettings);\n    }\n\n    logDebug(message: string, ...optionalParams: unknown[]): void {\n        if (!this.settings || this.settings.debug) {\n            console.debug(`(TC) ${message}`, ...optionalParams);\n        }\n    }\n\n    notify(message: string) {\n        if (this.settings?.hideNotifications) {\n            console.warn(message);\n        } else {\n            new Notice(message);\n        }\n    }\n\n    /**\n     * Process task manipulation settings and populate cache\n     * @param mts\n     * @param cache\n     */\n    private cacheTaskSettings(\n        mts: ManipulationSettings,\n        cache: TaskCollectorCache,\n    ) {\n        for (const x of mts.marks.split(\"\")) {\n            if (cache.marks[x]) {\n                const name = cache.marks[x].name;\n                console.warn(\n                    `Two groups of settings contain ${x}: ${name} and ${mts.name}. Using ${name}`,\n                );\n            } else {\n                // allow for lookup of this configuration per character\n                cache.marks[x] = mts;\n\n                // This specific configuration may want to add a context menu\n                cache.useContextMenu =\n                    cache.useContextMenu || mts.useContextMenu;\n\n                // store the regex for matching text to remove\n                if (mts.removeExpr) {\n                    const regex = tryRemoveTextRegex(mts.removeExpr);\n                    cache.removeExpr[mts.name] = regex;\n                }\n\n                // store the undo string for this collection of marks\n                if (mts.appendDateFormat) {\n                    const regex = tryUndoRegex(mts.appendDateFormat);\n                    cache.undoExpr[mts.name] = regex;\n                }\n\n                // store the area heading for this mark\n                if (mts.collection?.areaHeading) {\n                    if (cache.headingToMark[mts.collection.areaHeading]) {\n                        cache.headingToMark[mts.collection.areaHeading] += x;\n                    } else {\n                        cache.headingToMark[mts.collection.areaHeading] = x;\n                    }\n                }\n\n                if (x !== TEXT_ONLY_MARK) {\n                    if (mts.complete) {\n                        cache.completedMarks += x;\n                    } else {\n                        cache.incompleteMarks += x;\n                    }\n                }\n            }\n        }\n    }\n\n    // Mark tasks\n\n    /**\n     * Mark selected tasks\n     * @param source\n     * @param direction: -1 or 1\n     * @param lines\n     */\n    markInCycle(source: string, d: Direction, lines: number[] = []): string {\n        const split = source.split(\"\\n\");\n        const len = this.settings.markCycle.length;\n        for (const n of lines) {\n            const taskMatch = this.anyTaskMark.exec(split[n]);\n            const listMatch = this.anyListItem.exec(split[n]);\n            if (taskMatch) {\n                // already a task: change from old to new\n                const old = taskMatch[2];\n                const i = this.settings.markCycle.indexOf(old);\n                const next =\n                    i < 0\n                        ? d === Direction.NEXT // i < 0\n                            ? 0 // NEXT\n                            : len - 1 // PREV\n                        : d === Direction.NEXT // i >= 0\n                          ? (i + 1) % len // NEXT\n                          : (i + len - 1) % len; // PREV\n\n                const chosenMark = this.settings.markCycle[next];\n                if (chosenMark === \"§\") {\n                    split[n] = this.doRemoveTask(split[n]);\n                } else {\n                    split[n] = this.doMarkTask(split[n], old, chosenMark);\n                }\n            } else if (listMatch?.[2]) {\n                const cycle = this.settings.markCycle.replace(\"§\", \"\");\n                const chosenMark =\n                    cycle[d === Direction.NEXT ? 0 : cycle.length - 1];\n                // convert to a task, and then mark\n                split[n] = this.updateLineText(\n                    `${listMatch[1]}[ ] ${listMatch[2]}`,\n                    chosenMark,\n                );\n                this.logDebug(\"task marked\", n, `|${split[n]}|`);\n            }\n        }\n        return split.join(\"\\n\");\n    }\n\n    /**\n     * Mark selected tasks\n     * @param source\n     * @param mark\n     * @param lines\n     */\n    markSelectedTask(\n        source: string,\n        mark: string,\n        lines: number[] = [],\n    ): string {\n        const split = source.split(\"\\n\");\n        for (const n of lines) {\n            split[n] = this.updateLineText(split[n], mark);\n            this.logDebug(\"task marked\", n, `|${split[n]}|`);\n        }\n        return split.join(\"\\n\");\n    }\n\n    /**\n     * Update the task in the provided line text to use\n     * the specified mark\n     * @param lineText\n     * @param mark\n     */\n    updateLineText(lineText: string, existingMark: string): string {\n        let mark = existingMark;\n        if (mark === \"Backspace\") {\n            return this.doRemoveTask(lineText);\n        }\n        if (mark === \"\") {\n            mark = TEXT_ONLY_MARK;\n        }\n\n        if (mark === TEXT_ONLY_MARK && this.cache.marks[TEXT_ONLY_MARK]) {\n            // append general text. Do not convert to or mess with the task-nature\n            return this.doAppendText(lineText);\n        }\n        const taskMatch = this.anyTaskMark.exec(lineText);\n        if (taskMatch) {\n            // already a task: change from old to new\n            const old = taskMatch[2];\n            return this.doMarkTask(lineText, old, mark);\n        }\n        const listMatch = this.anyListItem.exec(lineText);\n        if (listMatch?.[2]) {\n            // convert to a task, and then mark (recurse)\n            return this.updateLineText(\n                `${listMatch[1]}[ ] ${listMatch[2]}`,\n                mark,\n            );\n        }\n        if (this.settings.convertEmptyLines) {\n            const indentMatch = this.anyText.exec(lineText);\n            if (indentMatch) {\n                // split line on first character\n                return this.updateLineText(\n                    `${indentMatch[1]}- [ ] ${indentMatch[2]}`,\n                    mark,\n                );\n            }\n        }\n\n        this.logDebug(\"not a task or list item %s\", `|${lineText}|`);\n        return lineText;\n    }\n\n    private doAppendText(existingLine: string, append = true): string {\n        let lineText = existingLine;\n        // remember line ending: block id and strict line ending whitespace\n        let blockid = \"\";\n        const strictLineEnding = lineText.endsWith(\"  \");\n        const match = this.blockRef.exec(lineText);\n        if (match?.[2]) {\n            lineText = match[1];\n            blockid = match[2];\n        }\n\n        // Apply text-only configuration\n        const undoExpr = this.cache.undoExpr[TEXT_ONLY_NAME];\n        if (undoExpr) {\n            lineText = lineText.replace(undoExpr, \"\");\n        }\n        if (append) {\n            const removeExpr = this.cache.removeExpr[TEXT_ONLY_NAME];\n            if (removeExpr) {\n                lineText = lineText.replace(removeExpr, \"\");\n            }\n            const appendExpr =\n                this.settings.groups[TEXT_ONLY_NAME].appendDateFormat;\n            if (appendExpr) {\n                if (!lineText.endsWith(\" \")) {\n                    lineText += \" \";\n                }\n                lineText += momentFn().format(appendExpr);\n            }\n        }\n\n        // restore block id & trailing whitespace\n        lineText = lineText.replace(/\\s*$/, blockid);\n        if (strictLineEnding) {\n            lineText += \"  \";\n        }\n        this.logDebug(\"text updated\", `|${lineText}|`);\n        return lineText;\n    }\n\n    private doMarkTask(\n        existingLine: string,\n        old: string,\n        mark: string,\n    ): string {\n        let lineText = existingLine;\n        if (old === mark) {\n            this.logDebug(\"task already marked\", `|${lineText}|`);\n            return lineText;\n        }\n\n        const oldMarkName = this.cache.marks[old]?.name || DEFAULT_NAME;\n        const newMarkName = this.cache.marks[mark]?.name || DEFAULT_NAME;\n\n        // replace the task mark\n        lineText = lineText.replace(this.anyTaskMark, `$1${mark}$3`);\n\n        // remember line ending: block id and strict line ending whitespace\n        let blockid = \"\";\n        const strictLineEnding = lineText.endsWith(\"  \");\n        const match = this.blockRef.exec(lineText);\n        if (match?.[2]) {\n            lineText = match[1];\n            blockid = match[2];\n        }\n\n        const undoExpr = this.cache.undoExpr[oldMarkName];\n        if (undoExpr) {\n            lineText = lineText.replace(undoExpr, \"\");\n        }\n\n        const removeExpr = this.cache.removeExpr[newMarkName];\n        if (removeExpr) {\n            lineText = lineText.replace(removeExpr, \"\");\n        }\n\n        const appendExpr = this.settings.groups[newMarkName].appendDateFormat;\n        if (appendExpr) {\n            if (!lineText.endsWith(\" \")) {\n                lineText += \" \";\n            }\n            lineText += momentFn().format(appendExpr);\n        }\n\n        // append block id & replace ending whitespace\n        lineText = lineText.replace(/\\s*$/, blockid);\n        if (strictLineEnding) {\n            lineText += \"  \";\n        }\n        return lineText;\n    }\n\n    private doRemoveTask(lineText: string): string {\n        return lineText.replace(this.stripTask, \"$1 $2\");\n    }\n\n    // Reset all tasks not in a completion/skipped area\n\n    resetAllTasks(source: string): string {\n        const lines = source.split(\"\\n\");\n        const result: string[] = [];\n\n        let inCompletedSection = false;\n        let inSkippedSection = false;\n        for (const line of lines) {\n            const trimmed = line.trim();\n            if (inCompletedSection || inSkippedSection) {\n                if (line.startsWith(\"#\") || trimmed === \"---\") {\n                    inSkippedSection = this.isSkippedSection(line);\n                    inCompletedSection =\n                        contains(this.cache.areaHeadings, trimmed) !==\n                        undefined;\n                }\n                result.push(line);\n            } else if (trimmed.startsWith(\"#\") || trimmed === \"---\") {\n                inCompletedSection =\n                    contains(this.cache.areaHeadings, trimmed) !== undefined;\n                inSkippedSection = this.isSkippedSection(line);\n                result.push(line);\n            } else if (!(inCompletedSection || inSkippedSection)) {\n                result.push(line.replace(this.anyTaskMark, \"$1 $3\"));\n            }\n        }\n        return result.join(\"\\n\");\n    }\n\n    // Task Collection / Move tasks\n\n    /**\n     * Move marked task to the appropriate heading\n     * @param source\n     */\n    moveAllTasks(source: string): string {\n        if (this.cache.areaHeadings.length === 0) {\n            return source;\n        }\n\n        const parsed: string[] = [];\n        const headersInOrder: string[] = [];\n\n        // split out content for named sections\n        const sections = this.scan(source, parsed, headersInOrder);\n\n        // move general tasks to appropriate sections\n        const result = this.move(parsed, sections, headersInOrder, 0);\n\n        // in order of appearance from top to bottom\n        for (let i = 0; i < headersInOrder.length; i++) {\n            const [heading, bi] = headersInOrder[i].split(\"%:%\");\n            const bi2 = Number(bi);\n\n            // move existing tasks in sections to other sections\n            sections[heading].blocks[bi2].existing = this.move(\n                sections[heading].blocks[bi2].existing,\n                sections,\n                headersInOrder,\n                i,\n                this.cache.headingToMark[heading],\n            );\n        }\n\n        return result\n            .flatMap((l) => {\n                const match = l.match(/%%--TC--(.*)--(\\d+)--%%/);\n                if (match) {\n                    const h = match[1];\n                    const i = Number(match[2]);\n                    return sections[h].blocks[i].newTasks.concat(\n                        ...sections[h].blocks[i].existing,\n                    );\n                }\n                return l;\n            })\n            .join(\"\\n\");\n    }\n\n    private scan(\n        source: string,\n        parsed: string[],\n        headersInOrder: string[],\n    ): Record<string, TcSection> {\n        const split = source.split(\"\\n\");\n        this.ensureHeadings(split);\n\n        const sections: Record<string, TcSection> = {};\n        let activeSection: string[] = null;\n\n        // parse / analyze\n        for (const line of split) {\n            const trimmed = line.trim();\n\n            if (\n                line.startsWith(\"#\") &&\n                contains(this.cache.areaHeadings, trimmed)\n            ) {\n                parsed.push(line); // push heading to parsed lines\n                const index = this.createCompletionArea(trimmed, sections);\n\n                activeSection = sections[trimmed].blocks[index].existing;\n                parsed.push(`%%--TC--${trimmed}--${index}--%%`);\n                headersInOrder.push(`${trimmed}%:%${index}`);\n            } else if (\n                activeSection &&\n                (line.startsWith(\"#\") || line.trim() === \"---\")\n            ) {\n                activeSection = null;\n                parsed.push(line);\n            } else if (activeSection) {\n                activeSection.push(line);\n            } else {\n                parsed.push(line);\n            }\n        }\n        return sections;\n    }\n\n    private move(\n        source: string[],\n        sections: Record<string, TcSection>,\n        order: string[],\n        prevOrder: number,\n        excluded?: string,\n    ): string[] {\n        let orderIndex = prevOrder;\n        const remaining: string[] = [];\n\n        let markToMove = null;\n        let taskToBeMoved = null;\n        let inCallout = false;\n        let inSkippedSection = false;\n        let i = -1;\n\n        for (let line of source) {\n            i++;\n            if (line.startsWith(\"#\") || line.trim() === \"---\") {\n                inSkippedSection = this.isSkippedSection(line);\n                this.logDebug(\"TC: section\", line, inSkippedSection);\n            }\n            if (\n                taskToBeMoved &&\n                !this.isTaskLine(line) &&\n                this.isContinuation(line, inCallout, source, i)\n            ) {\n                // keep task lines together\n                taskToBeMoved.push(line);\n                continue;\n            }\n            if (taskToBeMoved) {\n                this.moveMark(\n                    markToMove,\n                    taskToBeMoved,\n                    sections,\n                    order,\n                    orderIndex,\n                );\n                markToMove = null;\n                taskToBeMoved = null;\n                inCallout = false;\n            }\n            if (line.startsWith(\"%%--TC--\")) {\n                // only applies to general text, not completion sections\n                // always preceded by a section heading\n                orderIndex = indexFromLine(line);\n                remaining.push(line);\n                continue;\n            }\n\n            const taskMatch = this.anyTaskMark.exec(line);\n            if (inSkippedSection) {\n                remaining.push(line);\n            } else if (taskMatch) {\n                const mark = taskMatch[2];\n                if (excluded && excluded.indexOf(mark) >= 0) {\n                    // we are in the target section for this mark\n                    remaining.push(line);\n                } else if (this.isCollected(mark)) {\n                    // start of task that should be moved to another section\n                    if (this.removeCheckbox(mark)) {\n                        line = this.doRemoveTask(line);\n                    }\n                    markToMove = mark;\n                    taskToBeMoved = [];\n                    taskToBeMoved.push(line);\n                    inCallout = this.isCallout(line); // is the task inside a callout\n                } else {\n                    // mark not configured for collection\n                    remaining.push(line);\n                }\n            } else {\n                remaining.push(line);\n            }\n        }\n        if (taskToBeMoved && markToMove) {\n            this.moveMark(\n                markToMove,\n                taskToBeMoved,\n                sections,\n                order,\n                orderIndex,\n            );\n        }\n        return remaining;\n    }\n\n    private moveMark(\n        markToMove: string,\n        taskToBeMoved: string[],\n        sections: Record<string, TcSection>,\n        order: string[],\n        orderIndex: number,\n    ) {\n        const heading = this.cache.marks[markToMove].collection.areaHeading;\n        const index = this.findNextSection(heading, order, orderIndex);\n\n        // add this task to the list of new tasks for the section\n        for (const l of taskToBeMoved) {\n            sections[heading].blocks[index].newTasks.push(l);\n        }\n    }\n\n    /**\n     * Find _next_ heading of the requested type (looping back to the beginning if necessary)\n     * @param heading\n     * @param order\n     * @param start\n     * @returns\n     */\n    private findNextSection(\n        heading: string,\n        order: string[],\n        start: number,\n    ): number | undefined {\n        let wrap = false;\n        for (let i = start; !wrap || i !== start; i++) {\n            if (i === order.length) {\n                i = 0;\n                wrap = true;\n            }\n            if (order[i].startsWith(heading)) {\n                const split = order[i].split(\"%:%\");\n                return Number(split[1]);\n            }\n        }\n        return undefined;\n    }\n\n    private createCompletionArea(\n        name: string,\n        sections: Record<string, TcSection>,\n    ): number {\n        if (!sections[name]) {\n            sections[name] = {\n                blocks: [],\n            };\n        }\n        sections[name].blocks.push({\n            existing: [],\n            newTasks: [],\n        });\n        return sections[name].blocks.length - 1;\n    }\n\n    private ensureHeadings(split: string[]) {\n        for (const h of this.cache.areaHeadings) {\n            if (!contains(split, h)) {\n                if (split[split.length - 1].trim() !== \"\") {\n                    split.push(\"\");\n                }\n                split.push(h);\n                split.push(\"\");\n            }\n        }\n    }\n\n    private isCollected(mark: string) {\n        return this.cache.marks[mark]?.collection?.areaHeading;\n    }\n\n    private removeCheckbox(mark: string) {\n        return this.cache.marks[mark]?.collection?.removeCheckbox;\n    }\n\n    private isSkippedSection(lineText: string): boolean {\n        return this.cache.skipSectionExpr?.test(lineText);\n    }\n\n    private isCallout(lineText: string): boolean {\n        return this.blockQuote.test(lineText);\n    }\n\n    private isTaskLine(lineText: string): boolean {\n        return this.anyTaskMark.test(lineText);\n    }\n\n    private isContinuation(\n        lineText: string,\n        inCallout: boolean,\n        source: string[],\n        i: number,\n    ): boolean {\n        if (inCallout) {\n            const match = this.blockQuote.exec(lineText);\n            if (match) {\n                return (\n                    match[1].endsWith(\">\") || // newline w/in callout\n                    match[1].endsWith(\"  \") || // leading whitespace\n                    match[1].endsWith(\"\\t\") // leading whitespace\n                );\n            }\n        }\n        if (lineText.length === 0) {\n            let j = i + 1;\n            while (j < source.length) {\n                if (source[j].length > 0) {\n                    return this.continuation.test(source[j]);\n                }\n                j++;\n            }\n        }\n        return this.continuation.test(lineText);\n    }\n}\n\nfunction contains(haystack: string[], needle: string) {\n    return haystack.find((s) => s === needle);\n}\n\nfunction indexFromLine(lineText: string): number {\n    const match = lineText.match(/%%--TC--(.*)--(\\d+)--%%/);\n    if (match) {\n        return Number(match[2]);\n    }\n    return undefined;\n}\n\nexport const _regex = {\n    tryCompleteRegex,\n    tryIncompleteRegex,\n    tryUndoRegex,\n    tryRemoveTextRegex,\n    trySkipSectionRegex,\n};\n\nfunction trySkipSectionRegex(param: string): RegExp {\n    return param ? new RegExp(param) : null;\n}\n\nfunction tryCompleteRegex(param: string): RegExp {\n    return new RegExp(`^([\\\\s>]*- \\\\[)[${param}](\\\\] .*)$`);\n}\n\nfunction tryIncompleteRegex(param: string): RegExp {\n    return new RegExp(`^([\\\\s>]*- \\\\[)[${param}](\\\\] .*)$`);\n}\n\nfunction tryRemoveTextRegex(param: string): RegExp {\n    return param ? new RegExp(param) : null;\n}\n\nfunction tryUndoRegex(appendDateFormat: string): RegExp {\n    const array = appendDateFormat.match(ALL_FORMATTING_TOKENS);\n    for (let i = 0, length = array.length; i < length; i++) {\n        const segment = array[i];\n        if (DATE_FORMATTING_TOKENS.test(segment)) {\n            array[i] = segment\n                .replace(/YYYY/g, \"\\\\d{4}\") // 4-digit year\n                .replace(/YY/g, \"\\\\d{2}\") // 2-digit year\n                .replace(/DD/g, \"\\\\d{2}\") // day of month, padded\n                .replace(/D/g, \"\\\\d{1,2}\") // day of month, not padded\n                .replace(/MMM/g, \"[A-Za-z]{3}\") // month, abbrv\n                .replace(/MM/g, \"\\\\d{2}\") // month, padded\n                .replace(/M/g, \"\\\\d{1,2}\") // month, not padded\n                .replace(/HH/g, \"\\\\d{2}\") // 24-hour, padded\n                .replace(/H/g, \"\\\\d{1,2}\") // 24-hour, not padded\n                .replace(/hh/g, \"\\\\d{2}\") // 12-hour, padded\n                .replace(/h/g, \"\\\\d{1,2}\") // 12-hour, not padded\n                .replace(/mm/g, \"\\\\d{2}\") // minute, padded\n                .replace(/m/g, \"\\\\d{1,2}\"); // minute, not padded;\n        } else if (segment.match(/\\[[\\s\\S]/)) {\n            array[i] = replaceLiterals(segment.replace(/^\\[|\\]$/g, \"\"));\n        } else {\n            array[i] = replaceLiterals(segment);\n        }\n    }\n\n    // allow whitespace around the appended string\n    const matchString = `\\\\s*${array.join(\"\")}\\\\s*`;\n\n    // allow a block reference at the end of the line\n    return new RegExp(`${matchString}( \\\\^[A-Za-z0-9-]+)?$`);\n}\n\nfunction replaceLiterals(segment: string) {\n    return segment\n        .replace(/\\(/g, \"\\\\(\") // escape literal (\n        .replace(/\\)/g, \"\\\\)\") // escape literal )\n        .replace(/\\[/g, \"\\\\[\") // escape literal [\n        .replace(/\\]/g, \"\\\\]\"); // escape literal ]\n}\n"
  },
  {
    "path": "src/taskcollector-TaskMarkModal.ts",
    "content": "import { type App, Modal } from \"obsidian\";\nimport type { TaskCollector } from \"./taskcollector-TaskCollector\";\n\nexport function promptForMark(\n    app: App,\n    taskCollector: TaskCollector,\n): Promise<string> {\n    return new Promise((resolve) => {\n        const modal = new TaskMarkModal(app, taskCollector);\n\n        modal.onClose = () => {\n            resolve(modal.chosenMark);\n        };\n\n        modal.open();\n    });\n}\n\nexport class TaskMarkModal extends Modal {\n    taskCollector: TaskCollector;\n    chosenMark: string;\n    constructor(app: App, taskCollector: TaskCollector) {\n        super(app);\n        this.taskCollector = taskCollector;\n        this.containerEl.id = \"taskcollector-modal\";\n    }\n\n    onOpen(): void {\n        const selector = this.contentEl.createDiv(\n            \"taskcollector-selector markdown-preview-view\",\n        );\n\n        const completedList = selector.createEl(\"ul\");\n        completedList.addClass(\"contains-task-list\");\n        this.addTaskValues(\n            completedList,\n            this.taskCollector.cache.completedMarks,\n            true,\n        );\n\n        const list = selector.createEl(\"ul\");\n        list.addClass(\"contains-task-list\");\n        this.addTaskValues(\n            list,\n            this.taskCollector.cache.incompleteMarks,\n            false,\n        );\n\n        const footer = selector.createEl(\"nav\");\n        const esc = footer.createSpan();\n        esc.createEl(\"b\", { text: \"Esc\" });\n        esc.appendText(\" to dismiss\");\n        const bksp = footer.createSpan();\n        bksp.createEl(\"b\", { text: \"Bksp\" });\n        bksp.appendText(\" to remove \");\n        bksp.createEl(\"code\", { text: \"[]\" });\n\n        const keyListener = (event: KeyboardEvent) => {\n            switch (event.key) {\n                case \"ArrowLeft\":\n                case \"ArrowRight\":\n                case \"ArrowUp\":\n                case \"ArrowDown\":\n                case \"CapsLock\":\n                case \"Tab\":\n                    break;\n                default: {\n                    this.chosenMark = event.key;\n                    event.preventDefault();\n                    event.stopImmediatePropagation();\n                    this.close();\n                }\n            }\n        };\n        this.scope.register([], null, keyListener);\n        this.scope.register([\"Shift\"], null, keyListener);\n    }\n\n    addTaskValues(\n        list: HTMLUListElement,\n        choices: string,\n        _markComplete: boolean,\n    ): void {\n        for (const character of choices) {\n            const li = list.createEl(\"li\", {\n                cls: `task-list-item ${character === \" \" ? \"\" : \" is-checked\"}`,\n                attr: {\n                    \"data-task\": character,\n                },\n            });\n            li.addEventListener(\"click\", (_event) => {\n                this.chosenMark = character;\n                this.close();\n            });\n\n            const input = li.createEl(\"input\", {\n                cls: \"task-list-item-checkbox\",\n                attr: {\n                    id: `task-list-item-checkbox-${character}`,\n                    type: \"checkbox\",\n                    style: \"pointer-events: none;\",\n                },\n            });\n            if (character !== \" \") {\n                input.setAttribute(\"checked\", \"\");\n            }\n            li.createSpan({\n                text: character === \" \" ? \"␣\" : character,\n                attr: {\n                    style: \"pointer-events: none;\",\n                },\n            });\n        }\n    }\n\n    onClose(): void {\n        this.contentEl.empty();\n    }\n}\n"
  },
  {
    "path": "test/dataMigration.test.ts",
    "content": "import { App, type PluginManifest } from \"obsidian\";\nimport type { TaskCollectorSettings } from \"../src/@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS_0,\n    GROUP_COMPLETE,\n    GROUP_DEFAULT,\n    TEXT_ONLY_MARK,\n} from \"../src/taskcollector-Constants\";\nimport { Data } from \"../src/taskcollector-Data\";\nimport { TaskCollectorPlugin } from \"../src/taskcollector-Plugin\";\nimport { TaskCollector } from \"../src/taskcollector-TaskCollector\";\n\nconst MANIFEST: PluginManifest = {\n    id: \"obsidian-task-collector\",\n    name: \"Task Collector (TC)\",\n    author: \"\",\n    version: \"1.0.0\",\n    minAppVersion: \"\",\n    description: \"\",\n};\n\njest.mock(\"obsidian\", () => ({\n    App: jest.fn().mockImplementation(),\n    Plugin: jest.fn().mockImplementation(() => {\n        return {\n            manifest: MANIFEST,\n            saveData: () => Promise.resolve(),\n            // debug: (message: string, ...optionalParams: any[]) => {\n            //     console.debug(message, ...optionalParams); // tests\n            // }\n        };\n    }),\n    PluginSettingTab: jest.fn().mockImplementation(),\n    Modal: jest.fn().mockImplementation(),\n    moment: jest.requireActual(\"moment-obsidian\"),\n}));\n\nconst plugin = new TaskCollectorPlugin(new App(), MANIFEST);\nplugin.tc = new TaskCollector();\n\nconst DEFAULT_MIGRATION = {\n    groups: {\n        default: {\n            name: DEFAULT_NAME,\n            marks: \" \",\n            complete: false,\n            removeExpr: \"\",\n            appendDateFormat: \"\",\n            registerCommand: false,\n            useContextMenu: false,\n        },\n        complete: {\n            name: COMPLETE_NAME,\n            marks: \"-Xx\",\n            complete: true,\n            removeExpr: \"\",\n            appendDateFormat: \"\",\n            registerCommand: false,\n            useContextMenu: false,\n            collection: {\n                areaHeading: \"## Log\",\n                removeCheckbox: false,\n            },\n        },\n    },\n    markCycle: \"\",\n    markCycleRemoveTask: false,\n    collectionEnabled: true,\n    previewClickModal: false,\n    contextMenu: {\n        markTask: false,\n        resetTask: false,\n        resetAllTasks: false,\n        collectTasks: false,\n    },\n    debug: false,\n    convertEmptyLines: false,\n    hideNotifications: false,\n    skipSectionMatch: \"\",\n    version: {\n        major: 1,\n        minor: 0,\n        patch: 0,\n    },\n};\n\ntest(\"Migration: defaults\", async () => {\n    const settings = await Data.constructSettings(plugin, DEFAULT_SETTINGS_0);\n    expect(settings).toEqual(DEFAULT_MIGRATION);\n});\n\ntest(\"Migration: appendReplace all\", async () => {\n    const initial = Object.assign({}, DEFAULT_SETTINGS_0, {\n        appendDateFormat: \"[(completed on ]D MMM, YYYY[)]\",\n        removeExpression: \"#done\",\n        appendRemoveAllTasks: true,\n    });\n\n    const expected: TaskCollectorSettings = JSON.parse(\n        JSON.stringify(DEFAULT_MIGRATION),\n    );\n    expected.groups[COMPLETE_NAME].appendDateFormat =\n        \"[(completed on ]D MMM, YYYY[)]\";\n    expected.groups[DEFAULT_NAME].appendDateFormat =\n        \"[(completed on ]D MMM, YYYY[)]\";\n    expected.groups[COMPLETE_NAME].removeExpr = \"#done\";\n    expected.groups[DEFAULT_NAME].removeExpr = \"#done\";\n\n    const settings = await Data.constructSettings(plugin, initial);\n    expect(settings).toEqual(expected);\n});\n\ntest(\"Task Marker: User configuration\", async () => {\n    const initial = {\n        removeExpression: \"\",\n        appendDateFormat: \"\",\n        appendTextFormatMark: \"\",\n        appendTextFormatMarkRow2: \"\",\n        appendTextFormatCreation: \"\",\n        appendTextFormatAppend: \" YYYY-MM-DD\",\n        appendRemoveAllTasks: false,\n        incompleteTaskValues: \" /ib?>\",\n        incompleteTaskValuesRow2: \"I!\",\n        cycleTaskValues: \" x/>-\",\n        onlyLowercaseX: false,\n        supportCanceledTasks: true,\n        previewOnClick: false,\n        rightClickComplete: true,\n        rightClickMark: true,\n        rightClickCycle: true,\n        rightClickCreate: true,\n        rightClickAppend: true,\n        rightClickResetTask: false,\n        rightClickResetAll: false,\n        rightClickToggleAll: false,\n    };\n\n    const expected: TaskCollectorSettings = Object.assign(\n        {},\n        JSON.parse(JSON.stringify(DEFAULT_MIGRATION)),\n        {\n            markCycle: \" x/>-\",\n            markCycleRemoveTask: false,\n            previewClickModal: false,\n            collectionEnabled: false,\n            contextMenu: {\n                markTask: true,\n                collectTasks: false,\n                resetTask: false,\n                resetAllTasks: false,\n            },\n            groups: {\n                default: {\n                    ...GROUP_DEFAULT,\n                    marks: \" />?bi\",\n                },\n                complete: {\n                    ...GROUP_COMPLETE,\n                    marks: \"-Xx\",\n                    useContextMenu: true,\n                },\n                \"group-2\": {\n                    ...GROUP_DEFAULT,\n                    name: \"group-2\",\n                    marks: \"!I\",\n                },\n                text: {\n                    ...GROUP_DEFAULT,\n                    name: \"text\",\n                    marks: TEXT_ONLY_MARK,\n                    appendDateFormat: \" YYYY-MM-DD\",\n                    useContextMenu: true,\n                },\n            },\n        },\n    );\n\n    console.log(expected);\n\n    const settings = await Data.constructSettings(plugin, initial);\n    expect(settings).toEqual(expected);\n});\n"
  },
  {
    "path": "test/generatedRegExp.test.ts",
    "content": "import type { TaskCollectorSettings } from \"../src/@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS,\n    TEXT_ONLY_MARK,\n    TEXT_ONLY_NAME,\n} from \"../src/taskcollector-Constants\";\nimport { Data } from \"../src/taskcollector-Data\";\nimport { TaskCollector } from \"../src/taskcollector-TaskCollector\";\n\njest.mock(\"obsidian\", () => ({\n    App: jest.fn().mockImplementation(),\n    moment: jest.requireActual(\"moment-obsidian\"),\n}));\n\nlet tc = new TaskCollector();\nlet config: TaskCollectorSettings = JSON.parse(\n    JSON.stringify(DEFAULT_SETTINGS),\n);\n\nafterEach(() => {\n    tc = new TaskCollector();\n    config = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\n});\n\ntest(\"Match blockquotes / callouts\", () => {\n    tc.init(config);\n\n    expect(\"> - [x] \").toMatch(tc.anyTaskMark);\n    expect(\"> > - [x] \").toMatch(tc.anyTaskMark);\n    expect(\"> - [ ] \").toMatch(tc.anyTaskMark);\n    expect(\"> > - [ ] \").toMatch(tc.anyTaskMark);\n});\n\ntest(\"Match specified removal patterns\", () => {\n    config.groups[COMPLETE_NAME].removeExpr = \"#(task|todo)\";\n    tc.init(config);\n\n    expect(\"- [ ] something #todo\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something #task\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something #task #todo\").toMatch(\n        tc.cache.removeExpr[COMPLETE_NAME],\n    );\n    expect(\"- [ ] something else\").not.toMatch(\n        tc.cache.removeExpr[COMPLETE_NAME],\n    );\n\n    // Remove text when transitioning to 'x'\n    expect(tc.updateLineText(\"- [ ] something #todo\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n    expect(tc.updateLineText(\"- [>] something #todo\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n    // text not removed when transitioning to something else (default)\n    expect(tc.updateLineText(\"- [x] something #todo\", \" \")).toEqual(\n        \"- [ ] something #todo\",\n    );\n    expect(tc.updateLineText(\"- [>] something #todo\", \"m\")).toEqual(\n        \"- [m] something #todo\",\n    );\n});\n\ntest(\"Match multiple string removal pattern (more complicated regex)\", () => {\n    config.groups[COMPLETE_NAME].removeExpr = \"((#(next|waiting|someday)|\\\\\\{\\\\d{4}-\\\\d{2}-\\\\d{2}\\\\\\})\\\\s*)+\";\n    tc.init(config);\n\n    expect(\"- [ ] something #next\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something #waiting\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something #someday\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something {2025-10-25}\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(\"- [ ] something #next {2025-10-25}\").toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n\n    expect(tc.updateLineText(\"- [ ] something #next\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n    expect(tc.updateLineText(\"- [ ] something {2025-10-25}\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n    expect(tc.updateLineText(\"- [ ] something #next {2025-10-25}\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n});\n\ndescribe(\"Set an append date\", () => {\n    test(\"YYYY-MM-DD append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"YYYY-MM-DD\";\n        tc.init(config);\n\n        // make sure various strings match the undo expression\n        expect(\"- [x] something 2021-08-24\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] something   2021-08-24  \").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] something (2021-08-24)\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 2021-08-24 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo\",\n        );\n    });\n\n    test(\"(YYYY-MM-DD) append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"[(]YYYY-MM-DD[)]\";\n        tc.init(config);\n\n        expect(\"- [x] something (2021-08-24)\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"- [x] something 2021-08-24\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 2021-08-24 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo\",\n        );\n    });\n\n    test(\"(D MMM, YYYY) append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"[(]D MMM, YYYY[)]\";\n        tc.init(config);\n\n        expect(\"- [x] something (6 Oct, 2021)\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"- [x] something 6 Oct, 2021\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 6 Oct, 2021, something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 2021-10-06 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo\",\n        );\n    });\n\n    test(\"DD MMM, YYYY append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"DD MMM, YYYY\";\n        tc.init(config);\n\n        expect(\"- [x] something 06 Oct, 2021\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"- [x] something (6 Oct, 2021)\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] something 6 Oct, 2021\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 6 Oct, 2021, something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 2021-10-06 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo\",\n        );\n    });\n\n    test(\"[(completed on ]D MMM, YYYY[)] append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat =\n            \"[(completed on ]D MMM, YYYY[)]\";\n        tc.init(config);\n\n        expect(\"+ [x] something (completed on 6 Oct, 2021)\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"+ [x] something (6 Oct, 2021)\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"+ [x] something 6 Oct, 2021\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"+ [x] 6 Oct, 2021, something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"+ [x] 2021-10-06 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"+ [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"+ [ ] something #todo\",\n        );\n    });\n\n    test(\"[✅ ]YYYY-MM-DDTHH:mm append string\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"[✅ ]YYYY-MM-DDTHH:mm\";\n        tc.init(config);\n\n        expect(\"* [x] something ✅ 2021-10-07T13:55\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"* [x] something (6 Oct, 2021)\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"* [x] something 6 Oct, 2021\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"* [x] 6 Oct, 2021, something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"* [x] 2021-10-06 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"* [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"* [ ] something #todo\",\n        );\n    });\n\n    test(\"Dataview annotated string [completion::2021-08-15]\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat =\n            \"[[completion::]YYYY-MM-DD[]]\";\n        tc.init(config);\n\n        expect(\"- [x] I finished this on [completion::2021-08-15]\").toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        expect(\"- [x] something (6 Oct, 2021)\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] something 6 Oct, 2021\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 6 Oct, 2021, something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n        expect(\"- [x] 2021-10-06 something else\").not.toMatch(\n            tc.cache.undoExpr[COMPLETE_NAME],\n        );\n\n        const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo\",\n        );\n    });\n\n    test(\"Correctly insert annotation ahead of block reference\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat =\n            \"[[completion::]YYYY-MM-DD[]]\";\n        tc.init(config);\n\n        expect(\"- [ ] something (6 Oct, 2021) ^your-ID-1\").toMatch(tc.blockRef);\n        expect(\n            \"- [x] I finished this on [completion::2021-08-15] ^your-ID-1\",\n        ).toMatch(tc.blockRef);\n        expect(\n            \"- [x] I finished this on [completion::2021-08-15] ^your-ID-1\",\n        ).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n\n        const completed = tc.updateLineText(\n            \"- [ ] something #todo ^your-ID-1\",\n            \"x\",\n        );\n        expect(completed).toMatch(\n            /- \\[x\\] something #todo \\[completion::\\d+-\\d+-\\d+\\] \\^your-ID-1/,\n        );\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(tc.updateLineText(completed, \" \")).toEqual(\n            \"- [ ] something #todo ^your-ID-1\",\n        );\n    });\n\n    test(\"Preserve continuation with strict line-break\", () => {\n        config.groups[COMPLETE_NAME].appendDateFormat = \"[(]YYYY-MM-DD[)]\";\n        tc.init(config);\n\n        const completed = tc.updateLineText(\"- [ ] something  \", \"x\");\n        expect(completed).toMatch(/- \\[x\\] something  \\(\\d+-\\d+-\\d+\\)  /);\n    });\n\n    test(\"Preserve continuation with strict line-break across reset\", () => {\n        config.groups[DEFAULT_NAME].marks += \">\";\n        config.groups[DEFAULT_NAME].appendDateFormat = \"[(]YYYY-MM-DD[)]\";\n        config.groups[COMPLETE_NAME].appendDateFormat = \"[(]YYYY-MM-DD[)]\";\n        tc.init(config);\n\n        const completed = tc.updateLineText(\"- [ ] something  \", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(completed).toMatch(/^- \\[x\\] something\\s+\\(\\d+-\\d+-\\d+\\)  $/);\n\n        const forwarded = tc.updateLineText(completed, \">\");\n        expect(forwarded).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(forwarded).toMatch(/^- \\[>\\] something\\s+\\(\\d+-\\d+-\\d+\\)  $/);\n\n        expect(tc.updateLineText(forwarded, \"x\")).toMatch(\n            /^- \\[x\\] something\\s+\\(\\d+-\\d+-\\d+\\)  $/,\n        );\n    });\n\n    test(\"Deal with lots of square brackets\", () => {\n        config.groups[DEFAULT_NAME].marks += \">\";\n        config.groups[COMPLETE_NAME].appendDateFormat =\n            \"[[completion::]YYYY-MM-DD[] ✅ ]YYYY-MM-DD[T]HH:mm\";\n        tc.init(config);\n\n        const completed = tc.updateLineText(\"- [ ] something\", \"x\");\n        expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n        expect(completed).toMatch(\n            /^- \\[x\\] something \\[completion::\\d+-\\d+-\\d+\\] ✅ \\d+-\\d+-\\d+T\\d+:\\d+$/,\n        );\n        expect(tc.updateLineText(completed, \" \")).toEqual(\"- [ ] something\");\n    });\n\n    test(\"Mark plain text\", () => {\n        Data.createSettingsGroup(config.groups, TEXT_ONLY_NAME, {\n            marks: TEXT_ONLY_MARK,\n            removeExpr: \"#(task|todo)\",\n            appendDateFormat: \"[(]D MMM, YYYY[)]\",\n        });\n        tc.init(config);\n\n        const marked = tc.updateLineText(\"something  \", \"\");\n        expect(marked).toMatch(tc.cache.undoExpr[TEXT_ONLY_NAME]);\n    });\n});\n\ntest(\"Apply text stripping/reset rules between task groups\", () => {\n    config.groups[DEFAULT_NAME].marks += \">\";\n    config.groups[DEFAULT_NAME].appendDateFormat = \"[(]D MMM, YYYY[)]\";\n    config.groups[COMPLETE_NAME].appendDateFormat = \"[(]YYYY-MM-DD[)]\";\n    config.groups[DEFAULT_NAME].removeExpr = \"#done\";\n    config.groups[COMPLETE_NAME].removeExpr = \"#todo\";\n    tc.init(config);\n\n    const completed = tc.updateLineText(\"- [ ] something #todo\", \"x\");\n    expect(completed).not.toMatch(tc.cache.removeExpr[COMPLETE_NAME]);\n    expect(completed).toMatch(tc.cache.undoExpr[COMPLETE_NAME]);\n    expect(completed).toMatch(/^- \\[x\\] something \\(\\d+-\\d+-\\d+\\)$/);\n\n    const changed = tc.updateLineText(\n        \"- [x] something #done  (2022-04-26)\",\n        \">\",\n    );\n    expect(changed).not.toMatch(tc.cache.removeExpr[DEFAULT_NAME]);\n    expect(changed).toMatch(tc.cache.undoExpr[DEFAULT_NAME]);\n    expect(changed).toMatch(/^- \\[>\\] something\\s+\\(\\d+ \\S+, \\d+\\)$/);\n\n    expect(tc.updateLineText(changed, \"x\")).toEqual(completed);\n    expect(tc.updateLineText(changed, \"-\")).toMatch(\n        /^- \\[-\\] something \\(\\d+ \\S+, \\d+\\)$/,\n    );\n});\n"
  },
  {
    "path": "test/markTasks.test.ts",
    "content": "import type { TaskCollectorSettings } from \"../src/@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_COLLECTION,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS,\n} from \"../src/taskcollector-Constants\";\nimport { Direction, TaskCollector } from \"../src/taskcollector-TaskCollector\";\n\njest.mock(\"obsidian\", () => ({\n    App: jest.fn().mockImplementation(),\n    moment: jest.requireActual(\"moment-obsidian\"),\n}));\n\nlet tc = new TaskCollector();\nlet config: TaskCollectorSettings = JSON.parse(\n    JSON.stringify(DEFAULT_SETTINGS),\n);\n\nafterEach(() => {\n    tc = new TaskCollector();\n    config = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\n});\n\ntest(\"Test default settings\", () => {\n    tc.init(config);\n\n    expect(tc.cache.removeExpr[COMPLETE_NAME]).toBeUndefined();\n    expect(tc.cache.undoExpr[COMPLETE_NAME]).toBeUndefined();\n\n    expect(tc.updateLineText(\"- [ ] something\", \"x\")).toEqual(\n        \"- [x] something\",\n    );\n    expect(tc.updateLineText(\"- [x] something\", \"-\")).toEqual(\n        \"- [-] something\",\n    );\n    expect(tc.updateLineText(\"- [-] something\", \">\")).toEqual(\n        \"- [>] something\",\n    );\n    expect(tc.updateLineText(\"- [>] something\", \" \")).toEqual(\n        \"- [ ] something\",\n    );\n});\n\ntest(\"Test asterisk task list\", () => {\n    tc.init(config);\n\n    expect(tc.cache.removeExpr[COMPLETE_NAME]).toBeUndefined();\n    expect(tc.cache.undoExpr[COMPLETE_NAME]).toBeUndefined();\n\n    expect(tc.updateLineText(\"* [ ] something\", \"x\")).toEqual(\n        \"* [x] something\",\n    );\n    expect(tc.updateLineText(\"* [x] something\", \"-\")).toEqual(\n        \"* [-] something\",\n    );\n    expect(tc.updateLineText(\"* [-] something\", \">\")).toEqual(\n        \"* [>] something\",\n    );\n    expect(tc.updateLineText(\"* [>] something\", \" \")).toEqual(\n        \"* [ ] something\",\n    );\n});\n\ntest(\"Test plus task list\", () => {\n    tc.init(config);\n\n    expect(tc.cache.removeExpr[COMPLETE_NAME]).toBeUndefined();\n    expect(tc.cache.undoExpr[COMPLETE_NAME]).toBeUndefined();\n\n    expect(tc.updateLineText(\"+ [ ] something\", \"x\")).toEqual(\n        \"+ [x] something\",\n    );\n    expect(tc.updateLineText(\"+ [x] something\", \"-\")).toEqual(\n        \"+ [-] something\",\n    );\n    expect(tc.updateLineText(\"+ [-] something\", \">\")).toEqual(\n        \"+ [>] something\",\n    );\n    expect(tc.updateLineText(\"+ [>] something\", \" \")).toEqual(\n        \"+ [ ] something\",\n    );\n});\n\ntest(\"Test numbered tasks\", () => {\n    tc.init(config);\n\n    expect(tc.cache.removeExpr[COMPLETE_NAME]).toBeUndefined();\n    expect(tc.cache.undoExpr[COMPLETE_NAME]).toBeUndefined();\n\n    expect(tc.updateLineText(\"1. [ ] something\", \"x\")).toEqual(\n        \"1. [x] something\",\n    );\n    expect(tc.updateLineText(\"11. [x] something\", \"-\")).toEqual(\n        \"11. [-] something\",\n    );\n    expect(tc.updateLineText(\"111. [-] something\", \">\")).toEqual(\n        \"111. [>] something\",\n    );\n    expect(tc.updateLineText(\"1111. [>] something\", \" \")).toEqual(\n        \"1111. [ ] something\",\n    );\n});\n\ntest(\"Correctly mark items in a selection\", () => {\n    tc.init(config);\n\n    const start = \"- [ ] one\\n- [>] two\\n- [-] three\\n- [x] four\";\n    expect(tc.markSelectedTask(start, \"x\", [0, 1, 2, 3])).toEqual(\n        \"- [x] one\\n- [x] two\\n- [x] three\\n- [x] four\",\n    );\n    expect(tc.markSelectedTask(start, \"-\", [0, 1, 2, 3])).toEqual(\n        \"- [-] one\\n- [-] two\\n- [-] three\\n- [-] four\",\n    );\n    expect(tc.markSelectedTask(start, \">\", [0, 1, 2, 3])).toEqual(\n        \"- [>] one\\n- [>] two\\n- [>] three\\n- [>] four\",\n    );\n    expect(tc.markSelectedTask(start, \" \", [0, 1, 2, 3])).toEqual(\n        \"- [ ] one\\n- [ ] two\\n- [ ] three\\n- [ ] four\",\n    );\n});\n\ntest(\"Remove checkbox from line with backspace from modal\", () => {\n    config.groups[COMPLETE_NAME].collection = Object.assign(\n        {},\n        DEFAULT_COLLECTION,\n    );\n    config.groups[COMPLETE_NAME].collection.removeCheckbox = true;\n    tc.init(config);\n\n    const completed = \"- [x] something [x]\";\n    const incomplete = \"- [ ] something [x]\";\n    const listItem = \"- something [x]\";\n    expect(tc.updateLineText(completed, \"Backspace\")).toEqual(listItem);\n    expect(tc.updateLineText(incomplete, \"Backspace\")).toEqual(listItem);\n});\n\ntest(\"Create and Mark a normal list item\", () => {\n    config.groups[DEFAULT_NAME].marks += \">\";\n    config.groups[COMPLETE_NAME].marks += \"-\";\n    config.groups[COMPLETE_NAME].removeExpr = \"#(task|todo)\";\n    tc.init(config);\n\n    const start = \"- one #task\";\n    expect(tc.markSelectedTask(start, \"x\", [0])).toEqual(\"- [x] one\");\n    expect(tc.markSelectedTask(start, \"-\", [0])).toEqual(\"- [-] one\");\n    expect(tc.markSelectedTask(start, \">\", [0])).toEqual(\"- [>] one #task\");\n    expect(tc.markSelectedTask(start, \" \", [0])).toEqual(\"- [ ] one #task\");\n});\n\ntest(\"Mark tasks within a callout\", () => {\n    tc.init(config);\n\n    expect(tc.updateLineText(\"> - [ ] something\", \"-\")).toEqual(\n        \"> - [-] something\",\n    );\n    expect(tc.updateLineText(\"> - [-] something\", \" \")).toEqual(\n        \"> - [ ] something\",\n    );\n    expect(tc.updateLineText(\"> 10. [-] something\", \" \")).toEqual(\n        \"> 10. [ ] something\",\n    );\n\n    expect(tc.updateLineText(\"> > - [x] something\", \"x\")).toEqual(\n        \"> > - [x] something\",\n    );\n    expect(tc.updateLineText(\"> > - [x] something\", \" \")).toEqual(\n        \"> > - [ ] something\",\n    );\n    expect(tc.updateLineText(\"> > 1. [x] something\", \" \")).toEqual(\n        \"> > 1. [ ] something\",\n    );\n});\n\ndescribe(\"Mark lines that are not tasks\", () => {\n    test(\"Mark non-task/list lines when convert non-list lines is true\", () => {\n        config.convertEmptyLines = true;\n        tc.init(config);\n        expect(tc.updateLineText(\"something\", \"x\")).toEqual(\"- [x] something\");\n    });\n\n    test(\"Use indent for non-task/list lines when convertEmptyLines is true\", () => {\n        config.convertEmptyLines = true;\n        tc.init(config);\n        expect(tc.updateLineText(\"\\tsomething\", \"x\")).toEqual(\n            \"\\t- [x] something\",\n        );\n    });\n\n    test(\"Do not mark non-task/list lines when convert non-list lines is false\", () => {\n        tc.init(config);\n        expect(tc.updateLineText(\"something\", \"-\")).toEqual(\"something\");\n    });\n\n    test(\"Mark empty lines when convertEmptyLines is true\", () => {\n        config.convertEmptyLines = true;\n        tc.init(config);\n        expect(tc.updateLineText(\"\", \"x\")).toEqual(\"- [x]\");\n    });\n\n    test(\"Accomodate callouts for non-task/list lines when convertEmptyLines is true\", () => {\n        config.convertEmptyLines = true;\n        tc.init(config);\n        expect(tc.updateLineText(\"> something\", \"x\")).toEqual(\n            \"> - [x] something\",\n        );\n    });\n});\n\ndescribe(\"Task mark cycle\", () => {\n    test(\"Mark tasks forward in a cycle (next)\", () => {\n        config.markCycle = \"abc\";\n        tc.init(config);\n        expect(tc.markInCycle(\"- [ ] something\", Direction.NEXT, [0])).toEqual(\n            \"- [a] something\",\n        );\n        expect(tc.markInCycle(\"- [a] something\", Direction.NEXT, [0])).toEqual(\n            \"- [b] something\",\n        );\n        expect(tc.markInCycle(\"- [b] something\", Direction.NEXT, [0])).toEqual(\n            \"- [c] something\",\n        );\n        expect(tc.markInCycle(\"- [c] something\", Direction.NEXT, [0])).toEqual(\n            \"- [a] something\",\n        );\n    });\n\n    test(\"Mark tasks backward in a cycle (prev)\", () => {\n        config.markCycle = \"abc\";\n        tc.init(config);\n        expect(tc.markInCycle(\"- [ ] something\", Direction.PREV, [0])).toEqual(\n            \"- [c] something\",\n        );\n        expect(tc.markInCycle(\"- [a] something\", Direction.PREV, [0])).toEqual(\n            \"- [c] something\",\n        );\n        expect(tc.markInCycle(\"- [b] something\", Direction.PREV, [0])).toEqual(\n            \"- [a] something\",\n        );\n        expect(tc.markInCycle(\"- [c] something\", Direction.PREV, [0])).toEqual(\n            \"- [b] something\",\n        );\n    });\n\n    test(\"Mark lines as tasks in a cycle\", () => {\n        config.markCycle = \"abc\";\n        tc.init(config);\n        expect(tc.markInCycle(\"- something\", Direction.NEXT, [0])).toEqual(\n            \"- [a] something\",\n        );\n        expect(tc.markInCycle(\"- something\", Direction.PREV, [0])).toEqual(\n            \"- [c] something\",\n        );\n    });\n\n    test(\"Mark tasks forward in a cycle (next) include remove task\", () => {\n        config.markCycle = \" ab§\";\n        config.markCycleRemoveTask = true;\n        tc.init(config);\n        expect(tc.markInCycle(\"- something\", Direction.NEXT, [0])).toEqual(\n            \"- [ ] something\",\n        );\n        expect(tc.markInCycle(\"- [ ] something\", Direction.NEXT, [0])).toEqual(\n            \"- [a] something\",\n        );\n        expect(tc.markInCycle(\"- [a] something\", Direction.NEXT, [0])).toEqual(\n            \"- [b] something\",\n        );\n        expect(tc.markInCycle(\"- [b] something\", Direction.NEXT, [0])).toEqual(\n            \"- something\",\n        );\n    });\n\n    test(\"Mark tasks backward in a cycle (prev) include remove task\", () => {\n        config.markCycle = \" ab§\";\n        config.markCycleRemoveTask = true;\n        tc.init(config);\n        expect(tc.markInCycle(\"- [b] something\", Direction.PREV, [0])).toEqual(\n            \"- [a] something\",\n        );\n        expect(tc.markInCycle(\"- [a] something\", Direction.PREV, [0])).toEqual(\n            \"- [ ] something\",\n        );\n        expect(tc.markInCycle(\"- [ ] something\", Direction.PREV, [0])).toEqual(\n            \"- something\",\n        );\n        expect(tc.markInCycle(\"- something\", Direction.PREV, [0])).toEqual(\n            \"- [b] something\",\n        );\n    });\n});\n"
  },
  {
    "path": "test/mocks/moment-obsidian.d.ts",
    "content": "declare module \"moment-obsidian\" {\n    import * as moment from \"moment\";\n    export = moment;\n}\n"
  },
  {
    "path": "test/mocks/obsidian.ts",
    "content": "import { Workspace, Vault, MetadataCache, FileManager, UserEvent } from \"obsidian\";\nimport moment from \"moment-obsidian\";\nexport { moment };\n\nexport const activeWindow = window;\n\nexport class App {\n\n    /** @public */\n    workspace: Workspace;\n\n    /** @public */\n    vault: Vault;\n    /** @public */\n    metadataCache: MetadataCache;\n\n    /** @public */\n    fileManager: FileManager;\n\n    /**\n     * The last known user interaction event, to help commands find out what modifier keys are pressed.\n     * @public\n     */\n    lastEvent: UserEvent | null;\n\n}\n"
  },
  {
    "path": "test/moveCompletedItem.test.ts",
    "content": "import type { TaskCollectorSettings } from \"../src/@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_COLLECTION,\n    DEFAULT_NAME,\n    DEFAULT_SETTINGS,\n    GROUP_COMPLETE,\n} from \"../src/taskcollector-Constants\";\nimport { TaskCollector } from \"../src/taskcollector-TaskCollector\";\n\njest.mock(\"obsidian\", () => ({\n    App: jest.fn().mockImplementation(),\n    moment: jest.requireActual(\"moment-obsidian\"),\n}));\n\nlet tc = new TaskCollector();\nconst begin = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\nbegin.collectionEnabled = true;\nbegin.groups[COMPLETE_NAME].collection = JSON.parse(\n    JSON.stringify(DEFAULT_COLLECTION),\n);\n\nlet config: TaskCollectorSettings = JSON.parse(JSON.stringify(begin));\n\nafterEach(() => {\n    tc = new TaskCollector();\n    config = JSON.parse(JSON.stringify(begin)); // reset\n});\n\ntest(\"Test move with collection disabled (no change)\", () => {\n    config.collectionEnabled = false;\n    tc.init(config);\n\n    const text = \"- [x] Complete\";\n\n    const result = tc.moveAllTasks(text);\n    expect(result).toEqual(\"- [x] Complete\");\n});\n\ndescribe(\"Test move with collection enabled\", () => {\n    test(\"No completed items -> Log section created\", () => {\n        tc.init(config);\n\n        const text = \"- [ ] Incomplete\";\n\n        const result = tc.moveAllTasks(text);\n        expect(result).toEqual(\"- [ ] Incomplete\\n\\n## Log\\n\");\n    });\n\n    test(\"No completed items -> Log section created (preserve continuation)\", () => {\n        tc.init(config);\n\n        const text = \"a  \\n    text continuation\";\n\n        const result = tc.moveAllTasks(text);\n        expect(result).toEqual(\"a  \\n    text continuation\\n\\n## Log\\n\");\n    });\n\n    test(\"Move completed tasks\", () => {\n        config.groups[COMPLETE_NAME].marks += \"-\";\n        tc.init(config);\n\n        const start =\n            \"\" +\n            \"- [ ] one\\n\" +\n            \"- [>] two\\n\" +\n            \"- [-] three\\n\" +\n            \"- [x] four\\n\";\n\n        const result =\n            \"\" +\n            \"- [ ] one\\n\" +\n            \"- [>] two\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [-] three\\n\" +\n            \"- [x] four\\n\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move completed tasks with text continuation\", () => {\n        tc.init(config);\n\n        const start =\n            \"\\n\" +\n            \"- [ ] An incomplete item\\n\" +\n            \"- [x] a  \\n\" +\n            \"    text continuation  \\n\" +\n            \"    \\n\" +\n            \"    Including a longer paragraph in the same bullet\\n\" +\n            \"- [ ] An incomplete item\\n\";\n\n        const result =\n            \"\\n\" +\n            \"- [ ] An incomplete item\\n\" +\n            \"- [ ] An incomplete item\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [x] a  \\n\" +\n            \"    text continuation  \\n\" +\n            \"    \\n\" +\n            \"    Including a longer paragraph in the same bullet\\n\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move completed tasks within a callout\", () => {\n        tc.init(config);\n\n        const start =\n            \"\" +\n            \"> - [ ] Another item\\n\" +\n            \"> - [x] This line ends with two spaces  \\n\" +\n            \">    which allows text to wrap using strict markdown line wrapping syntax. This line should move, too.  \\n\" +\n            \">\\n\" +\n            \">    This is also how you support list items with multiple paragraphs (leading whitespace indent), and it should travel with the previous list item.\\n\" +\n            \">\\n\" +\n            \"> - [ ] Another item\";\n\n        const result =\n            \"\" +\n            \"> - [ ] Another item\\n\" +\n            \"> - [ ] Another item\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"> - [x] This line ends with two spaces  \\n\" +\n            \">    which allows text to wrap using strict markdown line wrapping syntax. This line should move, too.  \\n\" +\n            \">\\n\" +\n            \">    This is also how you support list items with multiple paragraphs (leading whitespace indent), and it should travel with the previous list item.\\n\" +\n            \">\\n\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move completed tasks with associated callout\", () => {\n        tc.init(config);\n\n        const start =\n            \"\" +\n            \"- [ ] An incomplete item\\n\" +\n            \"- [x] The nested quote should move with the item\\n\" +\n            \"    > [!note]\\n\" +\n            \"    > Nested blockquotes associated with it would also be moved.\\n\" +\n            \"- [ ] A subsequent item should not be moved\\n\";\n\n        const result =\n            \"\" +\n            \"- [ ] An incomplete item\\n\" +\n            \"- [ ] A subsequent item should not be moved\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [x] The nested quote should move with the item\\n\" +\n            \"    > [!note]\\n\" +\n            \"    > Nested blockquotes associated with it would also be moved.\\n\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n});\n\ntest(\"Test move lists with mixed completion\", () => {\n    tc.init(config);\n\n    const start =\n        \"\" +\n        \"- [x] This line ends with two spaces  \\n\" +\n        \"    which allows text to wrap using strict markdown line wrapping syntax. This line should move, too.  \\n\" +\n        \"\\n\" +\n        \"    This is also how you support list items with multiple paragraphs (leading whitespace indent), and it should travel with the previous list item.\\n\" +\n        \"- [x] If this item is completed,\\n\" +\n        \"    any wrapped text like this should also be moved, as it is indented\\n\" +\n        \"    - [x] If there are nested bullets, it should all stay together. \\n\" +\n        \"    - [ ] This is where things get messy. If this task remained incomplete, it would stay behind\\n\" +\n        \"- [x] If this task is completed,\\n\" +\n        \"    > Nested blockquotes associated with it would also be moved.\\n\";\n\n    const result =\n        \"\" +\n        \"    - [ ] This is where things get messy. If this task remained incomplete, it would stay behind\\n\" +\n        \"\\n\" +\n        \"## Log\\n\" +\n        \"- [x] This line ends with two spaces  \\n\" +\n        \"    which allows text to wrap using strict markdown line wrapping syntax. This line should move, too.  \\n\" +\n        \"\\n\" +\n        \"    This is also how you support list items with multiple paragraphs (leading whitespace indent), and it should travel with the previous list item.\\n\" +\n        \"- [x] If this item is completed,\\n\" +\n        \"    any wrapped text like this should also be moved, as it is indented\\n\" +\n        \"    - [x] If there are nested bullets, it should all stay together. \\n\" +\n        \"- [x] If this task is completed,\\n\" +\n        \"    > Nested blockquotes associated with it would also be moved.\\n\";\n\n    expect(tc.moveAllTasks(start)).toEqual(result);\n});\n\ndescribe(\"Test move with multiple sections\", () => {\n    beforeEach(() => {\n        config.groups[\"deferred\"] = {\n            ...JSON.parse(JSON.stringify(GROUP_COMPLETE)),\n            marks: \">\",\n            collection: {\n                areaHeading: \"## Deferred\",\n                removeCheckbox: false,\n            },\n        };\n    });\n\n    test(\"No completed or deferred items -> Log & Deferred sections created\", () => {\n        tc.init(config);\n\n        const text = \"- [ ] Incomplete\";\n\n        const result = tc.moveAllTasks(text);\n        expect(result).toEqual(\"- [ ] Incomplete\\n\\n## Deferred\\n\\n## Log\\n\");\n    });\n\n    test(\"Move completed tasks between sections\", () => {\n        config.groups[DEFAULT_NAME].collection = {\n            areaHeading: \"## To Do\",\n            removeCheckbox: false,\n        };\n        tc.init(config);\n\n        const start =\n            \"- [ ] i1\\n\" +\n            \"- [x] one\\n\" +\n            \"- [>] two\\n\" +\n            \"\\n\" +\n            \"## To Do\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [ ] i2\\n\" +\n            \"- [x] three\\n\" +\n            \"- [>] four\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [ ] i3\\n\" +\n            \"- [x] five\\n\" +\n            \"- [>] six\\n\" +\n            \"\";\n\n        const result =\n            \"\\n\" +\n            \"## To Do\\n\" +\n            \"- [ ] i1\\n\" +\n            \"- [ ] i2\\n\" +\n            \"- [ ] i3\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [x] one\\n\" +\n            \"- [x] five\\n\" +\n            \"- [x] three\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [>] two\\n\" +\n            \"- [>] four\\n\" +\n            \"- [>] six\\n\" +\n            \"\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move duplicate section\", () => {\n        tc.init(config);\n\n        const start =\n            \"- [ ] i1\\n\" +\n            \"- [x] one\\n\" +\n            \"- [>] two\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [x] three\\n\" +\n            \"- [>] four\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [x] five\\n\" +\n            \"- [>] six\";\n\n        const result =\n            \"- [ ] i1\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [>] two\\n\" +\n            \"\\n\" +\n            \"## Log\\n\" +\n            \"- [x] one\\n\" +\n            \"- [x] five\\n\" +\n            \"- [x] three\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [>] four\\n\" +\n            \"- [>] six\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move unmarked items with no loss\", () => {\n        // Ensure that unmarked items are not lost when moving. Issue #262\n        config.groups[DEFAULT_NAME].collection = {\n            areaHeading: \"## 1 - ToDos\",\n            removeCheckbox: false,\n        };\n        config.groups[COMPLETE_NAME].collection = {\n            areaHeading: \"## 9 - Done √\",\n            removeCheckbox: false,\n        };\n        config.groups[\"deferred\"].collection = {\n            areaHeading: \"## 2 - Later >\",\n            removeCheckbox: false,\n        };\n        tc.init(config);\n\n        const start =\n            \"## 1 - ToDos\\n\" +\n            \"- [ ] 1\\n\" +\n            \"- [ ] 2\\n\" +\n            \"- [ ] 3\\n\" +\n            \"## 2 - Later >\\n\" +\n            \"\\n\" +\n            \"## 9 - Done √\\n\" +\n            \"- [ ] aa\\n\" +\n            \"- [ ] bb\\n\" +\n            \"- [ ] cc\";\n\n        const result =\n            \"## 1 - ToDos\\n\" +\n            \"- [ ] aa\\n\" +\n            \"- [ ] bb\\n\" +\n            \"- [ ] cc\\n\" +\n            \"- [ ] 1\\n\" +\n            \"- [ ] 2\\n\" +\n            \"- [ ] 3\\n\" +\n            \"## 2 - Later >\\n\" +\n            \"\\n\" +\n            \"## 9 - Done √\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n\n    test(\"Move completed tasks around skipped section\", () => {\n        config.groups[COMPLETE_NAME].marks += \"-\";\n        config.skipSectionMatch = \"# ❧ \";\n        tc.init(config);\n\n        const start =\n            \"\" +\n            \"- [ ] one\\n\" +\n            \"- [>] two\\n\" +\n            \"## ❧ Ignore me\\n\" +\n            \"- [-] three\\n\" +\n            \"## ❧ Ignore me twice\\n\" +\n            \"- [x] four\\n\";\n\n        const result =\n            \"\" +\n            \"- [ ] one\\n\" +\n            \"## ❧ Ignore me\\n\" +\n            \"- [-] three\\n\" +\n            \"## ❧ Ignore me twice\\n\" +\n            \"- [x] four\\n\" +\n            \"\\n\" +\n            \"## Deferred\\n\" +\n            \"- [>] two\\n\" +\n            \"\\n\" +\n            \"## Log\\n\";\n\n        expect(tc.moveAllTasks(start)).toEqual(result);\n    });\n});\n"
  },
  {
    "path": "test/resetAllTasks.test.ts",
    "content": "import type { TaskCollectorSettings } from \"../src/@types/settings\";\nimport {\n    COMPLETE_NAME,\n    DEFAULT_COLLECTION,\n    DEFAULT_SETTINGS,\n    GROUP_COMPLETE,\n} from \"../src/taskcollector-Constants\";\nimport { TaskCollector } from \"../src/taskcollector-TaskCollector\";\n\njest.mock(\"obsidian\", () => ({\n    App: jest.fn().mockImplementation(),\n    moment: jest.requireActual(\"moment-obsidian\"),\n}));\n\nlet tc = new TaskCollector();\nconst begin = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\nbegin.collectionEnabled = true;\nbegin.groups[COMPLETE_NAME].collection = JSON.parse(\n    JSON.stringify(DEFAULT_COLLECTION),\n);\n\nlet config: TaskCollectorSettings = JSON.parse(JSON.stringify(begin));\n\nconst start =\n    \"\" +\n    \"- [ ] i1\\n\" +\n    \"- [x] one\\n\" +\n    \"- [>] two\\n\" +\n    \"\\n\" +\n    \"## To Do\\n\" +\n    \"\\n\" +\n    \"## Log\\n\" +\n    \"- [ ] i2\\n\" +\n    \"- [x] three\\n\" +\n    \"- [>] four\\n\" +\n    \"\\n\" +\n    \"## Deferred\\n\" +\n    \"- [ ] i3\\n\" +\n    \"- [x] five\\n\" +\n    \"- [>] six\\n\" +\n    \"\";\n\nafterEach(() => {\n    tc = new TaskCollector();\n    config = JSON.parse(JSON.stringify(begin)); // reset\n});\n\ntest(\"Reset with collection disabled\", () => {\n    config.collectionEnabled = false;\n    tc.init(config);\n\n    // all items are reset\n    const expected =\n        \"\" +\n        \"- [ ] i1\\n\" +\n        \"- [ ] one\\n\" +\n        \"- [ ] two\\n\" +\n        \"\\n\" +\n        \"## To Do\\n\" +\n        \"\\n\" +\n        \"## Log\\n\" +\n        \"- [ ] i2\\n\" +\n        \"- [ ] three\\n\" +\n        \"- [ ] four\\n\" +\n        \"\\n\" +\n        \"## Deferred\\n\" +\n        \"- [ ] i3\\n\" +\n        \"- [ ] five\\n\" +\n        \"- [ ] six\\n\" +\n        \"\";\n\n    const result = tc.resetAllTasks(start);\n    expect(result).toEqual(expected);\n});\n\ntest(\"Reset with collection enabled\", () => {\n    config.groups[\"deferred\"] = {\n        ...JSON.parse(JSON.stringify(GROUP_COMPLETE)),\n        marks: \">\",\n        collection: {\n            areaHeading: \"## Deferred\",\n            removeCheckbox: false,\n        },\n    };\n    tc.init(config);\n\n    // items in \"completion\" areas are left alone\n    const expected =\n        \"\" +\n        \"- [ ] i1\\n\" +\n        \"- [ ] one\\n\" +\n        \"- [ ] two\\n\" +\n        \"\\n\" +\n        \"## To Do\\n\" +\n        \"\\n\" +\n        \"## Log\\n\" +\n        \"- [ ] i2\\n\" +\n        \"- [x] three\\n\" +\n        \"- [>] four\\n\" +\n        \"\\n\" +\n        \"## Deferred\\n\" +\n        \"- [ ] i3\\n\" +\n        \"- [x] five\\n\" +\n        \"- [>] six\\n\" +\n        \"\";\n\n    const result = tc.resetAllTasks(start);\n    expect(result).toEqual(expected);\n});\n\ntest(\"Reset with collection enabled and skipped section\", () => {\n    config.groups[\"deferred\"] = {\n        ...JSON.parse(JSON.stringify(GROUP_COMPLETE)),\n        marks: \">\",\n    };\n    config.skipSectionMatch = \"# ❧ \";\n    tc.init(config);\n\n    const startSkipped =\n        \"\" +\n        \"- [ ] i1\\n\" +\n        \"- [x] one\\n\" +\n        \"- [>] two\\n\" +\n        \"\\n\" +\n        \"## To Do\\n\" +\n        \"\\n\" +\n        \"## Log\\n\" +\n        \"- [ ] i2\\n\" +\n        \"- [x] three\\n\" +\n        \"- [>] four\\n\" +\n        \"\\n\" +\n        \"## ❧ Deferred\\n\" +\n        \"- [ ] i3\\n\" +\n        \"- [x] five\\n\" +\n        \"- [>] six\\n\" +\n        \"\";\n\n    // items in \"completion\" areas are left alone\n    const expected =\n        \"\" +\n        \"- [ ] i1\\n\" +\n        \"- [ ] one\\n\" +\n        \"- [ ] two\\n\" +\n        \"\\n\" +\n        \"## To Do\\n\" +\n        \"\\n\" +\n        \"## Log\\n\" +\n        \"- [ ] i2\\n\" +\n        \"- [x] three\\n\" +\n        \"- [>] four\\n\" +\n        \"\\n\" +\n        \"## ❧ Deferred\\n\" +\n        \"- [ ] i3\\n\" +\n        \"- [x] five\\n\" +\n        \"- [>] six\\n\" +\n        \"\";\n\n    const result = tc.resetAllTasks(startSkipped);\n    expect(result).toEqual(expected);\n});\n"
  },
  {
    "path": "test/setup.ts",
    "content": "// jsdom doesn't provide activeWindow; point it at the test window\n(globalThis as typeof globalThis & { activeWindow: Window }).activeWindow = window;\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"nodenext\",\n    \"allowJs\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": false,\n    \"strictPropertyInitialization\": false,\n    \"moduleResolution\": \"nodenext\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"importHelpers\": true,\n    \"lib\": [\n      \"dom\",\n      \"es5\",\n      \"scripthost\",\n      \"es2022\"\n    ],\n    \"types\": [\n      \"jest\"\n    ]\n  },\n  \"include\": [\n    \"**/*.ts\"\n  ]\n}\n"
  },
  {
    "path": "versions.json",
    "content": "{\n\t\"0.1.0\": \"0.13.4\",\n  \"1.2.0\": \"1.13.0\"\n}\n"
  }
]