Repository: obsidian-tasks-group/obsidian-tasks
Pull request #790 (2022-06-15 23:38:05)

feat: Add regular expression support to text filters
Supplied as an example. No test included. Will circle around to figuring out how to write a test in this repo if there's openness to a PR here.

elight: Apologies for lack of commit message. First time trying to use the GitHub in-browser VS Code. 😅
claremacrae: > Supplied as an example. No test included. Will circle around to figuring out how to write a test in this repo if there's openness to a PR here.

**Yes!!!!** I'm very much open to adding regex searching.

I really like the idea. I had been thinking of adding things like `starts with`, `ends with` or similar, but using a regex instead is a massively better idea.

Thank you very much for thinking of it, and for starting the PR. ❤️ 

## Tests

A good place to look for easy-to-add tests for a text field is [tests/Query/Filter/PathField.test.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/tests/Query/Filter/PathField.test.ts).

## What the user sees

I've been thinking about the `regexpPattern` regex...

If I understand the code correctly, users could write something like this, which would match both `wobble` and `wibble`:

`path includes /w.bble/`

And indeed with a little fixing of the code in the PR, I was just able to write this and the tests passed!

```typescript
    it('by path (includes regex)', () => {
        // Arrange
        const filter = new PathField().createFilterOrErrorMessage(
            'path includes /w.bble/',
        );

        // Assert
        testTaskFilterForTaskWithPath(filter, '', false);
        testTaskFilterForTaskWithPath(filter, '/some/path/wibble.md', true);
        testTaskFilterForTaskWithPath(filter, '/some/path/wobble.md', true);
        testTaskFilterForTaskWithPath(filter, '/other/path/file.md', false);
    });
```

A few thoughts on auto-detecting the presence of regexes inside `includes` and `does not include`

- It risks matching searches that were intended to be string searches - for example, to find tasks in a particular folder:
    - `path includes /some special folder 1.2/`
    - In simple cases, the regex search would give the same results as the string search, but not always.
    - If there were special regex characters, like `.`, the meaning would be different.
    - The `includes` search ignores case. The regex search requires case of all letters to match.
    - In other words, auto-detecting regexes is bound to change the results of some existing tests, sooner or later.
- There are some [flags to regexes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags), like adding a `i` to make search case-insensitive.
    - The risk with specifying a regex to match a regex is that we might miss out some case that some advanced user really wishes to use.
    - So we might fail to detect valid regexes, in which case the code would fall back to old-string sub-string match rather than regex, and users would silently get no results, or wrong results.

## Suggestions

I think it would be easier to document, and safer for users, if regex searches were explicit.

Off the top of my head, ideas include:

```text
# non-obvious that it uses regexes: matches could be interpreted as 'equals'
path matches /sadfasfdafa/i
path does not match /sadfasfdafa/i

# Maybe my preference...
path regex matches /sadfasfdafa/i
path regex does not match /sadfasfdafa/i

# Should the / not be needed? But if so, how would people specify flags?
path regex matches sadfasfdafa
path regex does not match sadfasfdafa
```

I've [asked in Discord](https://discord.com/channels/686053708261228577/965681451297304596/986889558740320266) to see if anyone else has any ideas...

How do you feel about doing the work to make the regex-type search explicit?

If changing the query to something other than the current `includes`/`does not include` was not of interest to you, I would be very happy to accept the PR with one or two basic tests, and then myself adjust it later on...

claremacrae: > How do you feel about doing the work to make the regex-type search explicit?
> 
> If changing the query to something other than the current `includes`/`does not include` was not of interest to you, I would be very happy to accept the PR with one or two basic tests, and then myself adjust it later on...

Perhaps better still, I would also be very happy to pair with you on the feature... With an hour or two of screen-sharing ands sharing knowledge, we would get a huge amount done, in my experience....

Just an option...
claremacrae: And for later consideration: should regex searches have the i flag enabled automatically, to make them case-insensitive?

- Pros: easier to convert an old-style sub-string search to regex one, and still get the same results
- Cons: would prevent people for searching for exact-case-matches
claremacrae: > And for later consideration: should regex searches have the i flag enabled automatically, to make them case-insensitive?
> 
> * Pros: easier to convert an old-style sub-string search to regex one, and still get the same results
> * Cons: would prevent people for searching for exact-case-matches

From years of working on search software in another field, I tend to favour erring on the side of giving people more matches rather than less, as they never know the invisible matches that they are missing...
claremacrae: > And indeed with a little fixing of the code in the PR, I was just able to write this and the tests passed!

```diff
diff --git a/src/Query/Filter/TextField.ts b/src/Query/Filter/TextField.ts
index 68cc93b..cd43879 100644
--- a/src/Query/Filter/TextField.ts
+++ b/src/Query/Filter/TextField.ts
@@ -8,7 +8,7 @@ import { FilterOrErrorMessage } from './Filter';
  * value, such as the description or file path.
  */
 export abstract class TextField extends Field {
-    const regexpPattern = /^\/(.*)\/$/;
+    private static readonly regexpPattern = /^\/(.*)\/$/;
 
     public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
         const result = new FilterOrErrorMessage();
@@ -17,13 +17,13 @@ export abstract class TextField extends Field {
             const filterMethod = match[1];
             if (filterMethod === 'includes') {
                 const queryString = match[2];
-                const regexpMatch = queryString.match(regexpPattern);
+                const regexpMatch = queryString.match(TextField.regexpPattern);
                 if (regexpMatch !== null) {
                     const pattern = regexpMatch[1];
                     const regexp = new RegExp(pattern);
-                    result.filter = (task: Task) => 
-                        this.value(task).match(regexp);
-                } else {   
+                    result.filter = (task: Task) =>
+                        this.value(task).match(regexp) !== null;
+                } else {
                     result.filter = (task: Task) =>
                         TextField.stringIncludesCaseInsensitive(
                             this.value(task),
```
elight: Wonderful!!!! Everyone wins!

On June 16, 2022, Vipercat ***@***.***> wrote:
> And indeed with a little fixing of the code in the PR, I was just able
> to write this and the tests passed!
>

—
Reply to this email directly, view it on GitHub
<https://github.com/obsidian-tasks-group/obsidian-
tasks/pull/790#issuecomment-1157341552>, or unsubscribe
<https://github.com/notifications/unsubscribe-
auth/AAACPAER4CGT7SEJEMTUNLLVPLLJVANCNFSM5Y47GK6A>.
You are receiving this because you authored the thread.Message ID:
***@***.***>

claremacrae: @elight So should I go ahead and add the fixes and test to your PR and go ahead and merge it?

I'm very happy to do so, and you will get the credit in the history and release notes.

I just didn't want to tread on your toes and do it, if that wasn't what you were expecting.

Again, huge thanks for the idea, and showing the code to do it!
elight: @claremacrae Oh, that would be lovely! I certainly didn't expect nor was I looking for it! I was more concerned that the PR would be rejected. 

That and my brain was already too numb for the day to figure out how automated testing works in JS and this repo. 😅
elight: This is wonderful! I'll be so glad to be able to torture myself with regex on a daily basis!

On second thought... 😂
claremacrae: > Sure! I've a 3-day weekend Sat-Mon. I'm on EDT. You're GMT? We can figure something out.

Excellent. I'm free on Monday. Can you email me on the address in my profile so we can set up a slot please?

> From a UX perspective, I was also thinking that it would be nice to have a separate filter for regex or at least to make it explicit/distinct like how you described above. I was being lazy in a Larry Wall sort of style by using the `TextField` filter but only when there are leading and trailing slashes on the query.

Super. It would be great if we could implement it and document and release it, working together!
claremacrae: An update... We paired on this yesterday for a couple of hours...

> I think it would be easier to document, and safer for users, if regex searches were explicit.


> Off the top of my head, ideas include:
> 
> ```
> ...
> path regex matches /sadfasfdafa/i
> path regex does not match /sadfasfdafa/I
> ...
> ```
> 

We opted for the above, and have started implementing it, initially just for `path regex matches`.

> I've [asked in Discord](https://discord.com/channels/686053708261228577/965681451297304596/986889558740320266) to see if anyone else has any ideas...

I didn't get any replies....
claremacrae: This feature would be useful for the user in https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/931
elight: @claremacrae This **should** be feature complete now. Also made a refactoring to remove some duplication between positive and negative match cases.

Documentation still pending. I'll have a look at updating that.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
new file mode 100644
index 00000000..1399a5ea
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -0,0 +1,78 @@
+name: Bug Report
+description: File a bug report - new, improved form!
+labels: ["type: bug"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        ## Before you start
+        Please **make sure you are on the latest version of Tasks.**
+        If you encountered the issue after you installed, updated, or reloaded the Tasks plugin, **please try restarting obsidian before reporting the bug**.
+
+  - type: textarea
+    id: expected
+    attributes:
+      label: Expected Behavior
+      description: Tell us what **should** happen.
+    validations:
+      required: true
+
+  - type: textarea
+    id: what-happened
+    attributes:
+      label: Current behaviour
+      description: |
+        Tell us what happens instead of the expected behavior.
+        Adding of screenshots really helps.
+    validations:
+      required: true
+
+  - type: textarea
+    id: reproduce
+    attributes:
+      label: Steps to reproduce
+      description: |
+        Which exact steps can a developer take to reproduce the issue?
+        The more detail you provide, the easier it will be to narrow down and fix the bug.
+        Please paste in any relevant text, such as example tasks and/or queries.
+    validations:
+      required: true
+
+  - type: checkboxes
+    id: operating-systems
+    attributes:
+      label: Which Operating Systems are you using?
+      description: You may select more than one.
+      options:
+        - label: Android
+        - label: iPhone/iPad
+        - label: Linux
+        - label: macOS
+        - label: Windows
+
+  - type: input
+    id: obsidian-version
+    attributes:
+      label: Obsidian Version
+      description: Which Obsidian version are you using?
+      placeholder: 0.15.9
+    validations:
+      required: true
+
+  - type: input
+    id: tasks-version
+    attributes:
+      label: Tasks Plugin Version
+      description: Which Tasks version are you using?
+      placeholder: 1.11.0
+    validations:
+      required: true
+
+  - type: checkboxes
+    id: other-plugins-disabled
+    attributes:
+      label: Checks
+      description: Please confirm
+      options:
+        - label: I have tried it with all other plugins disabled and the error still occurs
+          required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..03676acd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Suggest a new feature
+    url: https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/new?category=ideas
+    about: Request a new feature or improvement in the Tasks plugin. Please search existing Discussion Ideas first, and up-vote, to avoid duplicates.
+  - name: Ask a question
+    url: https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/new?category=q-a
+    about: Ask for help with using the Tasks plugin. In case your question has been asked before, for quicker results you can search existing Discussion Q&As.
diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md
deleted file mode 100644
index ba71e54a..00000000
--- a/.github/ISSUE_TEMPLATE/issue-template.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-name: Issue Template
-about: Use this template to report a bug. Feature requests go instead in a Discussion - under Ideas. In both cases, please check existing reports first, to avoid duplicates.
-title: ''
-labels: ''
-assignees: ''
-
----
-
-<!--
-Issue tracker is **ONLY** used for reporting bugs. New feature ideas should be discussed in the [ideas section](https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/categories/ideas). Please use the [Q&A section](https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/categories/q-a) for supporting issues. Please use the search function.
-
-If you encountered the issue after you installed, updated, or reloaded the tasks plugin, please try restarting obsidian before reporting the bug.
-
-If you want to report a bug, please follow the guide lines below to help me resolve it.
--->
-
-## Expected Behavior
-<!--- Tell us what should happen -->
-
-## Current Behavior
-<!--- Tell us what happens instead of the expected behavior -->
-
-## Steps to Reproduce
-<!-- Which exact steps can I take to reproduce the issue? -->
-
-## Context (Environment)
-
-* Obsidian version:
-* Tasks version:
-* [ ] I have tried it with all other plugins disabled and the error still occurs
-
-## Possible Solution
-<!--- Not obligatory, but suggest a fix/reason for the bug, if you have an idea -->
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 5e22dc4b..2ff188cd 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,13 +1,16 @@
 <!--- Provide a general summary of your changes in the Title above -->
 
 # Description
+
 <!--- Describe your changes in detail -->
 
 ## Motivation and Context
+
 <!--- Why is this change required? What problem does it solve? -->
 <!--- If it fixes an open issue, please link to the issue here. -->
 
 ## How has this been tested?
+
 <!--- Please describe in detail how you tested your changes. -->
 <!--- Include details of your testing environment, tests ran to see how -->
 <!--- your change affects other areas of the code, etc. -->
@@ -15,18 +18,41 @@
 ## Screenshots (if appropriate)
 
 ## Types of changes
+
 <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] Refactor (non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+
+Changes visible to users:
+
+- [ ] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
+- [ ] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
+- [ ] **Breaking change** (prefix: `feat!!` or `fix!!` - fix or feature that would cause existing functionality to not work as expected)
+- [ ] **Documentation** (prefix: `docs` - improvements to any documentation content)
+- [ ] **Sample vault** (prefix: `vault` - improvements to the [Tasks-Demo sample vault](https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/resources/sample_vaults/Tasks-Demo))
+
+Internal changes:
+
+- [ ] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
+- [ ] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)
+- [ ] **Infrastructure** (prefix: `chore` - examples include GitHub Actions, issue templates)
 
 ## Checklist
+
 <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
 <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
+
 - [ ] My code follows the code style of this project and passes `yarn run lint`.
 - [ ] My change requires a change to the documentation.
-- [ ] I have updated the documentation accordingly.
-- [ ] My change has adequate Unit Test coverage.
+- [ ] I have [updated the documentation](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
+- [ ] My change has adequate [Unit Test coverage](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#maintaining-the-tests).
+
+## Terms
+
+<!--
+By submitting this pull request, you must agree to follow our
+[contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md) and
+[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
+Put an x in the boxes to confirm you agree.
+-->
 
-By creating a Pull Request you agree to our [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md). For further guidance on contributing please see [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
+- [ ] My contribution follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
+- [ ] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)
diff --git a/docs/README.md b/docs/README.md
index 6ddd27e3..c29c38a8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -49,7 +49,13 @@ If you can run docker, this is the easiest way.
 
 ### Seeing the docs via Docker
 
-Now every time you want to see the docs locally, run:
+> Note: If using Windows, there are some interoperability issues with Windows, Docker, and Jekyll.
+> Recommend running the command below from a Windows Subsystem for Linux (WSL) shell,
+> where you can likely get to your Windows user directory via `/mnt/c/Users/<your username>`,
+> and using a browser from inside WSL2 if possible. Otherwise, if running the browser from Windows,
+> at every page load you must change "0.0.0.0" to "localhost" in the browser's address bar.
+
+To see the docs locally, run:
 
 ```bash
 cd obsidian-tasks/docs
@@ -108,3 +114,15 @@ bundle exec jekyll serve
 
 In the output, look for the line containing `Server address:` and open that URL in your local browser.
 It will be something like <http://0.0.0.0:4000/obsidian-tasks/>.
+
+## Dependency Management and Updates for the Docs
+
+The dependencies for running the docs locally are stored in the `Gemfile` (short, equivalent to Javascript `package.json`) and `Gemfile.lock` (auto-generated, equivalent to `yarn.lock`) files.
+The package manager (equivalent to "yarn") is "bundle" [docs here](https://bundler.io/guides/using_bundler_in_applications.html#recommended-workflow). The "docker_start" script runs the initial
+`bundle install` when building the Docker image.
+If seeing issues with dependencies or with running the docs locally, check that the versions in the `Gemfile`, especially "github-pages" are up-to-date. If making edits to the `Gemfile`, you
+will need to run `bundle install` or `bundle update` manually. (You may also need to update `bundle` itself!) Before checking in any changes to `Gemfile.lock`, see the paragraph below.
+
+Some dependencies, such as "nokogiri", have platform-specific gems that **should not** be checked into the repo's `Gemfile.lock`.
+For example, Dependabot might add a Linux-specific version of a dependency to the `Gemfile.lock`, which must then be manually edited to remove the Linux-specific version.
+Otherwise, future contributors on non-Linux systems will be unable to build the Docker image or run Jekyll locally.
diff --git a/docs/advanced/notifications.md b/docs/advanced/notifications.md
index e575a2b6..86423d22 100644
--- a/docs/advanced/notifications.md
+++ b/docs/advanced/notifications.md
@@ -18,15 +18,35 @@ _Note that this is a screenshot of the reminder plugin's settings and not Tasks.
 
 ## Where to add the reminder date
 
-The order is important when writing the task. You have to put the reminder date between the task name and the other fields :
+The order is important when writing the task. Tasks requires the reminder date after the task description and before any other Tasks fields. Reminders [requires no content between the reminder date and the due date](https://uphy.github.io/obsidian-reminder/guide/interop-tasks.html#distinguish-due-date-and-reminder-date).
 
 ```markdown
-- [ ] #task task name ⏰ YYYY-MM-DD HH:mm ⏫  🔁 every *** 🛫 YYYY-MM-DD ⏳ YYYY-MM-DD 📅 YYYY-MM-DD
+- [ ] #task task name ⏰ YYYY-MM-DD HH:mm 📅 YYYY-MM-DD ⏫ 🔁 every week 🛫 YYYY-MM-DD ⏳ YYYY-MM-DD
 ```
 
+---
+
+Warning
+{: .label .label-yellow}
+The output of the "Create or Edit Tasks" command will not put the due date directly behind the reminder date where Reminders wants it.
+You must fix this manually if you want to use the Reminders "defer" command;
+**otherwise (as of August 2022), using the Reminders "defer" command will overwrite all information between the ⏰ and 📅 emoji!**
+See [this issue in Reminders to check the current status](https://github.com/uphy/obsidian-reminder/issues/100).
+
+---
+
 ## How to complete the reminder
 
-The reminder date doesn't change when completing the task as of yet, the date will change only when you complete it from the reminder popup or from the notification.
+The reminder date doesn't change when completing the task, the date will change only when you complete it from the reminder popup or from the notification.
 
 ![image](https://user-images.githubusercontent.com/38974541/143463881-e4af4b91-426f-48e8-938e-4a1053b06677.png)
 ![image](https://user-images.githubusercontent.com/38974541/143464983-542675ae-a467-41c0-aaca-1075c42f8328.png)
+
+---
+
+Warning
+{: .label .label-yellow}
+Completing recurring tasks does not work correctly with Reminders as of August 2022.
+See [this issue in Reminders to check the current status](https://github.com/uphy/obsidian-reminder/issues/93).
+
+---
diff --git a/docs/getting-started/auto-suggest.md b/docs/getting-started/auto-suggest.md
index 8d4122e9..b961dab4 100644
--- a/docs/getting-started/auto-suggest.md
+++ b/docs/getting-started/auto-suggest.md
@@ -223,6 +223,17 @@ Similarly, you can type some fraction of the word `start` (of whatever length is
 | next month (2022-08-11) | 2022-08-11                 |
 | next year (2023-07-11)  | 2023-07-11                 |
 
+### How can I use auto-suggest features from other plugins together with the Tasks auto-suggest?
+
+Obsidian plugins such as Tasks cannot tell if you have auto-suggest features from other plugins enabled.
+Therefore it is user responsibility to manage conflicts between auto-suggest features.
+
+The Tasks auto-suggest will only appear on lines that start `- [ ]` and contain the global filter (if one is set).
+If you want to use auto-suggest features from another plugin on such lines, make sure that plugin's settings for auto-suggest
+appearance do not overlap with the keywords listed above,
+then increase the 'Minimum match length for auto-suggest' value in the Tasks settings to more characters than used to activate the other plugin's auto-suggest,
+and re-start Obsidian.
+
 ## Settings
 
 Note that like all Tasks settings, after any changes, Obsidian needs to be restarted for the new settings to take effect.
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index 616e2c9f..83d4685a 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -78,6 +78,31 @@ The following _does not work:_
 
 ---
 
+Warning
+{: .label .label-yellow}
+Tasks can read tasks that are inside blockquotes or [Obsidian's built-in callouts](https://help.obsidian.md/How+to/Use+callouts).
+
+> Reading tasks inside callouts and blockquotes was introduced in Tasks 1.11.1
+
+However, under the following very specific circumstance, Tasks cannot add or remove completion dates or make the next copy of a recurring task:
+
+- Obsidian is in Live Preview editor mode (pencil icon in lower right corner),
+- AND the task's markdown is in a callout,
+- AND the user clicked on the task's checkbox to complete or re-open the task.
+
+If you toggle a task's status in this situation, you will see a warning. Use the command `Tasks: Toggle Done`, or switch to Reading View (book icon in lower right corner) to click the checkbox.
+
+Completing a task by clicking its checkbox from a `tasks` query block _will_ work in any editor mode, even if the query is inside a callout.
+
+---
+
+Warning
+{: .label .label-yellow}
+
+Tasks cannot read tasks that are inside code blocks, such as the ones used by the Admonitions plugin. Use Obsidian's built-in callouts instead.
+
+---
+
 Warning
 {: .label .label-yellow}
 Tasks can only render inline footnotes. Regular footnotes are not supported.
diff --git a/docs/queries/filters.md b/docs/queries/filters.md
index 6d1584d1..e630dad2 100644
--- a/docs/queries/filters.md
+++ b/docs/queries/filters.md
@@ -134,6 +134,10 @@ As well as the date-related searches above, these filters search other propertie
 - `description (includes|does not include) <string>`
   - Matches case-insensitive (disregards capitalization).
   - Disregards the global filter when matching.
+- `description (regex matches|regex does not match) <JavaScript-style Regex>`
+  - Matches based on [JavaScript's RegExp implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
+  - Supports [JavaScript RegExp Flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
+  - **_Use with extreme care; this is a tool for software developers or people willing to spend a lot of time reading complicated documentation_**
 
 ### Priority
 
diff --git a/manifest.json b/manifest.json
index 7e824ec8..15743582 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
 {
   "id": "obsidian-tasks-plugin",
   "name": "Tasks",
-  "version": "1.11.0",
+  "version": "1.11.1",
   "minAppVersion": "0.14.6",
   "description": "Task management for Obsidian",
   "author": "Martin Schenck and Clare Macrae",
diff --git a/package.json b/package.json
index ceec778b..20618bc9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "obsidian-tasks",
-  "version": "1.11.0",
+  "version": "1.11.1",
   "description": "Task management for the Obsidian knowledge base",
   "main": "main.js",
   "scripts": {
@@ -24,14 +24,14 @@
   "license": "MIT",
   "devDependencies": {
     "@arkweid/lefthook": "^0.7.6",
-    "@codemirror/view": "^6.1.2",
+    "@codemirror/view": "^6.2.0",
     "@tsconfig/svelte": "^3.0.0",
     "@types/jest": "^28.1.6",
-    "@typescript-eslint/eslint-plugin": "^5.32.0",
-    "@typescript-eslint/parser": "^5.32.0",
+    "@typescript-eslint/eslint-plugin": "^5.33.0",
+    "@typescript-eslint/parser": "^5.33.0",
     "async-mutex": "^0.3.1",
     "builtin-modules": "^3.3.0",
-    "esbuild": "0.14.51",
+    "esbuild": "0.14.54",
     "esbuild-svelte": "^0.7.1",
     "eslint": "^8.21.0",
     "eslint-config-prettier": "^8.5.0",
@@ -57,4 +57,4 @@
     "eventemitter2": "^6.4.5",
     "rrule": "^2.7.1"
   }
-}
\ No newline at end of file
+}
diff --git a/src/Cache.ts b/src/Cache.ts
index 9a64886e..be05b6d6 100644
--- a/src/Cache.ts
+++ b/src/Cache.ts
@@ -1,6 +1,6 @@
 import { MetadataCache, TAbstractFile, TFile, Vault } from 'obsidian';
-import type { CachedMetadata, ListItemCache } from 'obsidian';
-import type { EventRef, SectionCache } from 'obsidian';
+import type { CachedMetadata, EventRef } from 'obsidian';
+import type { ListItemCache, SectionCache } from 'obsidian';
 import { Mutex } from 'async-mutex';
 
 import { Task } from './Task';
@@ -320,7 +320,6 @@ export class Cache {
 
         for (const section of sections) {
             if (
-                section.type === 'list' &&
                 section.position.start.line <= lineNumberTask &&
                 section.position.end.line >= lineNumberTask
             ) {
diff --git a/src/Commands/CreateOrEdit.ts b/src/Commands/CreateOrEdit.ts
index cfc970d0..bae133fc 100644
--- a/src/Commands/CreateOrEdit.ts
+++ b/src/Commands/CreateOrEdit.ts
@@ -58,7 +58,7 @@ const taskFromLine = ({ line, path }: { line: string; path: string }): Task => {
 
     // If we are not on a line of a task, we take what we have.
     // The non-task line can still be a checklist, for example if it is lacking the global filter.
-    const nonTaskRegex: RegExp = /^([\s\t]*)[-*]? *(\[(.)\])? *(.*)/u;
+    const nonTaskRegex: RegExp = /^([\s\t>]*)[-*]? *(\[(.)\])? *(.*)/u;
     const nonTaskMatch = line.match(nonTaskRegex);
     if (nonTaskMatch === null) {
         // Should never happen; everything in the regex is optional.
diff --git a/src/Commands/ToggleDone.ts b/src/Commands/ToggleDone.ts
index a595f69f..30545799 100644
--- a/src/Commands/ToggleDone.ts
+++ b/src/Commands/ToggleDone.ts
@@ -72,13 +72,13 @@ const toggleLine = ({ line, path }: { line: string; path: string }): string => {
             // 1. a list item
             // 2. a simple text line
 
-            const listItemRegex = /^([\s\t]*)([-*])/;
+            const listItemRegex = /^([\s\t>]*)([-*])/;
             if (listItemRegex.test(line)) {
                 // Let's convert the list item to a checklist item.
                 toggledLine = line.replace(listItemRegex, '$1$2 [ ]');
             } else {
                 // Let's convert the line to a list item.
-                toggledLine = line.replace(/^([\s\t]*)/, '$1- ');
+                toggledLine = line.replace(/^([\s\t>]*)/, '$1- ');
             }
         }
     }
diff --git a/src/Events.ts b/src/Events.ts
index ae80a0ef..bf87d834 100644
--- a/src/Events.ts
+++ b/src/Events.ts
@@ -16,8 +16,8 @@ interface CacheUpdateData {
 export class Events {
     private obsidianEvents: ObsidianEvents;
 
-    constructor({ obsidianEents }: { obsidianEents: ObsidianEvents }) {
-        this.obsidianEvents = obsidianEents;
+    constructor({ obsidianEvents }: { obsidianEvents: ObsidianEvents }) {
+        this.obsidianEvents = obsidianEvents;
     }
 
     public onCacheUpdate(
diff --git a/src/LivePreviewExtension.ts b/src/LivePreviewExtension.ts
index 7a0715fb..372f274b 100644
--- a/src/LivePreviewExtension.ts
+++ b/src/LivePreviewExtension.ts
@@ -1,5 +1,6 @@
 import { EditorView, ViewPlugin } from '@codemirror/view';
 import type { PluginValue } from '@codemirror/view';
+import { Notice } from 'obsidian';
 
 import { Task } from './Task';
 
@@ -33,8 +34,31 @@ class LivePreviewExtension implements PluginValue {
             return false;
         }
 
+        /* Right now Obsidian API does not give us a way to handle checkbox clicks inside rendered-widgets-in-LP such as
+         * callouts, tables, and transclusions because `this.view.posAtDOM` will return the beginning of the widget
+         * as the position for any click inside the widget.
+         * For callouts, this means that the task will never be found, since the `lineAt` will be the beginning of the callout.
+         * Therefore, produce an error message pop-up using Obsidian's "Notice" feature, log a console warning, then return.
+         */
+
+        // Tasks from "task" query codeblocks handle themselves thanks to `toLi`, so be specific about error messaging, but still return.
+        const ancestor = target.closest(
+            'ul.plugin-tasks-query-result, div.callout-content',
+        );
+        if (ancestor) {
+            if (ancestor.matches('div.callout-content')) {
+                // Error message for now.
+                const msg =
+                    'obsidian-tasks-plugin warning: Tasks cannot add or remove completion dates or make the next copy of a recurring task for tasks written inside a callout when you click their checkboxes in Live Preview. \n' +
+                    'If you wanted Tasks to do these things, please undo your change, then either click the line of the task and use the "Toggle Task Done" command, or switch to Reading View to click the checkbox.';
+                console.warn(msg);
+                new Notice(msg, 45000);
+            }
+            return false;
+        }
+
         const { state } = this.view;
-        const position = this.view.posAtDOM(target as Node);
+        const position = this.view.posAtDOM(target);
         const line = state.doc.lineAt(position);
         const task = Task.fromLine({
             line: line.text,
@@ -47,6 +71,10 @@ class LivePreviewExtension implements PluginValue {
             precedingHeader: null,
         });
 
+        console.debug(
+            `Live Preview Extension: toggle called. Position: ${position} Line: ${line.text}`,
+        );
+
         // Only handle checkboxes of tasks.
         if (task === null) {
             return false;
@@ -58,7 +86,7 @@ class LivePreviewExtension implements PluginValue {
         // Clicked on a task's checkbox. Toggle the task and set it.
         const toggled = task.toggle();
         const toggledString = toggled
-            .map((task) => task.toFileLineString())
+            .map((t) => t.toFileLineString())
             .join(state.lineBreak);
 
         // Creates a CodeMirror transaction in order to update the document.
diff --git a/src/Query/Filter/DescriptionField.ts b/src/Query/Filter/DescriptionField.ts
index fa9c5577..d31bb931 100644
--- a/src/Query/Filter/DescriptionField.ts
+++ b/src/Query/Filter/DescriptionField.ts
@@ -9,17 +9,10 @@ import { TextField } from './TextField';
  * with the global filter (if any) removed.
  */
 export class DescriptionField extends TextField {
-    private static readonly descriptionRegexp =
-        /^description (includes|does not include) (.*)/;
-
     protected fieldName(): string {
         return 'description';
     }
 
-    protected filterRegexp(): RegExp {
-        return DescriptionField.descriptionRegexp;
-    }
-
     /**
      * Return the task's description, with any global tag removed
      * @param task
diff --git a/src/Query/Filter/ExcludeSubItemsField.ts b/src/Query/Filter/ExcludeSubItemsField.ts
index 227a777a..b8c551c5 100644
--- a/src/Query/Filter/ExcludeSubItemsField.ts
+++ b/src/Query/Filter/ExcludeSubItemsField.ts
@@ -7,10 +7,15 @@ export class ExcludeSubItemsField extends FilterInstructionsBasedField {
     constructor() {
         super();
 
-        this._filters.add(
-            'exclude sub-items',
-            (task) => task.indentation === '',
-        );
+        this._filters.add('exclude sub-items', (task) => {
+            if (task.indentation === '') return true; // no indentation, not a subitem
+
+            const lastBlockquoteMark = task.indentation.lastIndexOf('>');
+            if (lastBlockquoteMark === -1) return false; // indentation present, not in a blockquote, subitem
+
+            // Up to one space allowed after last > in blockquote/callout, otherwise subitem
+            return /^ ?$/.test(task.indentation.slice(lastBlockquoteMark + 1));
+        });
     }
 
     protected fieldName(): string {
diff --git a/src/Query/Filter/HeadingField.ts b/src/Query/Filter/HeadingField.ts
index d1aa6a52..403e1a8a 100644
--- a/src/Query/Filter/HeadingField.ts
+++ b/src/Query/Filter/HeadingField.ts
@@ -5,13 +5,6 @@ import { TextField } from './TextField';
  *
  */
 export class HeadingField extends TextField {
-    private static readonly headingRegexp =
-        /^heading (includes|does not include) (.*)/;
-
-    protected filterRegexp(): RegExp {
-        return HeadingField.headingRegexp;
-    }
-
     protected fieldName(): string {
         return 'heading';
     }
diff --git a/src/Query/Filter/PathField.ts b/src/Query/Filter/PathField.ts
index e2ac4b33..c19294e3 100644
--- a/src/Query/Filter/PathField.ts
+++ b/src/Query/Filter/PathField.ts
@@ -8,13 +8,6 @@ import { TextField } from './TextField';
  *
  */
 export class PathField extends TextField {
-    private static readonly pathRegexp =
-        /^path (includes|does not include) (.*)/;
-
-    protected filterRegexp(): RegExp {
-        return PathField.pathRegexp;
-    }
-
     protected fieldName(): string {
         return 'path';
     }
diff --git a/src/Query/Filter/TextField.ts b/src/Query/Filter/TextField.ts
index 497f2725..ddbde515 100644
--- a/src/Query/Filter/TextField.ts
+++ b/src/Query/Filter/TextField.ts
@@ -8,23 +8,42 @@ import { FilterOrErrorMessage } from './Filter';
  * value, such as the description or file path.
  */
 export abstract class TextField extends Field {
+    private maybeNegate(match: boolean, filterMethod: String) {
+        return filterMethod.match(/not/) ? !match : match;
+    }
     public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
         const result = new FilterOrErrorMessage();
         const match = Field.getMatch(this.filterRegexp(), line);
         if (match !== null) {
             const filterMethod = match[1];
-            if (filterMethod === 'includes') {
-                result.filter = (task: Task) =>
-                    TextField.stringIncludesCaseInsensitive(
-                        this.value(task),
-                        match[2],
-                    );
-            } else if (match[1] === 'does not include') {
+            if (['includes', 'does not include'].includes(filterMethod)) {
                 result.filter = (task: Task) =>
-                    !TextField.stringIncludesCaseInsensitive(
-                        this.value(task),
-                        match[2],
+                    this.maybeNegate(
+                        TextField.stringIncludesCaseInsensitive(
+                            this.value(task),
+                            match[2],
+                        ),
+                        filterMethod,
                     );
+            } else if (
+                ['regex matches', 'regex does not match'].includes(filterMethod)
+            ) {
+                // Courtesy of https://stackoverflow.com/questions/17843691/javascript-regex-to-match-a-regex
+                const regexPattern =
+                    /\/((?![*+?])(?:[^\r\n[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*])+)\/((?:g(?:im?|mi?)?|i(?:gm?|mg?)?|m(?:gi?|ig?)?)?)/;
+                const query = match[2].match(regexPattern);
+
+                if (query !== null) {
+                    result.filter = (task: Task) =>
+                        this.maybeNegate(
+                            this.value(task).match(
+                                new RegExp(query[1], query[2]),
+                            ) !== null,
+                            filterMethod,
+                        );
+                } else {
+                    result.error = `cannot parse regex (${this.fieldName()}); check your leading and trailing slashes for your query`;
+                }
             } else {
                 result.error = `do not understand query filter (${this.fieldName()})`;
             }
@@ -43,7 +62,11 @@ export abstract class TextField extends Field {
             .includes(needle.toLocaleLowerCase());
     }
 
-    protected abstract filterRegexp(): RegExp | null;
+    protected filterRegexp(): RegExp {
+        return new RegExp(
+            `^${this.fieldName()} (includes|does not include|regex matches|regex does not match) (.*)`,
+        );
+    }
 
     protected abstract fieldName(): string;
 
diff --git a/src/Task.ts b/src/Task.ts
index 436090d6..9cfee567 100644
--- a/src/Task.ts
+++ b/src/Task.ts
@@ -85,10 +85,10 @@ export class Task {
     public static readonly dateFormat = 'YYYY-MM-DD';
 
     // Main regex for parsing a line. It matches the following:
-    // - Indentation
+    // - Indentation (including > for potentially nested blockquotes or Obsidian callouts)
     // - Status character
     // - Rest of task after checkbox markdown
-    public static readonly taskRegex = /^([\s\t]*)[-*] +\[(.)\] *(.*)/u;
+    public static readonly taskRegex = /^([\s\t>]*)[-*] +\[(.)\] *(.*)/u;
 
     // Match on block link at end.
     public static readonly blockLinkRegex = / \^[a-zA-Z0-9-]+$/u;
diff --git a/src/main.ts b/src/main.ts
index 2df60804..e0fe1340 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -27,7 +27,7 @@ export default class TasksPlugin extends Plugin {
             vault: this.app.vault,
         });
 
-        const events = new Events({ obsidianEents: this.app.workspace });
+        const events = new Events({ obsidianEvents: this.app.workspace });
         this.cache = new Cache({
             metadataCache: this.app.metadataCache,
             vault: this.app.vault,
diff --git a/versions.json b/versions.json
index 4fb059b5..a9bc609d 100644
--- a/versions.json
+++ b/versions.json
@@ -35,5 +35,6 @@
   "1.8.2": "0.13.21",
   "1.9.0": "0.14.6",
   "1.10.0": "0.14.6",
-  "1.11.0": "0.14.6"
+  "1.11.0": "0.14.6",
+  "1.11.1": "0.14.6"
 }
diff --git a/yarn.lock b/yarn.lock
index a0da2e6a..8eb0f8e4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -523,15 +523,20 @@
     style-mod "^4.0.0"
     w3c-keyname "^2.2.4"
 
-"@codemirror/view@^6.1.2":
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.1.2.tgz#22dd4f6867581aa09dc1d952253f655c1a44bd91"
-  integrity sha512-puUydfKwfmOo+ixtuB+uN/ZpcteEYSnpjHmMaow1sOQhNICsKtGBup3i9ybVqyzDagARRYzSHTWjbdeHqmn31w==
+"@codemirror/view@^6.2.0":
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.2.0.tgz#bae4c486a84174bd9656af9c9d2bd709d5b26c9f"
+  integrity sha512-3emW1symh+GoteFMBPsltjmF790U/trouLILATh3JodbF/z98HvcQh2g3+H6dfNIHx16uNonsAF4mNzVr1TJNA==
   dependencies:
     "@codemirror/state" "^6.0.0"
     style-mod "^4.0.0"
     w3c-keyname "^2.2.4"
 
+"@esbuild/linux-loong64@0.14.54":
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
+  integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
+
 "@eslint/eslintrc@^1.3.0":
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
@@ -1019,14 +1024,14 @@
   dependencies:
     "@types/yargs-parser" "*"
 
-"@typescript-eslint/eslint-plugin@^5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz#e27e38cffa4a61226327c874a7be965e9a861624"
-  integrity sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew==
+"@typescript-eslint/eslint-plugin@^5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz#059798888720ec52ffa96c5f868e31a8f70fa3ec"
+  integrity sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.32.0"
-    "@typescript-eslint/type-utils" "5.32.0"
-    "@typescript-eslint/utils" "5.32.0"
+    "@typescript-eslint/scope-manager" "5.33.0"
+    "@typescript-eslint/type-utils" "5.33.0"
+    "@typescript-eslint/utils" "5.33.0"
     debug "^4.3.4"
     functional-red-black-tree "^1.0.1"
     ignore "^5.2.0"
@@ -1034,69 +1039,69 @@
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@^5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.32.0.tgz#1de243443bc6186fb153b9e395b842e46877ca5d"
-  integrity sha512-IxRtsehdGV9GFQ35IGm5oKKR2OGcazUoiNBxhRV160iF9FoyuXxjY+rIqs1gfnd+4eL98OjeGnMpE7RF/NBb3A==
+"@typescript-eslint/parser@^5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.0.tgz#26ec3235b74f0667414613727cb98f9b69dc5383"
+  integrity sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.32.0"
-    "@typescript-eslint/types" "5.32.0"
-    "@typescript-eslint/typescript-estree" "5.32.0"
+    "@typescript-eslint/scope-manager" "5.33.0"
+    "@typescript-eslint/types" "5.33.0"
+    "@typescript-eslint/typescript-estree" "5.33.0"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz#763386e963a8def470580cc36cf9228864190b95"
-  integrity sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg==
+"@typescript-eslint/scope-manager@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz#509d7fa540a2c58f66bdcfcf278a3fa79002e18d"
+  integrity sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==
   dependencies:
-    "@typescript-eslint/types" "5.32.0"
-    "@typescript-eslint/visitor-keys" "5.32.0"
+    "@typescript-eslint/types" "5.33.0"
+    "@typescript-eslint/visitor-keys" "5.33.0"
 
-"@typescript-eslint/type-utils@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz#45a14506fe3fb908600b4cef2f70778f7b5cdc79"
-  integrity sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg==
+"@typescript-eslint/type-utils@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz#92ad1fba973c078d23767ce2d8d5a601baaa9338"
+  integrity sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==
   dependencies:
-    "@typescript-eslint/utils" "5.32.0"
+    "@typescript-eslint/utils" "5.33.0"
     debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.32.0.tgz#484273021eeeae87ddb288f39586ef5efeb6dcd8"
-  integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==
+"@typescript-eslint/types@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.33.0.tgz#d41c584831805554b063791338b0220b613a275b"
+  integrity sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==
 
-"@typescript-eslint/typescript-estree@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz#282943f34babf07a4afa7b0ff347a8e7b6030d12"
-  integrity sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg==
+"@typescript-eslint/typescript-estree@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz#02d9c9ade6f4897c09e3508c27de53ad6bfa54cf"
+  integrity sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==
   dependencies:
-    "@typescript-eslint/types" "5.32.0"
-    "@typescript-eslint/visitor-keys" "5.32.0"
+    "@typescript-eslint/types" "5.33.0"
+    "@typescript-eslint/visitor-keys" "5.33.0"
     debug "^4.3.4"
     globby "^11.1.0"
     is-glob "^4.0.3"
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.32.0.tgz#eccb6b672b94516f1afc6508d05173c45924840c"
-  integrity sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ==
+"@typescript-eslint/utils@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.33.0.tgz#46797461ce3146e21c095d79518cc0f8ec574038"
+  integrity sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.32.0"
-    "@typescript-eslint/types" "5.32.0"
-    "@typescript-eslint/typescript-estree" "5.32.0"
+    "@typescript-eslint/scope-manager" "5.33.0"
+    "@typescript-eslint/types" "5.33.0"
+    "@typescript-eslint/typescript-estree" "5.33.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.32.0":
-  version "5.32.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz#b9715d0b11fdb5dd10fd0c42ff13987470525394"
-  integrity sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g==
+"@typescript-eslint/visitor-keys@5.33.0":
+  version "5.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz#fbcbb074e460c11046e067bc3384b5d66b555484"
+  integrity sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==
   dependencies:
-    "@typescript-eslint/types" "5.32.0"
+    "@typescript-eslint/types" "5.33.0"
     eslint-visitor-keys "^3.3.0"
 
 abab@^2.0.5, abab@^2.0.6:
@@ -1751,136 +1756,137 @@ es6-promise@^3.1.2:
   resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
   integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==
 
-esbuild-android-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.51.tgz#414a087cb0de8db1e347ecca6c8320513de433db"
-  integrity sha512-6FOuKTHnC86dtrKDmdSj2CkcKF8PnqkaIXqvgydqfJmqBazCPdw+relrMlhGjkvVdiiGV70rpdnyFmA65ekBCQ==
-
-esbuild-android-arm64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.51.tgz#55de3bce2aab72bcd2b606da4318ad00fb9c8151"
-  integrity sha512-vBtp//5VVkZWmYYvHsqBRCMMi1MzKuMIn5XDScmnykMTu9+TD9v0NMEDqQxvtFToeYmojdo5UCV2vzMQWJcJ4A==
-
-esbuild-darwin-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.51.tgz#4259f23ed6b4cea2ec8a28d87b7fb9801f093754"
-  integrity sha512-YFmXPIOvuagDcwCejMRtCDjgPfnDu+bNeh5FU2Ryi68ADDVlWEpbtpAbrtf/lvFTWPexbgyKgzppNgsmLPr8PA==
-
-esbuild-darwin-arm64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.51.tgz#d77b4366a71d84e530ba019d540b538b295d494a"
-  integrity sha512-juYD0QnSKwAMfzwKdIF6YbueXzS6N7y4GXPDeDkApz/1RzlT42mvX9jgNmyOlWKN7YzQAYbcUEJmZJYQGdf2ow==
-
-esbuild-freebsd-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.51.tgz#27b6587b3639f10519c65e07219d249b01f2ad38"
-  integrity sha512-cLEI/aXjb6vo5O2Y8rvVSQ7smgLldwYY5xMxqh/dQGfWO+R1NJOFsiax3IS4Ng300SVp7Gz3czxT6d6qf2cw0g==
-
-esbuild-freebsd-arm64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.51.tgz#63c435917e566808c71fafddc600aca4d78be1ec"
-  integrity sha512-TcWVw/rCL2F+jUgRkgLa3qltd5gzKjIMGhkVybkjk6PJadYInPtgtUBp1/hG+mxyigaT7ib+od1Xb84b+L+1Mg==
-
-esbuild-linux-32@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.51.tgz#c3da774143a37e7f11559b9369d98f11f997a5d9"
-  integrity sha512-RFqpyC5ChyWrjx8Xj2K0EC1aN0A37H6OJfmUXIASEqJoHcntuV3j2Efr9RNmUhMfNE6yEj2VpYuDteZLGDMr0w==
-
-esbuild-linux-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.51.tgz#5d92b67f674e02ae0b4a9de9a757ba482115c4ae"
-  integrity sha512-dxjhrqo5i7Rq6DXwz5v+MEHVs9VNFItJmHBe1CxROWNf4miOGoQhqSG8StStbDkQ1Mtobg6ng+4fwByOhoQoeA==
-
-esbuild-linux-arm64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.51.tgz#dac84740516e859d8b14e1ecc478dd5241b10c93"
-  integrity sha512-D9rFxGutoqQX3xJPxqd6o+kvYKeIbM0ifW2y0bgKk5HPgQQOo2k9/2Vpto3ybGYaFPCE5qTGtqQta9PoP6ZEzw==
-
-esbuild-linux-arm@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.51.tgz#b3ae7000696cd53ed95b2b458554ff543a60e106"
-  integrity sha512-LsJynDxYF6Neg7ZC7748yweCDD+N8ByCv22/7IAZglIEniEkqdF4HCaa49JNDLw1UQGlYuhOB8ZT/MmcSWzcWg==
-
-esbuild-linux-mips64le@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.51.tgz#dad10770fac94efa092b5a0643821c955a9dd385"
-  integrity sha512-vS54wQjy4IinLSlb5EIlLoln8buh1yDgliP4CuEHumrPk4PvvP4kTRIG4SzMXm6t19N0rIfT4bNdAxzJLg2k6A==
-
-esbuild-linux-ppc64le@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.51.tgz#b68c2f8294d012a16a88073d67e976edd4850ae0"
-  integrity sha512-xcdd62Y3VfGoyphNP/aIV9LP+RzFw5M5Z7ja+zdpQHHvokJM7d0rlDRMN+iSSwvUymQkqZO+G/xjb4/75du8BQ==
-
-esbuild-linux-riscv64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.51.tgz#608a318b8697123e44c1e185cdf6708e3df50b93"
-  integrity sha512-syXHGak9wkAnFz0gMmRBoy44JV0rp4kVCEA36P5MCeZcxFq8+fllBC2t6sKI23w3qd8Vwo9pTADCgjTSf3L3rA==
-
-esbuild-linux-s390x@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.51.tgz#c9e7791170a3295dba79b93aa452beb9838a8625"
-  integrity sha512-kFAJY3dv+Wq8o28K/C7xkZk/X34rgTwhknSsElIqoEo8armCOjMJ6NsMxm48KaWY2h2RUYGtQmr+RGuUPKBhyw==
-
-esbuild-netbsd-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.51.tgz#0abd40b8c2e37fda6f5cc41a04cb2b690823d891"
-  integrity sha512-ZZBI7qrR1FevdPBVHz/1GSk1x5GDL/iy42Zy8+neEm/HA7ma+hH/bwPEjeHXKWUDvM36CZpSL/fn1/y9/Hb+1A==
-
-esbuild-openbsd-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.51.tgz#4adba0b7ea7eb1428bb00d8e94c199a949b130e8"
-  integrity sha512-7R1/p39M+LSVQVgDVlcY1KKm6kFKjERSX1lipMG51NPcspJD1tmiZSmmBXoY5jhHIu6JL1QkFDTx94gMYK6vfA==
-
-esbuild-sunos-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.51.tgz#4b8a6d97dfedda30a6e39607393c5c90ebf63891"
-  integrity sha512-HoHaCswHxLEYN8eBTtyO0bFEWvA3Kdb++hSQ/lLG7TyKF69TeSG0RNoBRAs45x/oCeWaTDntEZlYwAfQlhEtJA==
+esbuild-android-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
+  integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
+
+esbuild-android-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
+  integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
+
+esbuild-darwin-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
+  integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
+
+esbuild-darwin-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
+  integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
+
+esbuild-freebsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
+  integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
+
+esbuild-freebsd-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
+  integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
+
+esbuild-linux-32@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
+  integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
+
+esbuild-linux-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
+  integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
+
+esbuild-linux-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
+  integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
+
+esbuild-linux-arm@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
+  integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
+
+esbuild-linux-mips64le@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
+  integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
+
+esbuild-linux-ppc64le@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
+  integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
+
+esbuild-linux-riscv64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
+  integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
+
+esbuild-linux-s390x@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
+  integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
+
+esbuild-netbsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
+  integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
+
+esbuild-openbsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
+  integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
+
+esbuild-sunos-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
+  integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
 
 esbuild-svelte@^0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/esbuild-svelte/-/esbuild-svelte-0.7.1.tgz#5390e2c66937fed51dffa9d4d8ed0547a6628357"
   integrity sha512-83vRAJ2OmoKxmK+rLFZVmyv5bJ8ahsYQwJ2RGmNAlHBIHq4ENUwA/hiwA2+AohrWD1BgZnGPMj8DL3l0I0xkug==
 
-esbuild-windows-32@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.51.tgz#d31d8ca0c1d314fb1edea163685a423b62e9ac17"
-  integrity sha512-4rtwSAM35A07CBt1/X8RWieDj3ZUHQqUOaEo5ZBs69rt5WAFjP4aqCIobdqOy4FdhYw1yF8Z0xFBTyc9lgPtEg==
-
-esbuild-windows-64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.51.tgz#7d3c09c8652d222925625637bdc7e6c223e0085d"
-  integrity sha512-HoN/5HGRXJpWODprGCgKbdMvrC3A2gqvzewu2eECRw2sYxOUoh2TV1tS+G7bHNapPGI79woQJGV6pFH7GH7qnA==
-
-esbuild-windows-arm64@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.51.tgz#0220d2304bfdc11bc27e19b2aaf56edf183e4ae9"
-  integrity sha512-JQDqPjuOH7o+BsKMSddMfmVJXrnYZxXDHsoLHc0xgmAZkOOCflRmC43q31pk79F9xuyWY45jDBPolb5ZgGOf9g==
-
-esbuild@0.14.51:
-  version "0.14.51"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.51.tgz#1c8ecbc8db3710da03776211dc3ee3448f7aa51e"
-  integrity sha512-+CvnDitD7Q5sT7F+FM65sWkF8wJRf+j9fPcprxYV4j+ohmzVj2W7caUqH2s5kCaCJAfcAICjSlKhDCcvDpU7nw==
+esbuild-windows-32@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
+  integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
+
+esbuild-windows-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
+  integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
+
+esbuild-windows-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
+  integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
+
+esbuild@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
+  integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
   optionalDependencies:
-    esbuild-android-64 "0.14.51"
-    esbuild-android-arm64 "0.14.51"
-    esbuild-darwin-64 "0.14.51"
-    esbuild-darwin-arm64 "0.14.51"
-    esbuild-freebsd-64 "0.14.51"
-    esbuild-freebsd-arm64 "0.14.51"
-    esbuild-linux-32 "0.14.51"
-    esbuild-linux-64 "0.14.51"
-    esbuild-linux-arm "0.14.51"
-    esbuild-linux-arm64 "0.14.51"
-    esbuild-linux-mips64le "0.14.51"
-    esbuild-linux-ppc64le "0.14.51"
-    esbuild-linux-riscv64 "0.14.51"
-    esbuild-linux-s390x "0.14.51"
-    esbuild-netbsd-64 "0.14.51"
-    esbuild-openbsd-64 "0.14.51"
-    esbuild-sunos-64 "0.14.51"
-    esbuild-windows-32 "0.14.51"
-    esbuild-windows-64 "0.14.51"
-    esbuild-windows-arm64 "0.14.51"
+    "@esbuild/linux-loong64" "0.14.54"
+    esbuild-android-64 "0.14.54"
+    esbuild-android-arm64 "0.14.54"
+    esbuild-darwin-64 "0.14.54"
+    esbuild-darwin-arm64 "0.14.54"
+    esbuild-freebsd-64 "0.14.54"
+    esbuild-freebsd-arm64 "0.14.54"
+    esbuild-linux-32 "0.14.54"
+    esbuild-linux-64 "0.14.54"
+    esbuild-linux-arm "0.14.54"
+    esbuild-linux-arm64 "0.14.54"
+    esbuild-linux-mips64le "0.14.54"
+    esbuild-linux-ppc64le "0.14.54"
+    esbuild-linux-riscv64 "0.14.54"
+    esbuild-linux-s390x "0.14.54"
+    esbuild-netbsd-64 "0.14.54"
+    esbuild-openbsd-64 "0.14.54"
+    esbuild-sunos-64 "0.14.54"
+    esbuild-windows-32 "0.14.54"
+    esbuild-windows-64 "0.14.54"
+    esbuild-windows-arm64 "0.14.54"
 
 escalade@^3.1.1:
   version "3.1.1"


Test patch:
diff --git a/resources/sample_vaults/Tasks-Demo/Manual Testing/Callouts and Block Quotes.md b/resources/sample_vaults/Tasks-Demo/Manual Testing/Callouts and Block Quotes.md
new file mode 100644
index 00000000..f326e3dc
--- /dev/null
+++ b/resources/sample_vaults/Tasks-Demo/Manual Testing/Callouts and Block Quotes.md
@@ -0,0 +1,63 @@
+# 1 Callouts and Block Quotes
+
+## 1.1 Query
+
+> [!NOTE] Tasks recognised by Tasks Plugin in this file
+>
+> ```tasks
+> path includes Callouts and Block Quotes
+> group by heading
+> sort by description
+> ```
+
+---
+
+## 1.2 Vanilla
+
+- [ ] #task Task 1 Vanilla
+- [ ] #task Task 2 Vanilla
+
+## 1.3 Callout
+
+> [!NOTE]
+>
+> - [ ] #task Task 1 Callout
+> - [ ] #task Task 2 Callout
+
+### 1.3.1 Callout containing Blockquote
+
+> [!NOTE]
+> >
+> > - [ ] #task Task 1 Callout containing Blockquote
+> > - [ ] #task Task 2 Callout containing Blockquote
+>
+
+## 1.4 Blockquote
+
+> - [ ] #task Task 1 Blockquote
+> - [ ] #task Task 2 Blockquote
+
+### 1.4.1 Blockquote containing Callout
+
+> > [!NOTE]
+> >
+> > - [ ] #task Task 1 Blockquote containing Callout
+> > - [ ] #task Task 2 Blockquote containing Callout
+
+## 1.5 Numbered task in unordered list
+
+- [ ] #task 1. Task 1 Numbered task in unordered list
+- [ ] #task 2. Task 2 Numbered task in unordered list
+
+## 1.6 Not currently treated as a task by Tasks
+
+x - [ ] #task  wibble
+
+```text
+- [ ] #task Task 1 Vanilla
+```
+
+### 1.6.1 Task in numbered list
+
+1. [ ] #task Task 1 Task in numbered list
+2. [ ] #task Task 2 Task in numbered list
diff --git a/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md b/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
index 078d3d71..f4584087 100644
--- a/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
+++ b/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
@@ -63,12 +63,17 @@ Work through all the tasks below, until zero tasks remain in this query:
 
 ### Recurring Tasks
 
-Confirm that when a recurring task is completed, a new task is created and all the date fields are incremented.
+Confirm that when a recurring task is completed, a new task is created, all the date fields are incremented, and the indentation is unchanged.
+
+> [!Todo]
+>
+> - [ ] #task Complete this recurring task in **Source view** using **Tasks: Toggle task done** command 🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
+>
+> > - [ ] #task Complete this recurring task in **Reading view**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
 
-- [ ] #task Complete this recurring task in **Source view** using **Tasks: Toggle task done** command 🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
-- [ ] #task Complete this recurring task in **Reading view**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
 - [ ] #task Complete this recurring task in **Live Preview**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
-- [ ] #task **check**: Checked all above steps for **recurring tasks** worked
+
+> - [ ] #task **check**: Checked all above steps for **recurring tasks** worked
 
 ### Rendering of Task Blocks
 
diff --git a/tests/Query/Filter/DescriptionField.test.ts b/tests/Query/Filter/DescriptionField.test.ts
index a2986895..b00d8f49 100644
--- a/tests/Query/Filter/DescriptionField.test.ts
+++ b/tests/Query/Filter/DescriptionField.test.ts
@@ -15,6 +15,48 @@ function testDescriptionFilter(
     testTaskFilter(filter, task, expected);
 }
 
+declare global {
+    namespace jest {
+        interface Matchers<R> {
+            toMatchTaskWithDescription(description: string): R;
+        }
+
+        interface Expect {
+            toMatchTaskWithDescription(description: string): any;
+        }
+
+        interface InverseAsymmetricMatchers {
+            toMatchTaskWithDescription(description: string): any;
+        }
+    }
+}
+
+export function toMatchTaskWithDescription(
+    filter: FilterOrErrorMessage,
+    description: string,
+) {
+    const task = fromLine({
+        line: description,
+    });
+
+    const matches = filter.filter!(task);
+    if (!matches) {
+        return {
+            message: () => `unexpected failure to match task: ${description}`,
+            pass: false,
+        };
+    }
+
+    return {
+        message: () => `filter should not have matched task: ${description}`,
+        pass: true,
+    };
+}
+
+expect.extend({
+    toMatchTaskWithDescription,
+});
+
 describe('description', () => {
     it('ignores the global filter when filtering', () => {
         // Arrange
@@ -63,4 +105,34 @@ describe('description', () => {
         // Cleanup
         updateSettings(originalSettings);
     });
+
+    it('works with regex', () => {
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            'description regex matches /^task/',
+        );
+
+        // Assert
+        expect(filter).not.toMatchTaskWithDescription(
+            '- [ ] this does not start with the pattern',
+        );
+        expect(filter).toMatchTaskWithDescription(
+            '- [ ] task does start with the pattern',
+        );
+    });
+
+    it('works negating regexes', () => {
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            'description regex does not match /^task/',
+        );
+
+        // Assert
+        expect(filter).toMatchTaskWithDescription(
+            '- [ ] this does not start with the pattern',
+        );
+        expect(filter).not.toMatchTaskWithDescription(
+            '- [ ] task does start with the pattern',
+        );
+    });
 });
diff --git a/tests/Query/Filter/ExcludeSubItemsField.test.ts b/tests/Query/Filter/ExcludeSubItemsField.test.ts
index 3029f767..c12df033 100644
--- a/tests/Query/Filter/ExcludeSubItemsField.test.ts
+++ b/tests/Query/Filter/ExcludeSubItemsField.test.ts
@@ -14,4 +14,20 @@ describe('sub-items', () => {
         testTaskFilter(filter, fromLine({ line: '  - [ ] Subtask1' }), false);
         testTaskFilter(filter, fromLine({ line: '    - [ ] Subtask2' }), false);
     });
+    it('subitem has more than one space after last > of blockquotes or callouts', () => {
+        // Arrange
+        const filter = new ExcludeSubItemsField().createFilterOrErrorMessage(
+            'exclude sub-items',
+        );
+
+        // Assert
+        testTaskFilter(filter, fromLine({ line: '> - [ ] Task' }), true);
+        testTaskFilter(filter, fromLine({ line: '> > - [ ] Task' }), true);
+        testTaskFilter(filter, fromLine({ line: '>>  - [ ] Subtask1' }), false);
+        testTaskFilter(
+            filter,
+            fromLine({ line: '> >  - [ ] Subtask2' }),
+            false,
+        );
+    });
 });
diff --git a/tests/Query/Filter/PathField.test.ts b/tests/Query/Filter/PathField.test.ts
index 8734a88a..4e69120e 100644
--- a/tests/Query/Filter/PathField.test.ts
+++ b/tests/Query/Filter/PathField.test.ts
@@ -12,6 +12,77 @@ function testTaskFilterForTaskWithPath(
     testFilter(filter, builder.path(path), expected);
 }
 
+declare global {
+    namespace jest {
+        interface Matchers<R> {
+            toMatchTaskWithPath(path: string): R;
+            toBeValid(): R;
+        }
+
+        interface Expect {
+            toMatchTaskWithPath(path: string): any;
+            toBeValid(): any;
+        }
+
+        interface InverseAsymmetricMatchers {
+            toMatchTaskWithPath(path: string): any;
+            toBeValid(): any;
+        }
+    }
+}
+
+export function toBeValid(filter: FilterOrErrorMessage) {
+    if (filter.filter === undefined) {
+        return {
+            message: () =>
+                'unexpected null filter: check your instruction matches your filter class',
+            pass: false,
+        };
+    }
+
+    if (filter.error !== undefined) {
+        return {
+            message: () =>
+                'unexpected error message in filter: check your instruction matches your filter class',
+            pass: false,
+        };
+    }
+
+    return {
+        message: () => 'filter is unexpectedly valid',
+        pass: true,
+    };
+}
+
+export function toMatchTaskWithPath(
+    filter: FilterOrErrorMessage,
+    path: string,
+) {
+    const builder = new TaskBuilder();
+    const task = builder.path(path).build();
+
+    const matches = filter.filter!(task);
+    if (!matches) {
+        return {
+            message: () => `unexpected failure to match task: ${path}`,
+            pass: false,
+        };
+    }
+
+    return {
+        message: () => `filter should not have matched task: ${path}`,
+        pass: true,
+    };
+}
+
+expect.extend({
+    toMatchTaskWithPath,
+});
+
+expect.extend({
+    toBeValid,
+});
+
 describe('path', () => {
     it('by path (includes)', () => {
         // Arrange
@@ -37,4 +108,49 @@ describe('path', () => {
         testTaskFilterForTaskWithPath(filter, '/some/path/file.md', false);
         testTaskFilterForTaskWithPath(filter, '/other/path/file.md', true);
     });
+
+    it('by path (regex matches)', () => {
+        // Arrange
+        const filter = new PathField().createFilterOrErrorMessage(
+            'path regex matches /w.bble/',
+        );
+
+        // Assert
+        expect(filter).toBeValid();
+        expect(filter).toMatchTaskWithPath('/some/path/wibble.md');
+        expect(filter).toMatchTaskWithPath('/some/path/wobble.md');
+        expect(filter).not.toMatchTaskWithPath('');
+        expect(filter).not.toMatchTaskWithPath('/some/path/WobblE.md'); // confirm case-sensitive
+        expect(filter).not.toMatchTaskWithPath('/other/path/file.md');
+    });
+
+    it('by path (regex matches) with flags', () => {
+        // Arrange
+        const filter = new PathField().createFilterOrErrorMessage(
+            'path regex matches /w.bble/i',
+        );
+
+        // Assert
+        expect(filter).toBeValid();
+        expect(filter).toMatchTaskWithPath('/some/path/wibble.md');
+        expect(filter).toMatchTaskWithPath('/some/path/wobble.md');
+        expect(filter).not.toMatchTaskWithPath('');
+        expect(filter).toMatchTaskWithPath('/some/path/WobblE.md'); // confirm case-insensitive (flag)
+        expect(filter).not.toMatchTaskWithPath('/other/path/file.md');
+    });
+
+    it('by path (regex does not match)', () => {
+        // Arrange
+        const filter = new PathField().createFilterOrErrorMessage(
+            'path regex does not match /w.bble/',
+        );
+
+        // Assert
+        expect(filter).toBeValid();
+        expect(filter).not.toMatchTaskWithPath('/some/path/wibble.md');
+        expect(filter).not.toMatchTaskWithPath('/some/path/wobble.md');
+        expect(filter).toMatchTaskWithPath('');
+        expect(filter).toMatchTaskWithPath('/some/path/WobblE.md'); // confirm case-sensitive
+        expect(filter).toMatchTaskWithPath('/other/path/file.md');
+    });
 });
diff --git a/tests/Task.test.ts b/tests/Task.test.ts
index dcaa78a8..9dad92ed 100644
--- a/tests/Task.test.ts
+++ b/tests/Task.test.ts
@@ -194,6 +194,35 @@ describe('parsing', () => {
             });
         }
     });
+
+    it('supports parsing of tasks inside blockquotes or callouts', () => {
+        // Arrange
+        const lines = [
+            '> - [ ] Task inside a blockquote or callout 📅2022-07-29',
+            '>>> - [ ] Task inside a blockquote or callout 📅2022-07-29',
+            '> > > * [ ] Task inside a blockquote or callout 📅2022-07-29',
+        ];
+
+        // Act
+        for (const line of lines) {
+            const task = fromLine({
+                line,
+            });
+
+            // Assert
+            expect({
+                _input: line, // Line is included, so it is shown in any failure output
+                description: task.description,
+                due: task.dueDate?.format('YYYY-MM-DD'),
+                indentation: task.indentation,
+            }).toMatchObject({
+                _input: line,
+                description: 'Task inside a blockquote or callout',
+                due: '2022-07-29',
+                indentation: line.split(/[-*]/)[0],
+            });
+        }
+    });
 });
 
 type TagParsingExpectations = {
@@ -295,7 +324,7 @@ describe('parsing tags', () => {
         },
         {
             markdownTask:
-                '- [ ] Export [Cloud Feedly feeds](https://cloud.feedly.com/#opml) #context/pc_clare 🔁 every 4 weeks on Sunday ⏳ 2022-05-15 #context/more_context',
+                '* [ ] Export [Cloud Feedly feeds](https://cloud.feedly.com/#opml) #context/pc_clare 🔁 every 4 weeks on Sunday ⏳ 2022-05-15 #context/more_context',
             expectedDescription:
                 'Export [Cloud Feedly feeds](https://cloud.feedly.com/#opml) #context/pc_clare #context/more_context',
             extractedTags: ['#context/pc_clare', '#context/more_context'],
@@ -317,6 +346,20 @@ describe('parsing tags', () => {
             extractedTags: ['#context/pc_clare'],
             globalFilter: '',
         },
+        {
+            markdownTask: '> - [ ] Task inside a blockquote or callout #tagone',
+            expectedDescription: 'Task inside a blockquote or callout #tagone',
+            extractedTags: ['#tagone'],
+            globalFilter: '',
+        },
+        {
+            markdownTask:
+                '>>> * [ ] Task inside a nested blockquote or callout #tagone',
+            expectedDescription:
+                'Task inside a nested blockquote or callout #tagone',
+            extractedTags: ['#tagone'],
+            globalFilter: '',
+        },
     ])(
         'should parse "$markdownTask" and extract "$extractedTags"',
         ({
@@ -348,6 +391,18 @@ describe('parsing tags', () => {
 });
 
 describe('to string', () => {
+    it('retains the indentation', () => {
+        const line = '> > > - [ ] Task inside a nested blockquote or callout';
+
+        // Act
+        const task: Task = fromLine({
+            line,
+        }) as Task;
+
+        // Assert
+        expect(task).not.toBeNull();
+        expect(task.toFileLineString()).toStrictEqual(line);
+    });
     it('retains the block link', () => {
         // Arrange
         const line = '- [ ] this is a task 📅 2021-09-12 ^my-precious';