├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ └── issue-template.md ├── .gitignore ├── .prettierrc.js ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs └── img │ ├── example-task-management.gif │ └── settings-add-remove-validate.gif ├── esbuild.config.mjs ├── manifest.json ├── package.json ├── release.sh ├── src ├── ApplyPattern.ts ├── FilterPatterns.ts ├── ParseDateRange.ts ├── PatternsModal.ts ├── Settings.guard.ts ├── Settings.ts ├── SettingsTab.ts ├── ValidateRuleString.ts └── main.ts ├── styles.css ├── tsconfig.json ├── versions.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | main.js 2 | npm node_modules 3 | build 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "parserOptions": { 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-vars": "off", 17 | "@typescript-eslint/no-unused-vars": [ 18 | "error", 19 | { 20 | "args": "none" 21 | } 22 | ], 23 | "@typescript-eslint/ban-ts-comment": "off", 24 | "no-prototype-builtins": "off", 25 | "@typescript-eslint/no-empty-function": "off" 26 | } 27 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue Template 3 | about: Use this template to create a new issue. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Issue tracker is **ONLY** used for reporting bugs. New feature ideas should be discussed in the [ideas section](https://github.com/publicus/obsidian-apply-patterns-plugin/discussions/categories/ideas). Please use the [Q&A section](https://github.com/publicus/obsidian-apply-patterns-plugin/discussions/categories/q-a) for supporting issues. Please use the search function. 11 | 12 | If you encountered the issue after you installed, updated, or reloaded the Apply Patterns plugin, please try restarting obsidian before reporting the bug. 13 | 14 | If you want to report a bug, please follow the guide lines below to help me resolve it. 15 | 16 | ## Expected Behavior 17 | 18 | 19 | ## Current Behavior 20 | 21 | 22 | ## Steps to Reproduce 23 | 24 | 25 | ## Context (Environment) 26 | * Obsidian version: 27 | * Apply Patterns version: 28 | * [ ] I have tried it with all other plugins disabled and the error still occurs 29 | 30 | ## Possible Solution 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | yarn-error.log 8 | 9 | # build 10 | main.js 11 | *.js.map 12 | 13 | # obsidian 14 | data.json 15 | 16 | # osx 17 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | singleQuote: true, 4 | tabWidth: 4, 5 | }; 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | j@adunumdatum.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jacob Levernier 4 | Copyright (c) 2021 Martin Schenck 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Obsidian Apply Patterns plugin

2 | 3 |

Apply collections of regular-expression find/replace rules on text in Obsidian notes.

4 | 5 | This plugin allows creating collections of find-and-replace "rules," and applying them to selections or whole lines of a document from a searchable menu. 6 | 7 | **Supports creating search and replace patterns of natural language dates, including ranges of dates.** 8 | 9 | > Please submit bugs here: https://github.com/jglev/obsidian-apply-patterns-plugin 10 | > 11 | > Please submit ideas here: https://github.com/jglev/obsidian-apply-patterns-plugin/discussions/categories/ideas 12 | > 13 | > Please ask for help here: https://github.com/jglev/obsidian-apply-patterns-plugin/discussions/categories/q-a 14 | 15 | --- 16 | 17 | 21 | 22 | For changes in each release, please check the releases page: https://github.com/jglev/obsidian-apply-patterns-plugin/releases 23 | 24 | --- 25 | 26 | ## Screenshots 27 | 28 | - *The theme is [Solarized Light](https://github.com/Slowbad/obsidian-solarized).* 29 | - *The theme has been enhanced for task management using [this CSS snippet](https://gist.github.com/jglev/30f289deb911cc8f8645c946e42f13a6). See [here](https://help.obsidian.md/Advanced+topics/Customizing+CSS) for instructions on incorporating CSS snippets in Obsidian.* 30 | 31 | ![Example Usage for Task management](docs/img/example-task-management.gif) 32 | 33 | Two "Patterns" have been defined, each with one "rule." These settings can be imported into the plugin by copying this JSON to the clipboard and clicking the "Import from Clipboard" button in the plugin's settings tab within Obsidian: 34 | 35 |
36 | 37 | ```json 38 | [ 39 | { 40 | "name": "Set open tasks as complete", 41 | "done": false, 42 | "rules": [ 43 | { 44 | "from": "- \\[ \\] #(?:TODO|SCHEDULED)(.*?)\\(Due by \\[\\[(\\d{4}-\\d{2}-\\d{2})\\]\\]\\)", 45 | "to": "- [X] #DONE$1(Completed on [[{{date:today}}]])", 46 | "caseInsensitive": true, 47 | "global": false, 48 | "sticky": false, 49 | "multiline": false, 50 | "disabled": false 51 | } 52 | ] 53 | }, 54 | { 55 | "name": "Reschedule closed task", 56 | "done": false, 57 | "rules": [ 58 | { 59 | "from": "- \\[[Xx]\\] #DONE(.*?)\\(Completed on (.*?)\\)", 60 | "to": "- [ ] #TODO$1(Due by [[{{date:two weeks from today}}]])", 61 | "caseInsensitive": false, 62 | "global": false, 63 | "multiline": false, 64 | "sticky": false 65 | } 66 | ] 67 | } 68 | ] 69 | ``` 70 |
71 | 72 | 73 | The first Pattern, "Set open tasks as complete," will affect lines that have an open checkbox and a `#TODO` or `#SCHEDULED` hashtag, as well as the phrase "(Due by...)" with a date. **It replaces a match with a closed checkbox, a `#DONE` hashtag, the original body of the line, and today's date.** 74 | 75 | The second Pattern, "Reschedule closed task," will affect lines that have a closed checkbox (`-[x]`) and a `#DONE` hashtag, as well as the phrase "(Completed on...)". **It replaces a match with an open checkbox, a `#TODO` hashtag, and a "Due by" date that is two weeks in the future.** 76 | 77 | ![Add, move, delete, and validate rules](docs/img/settings-add-remove-validate.gif) 78 | 79 | The plugin's Settings tab allows creating and removing Patterns, as well as importing and exporting patterns using the system's clipboard. Rules can be moved up and down within Patterns, and validated as correct in their `{{date}}` strings and Regular Expression syntax. Each rule can also have [Regular Expression modes](https://www.regular-expressions.info/refmodifiers.html) set. 80 | 81 | ## Installation 82 | 83 | Follow the steps below to install the plugin. 84 | 85 | 1. Search for "Apply Patterns" in Obsidian's community plugins browser 86 | 2. Enable the plugin in your Obsidian settings (find "Apply Patterns" under "Community plugins"). 87 | 3. Check the "Apply Patterns" settings tab. Add one or more patterns. 88 | 4. (Optional) In the "Hotkeys" settings tab, add a hotkey for one or both of the "Apply Patterns..." commands. 89 | 90 | ## Usage 91 | 92 | - This plugin uses the [ECMAScript / Javascript flavor](https://www.regular-expressions.info/javascript.html) of Regular Expressions. 93 | - Within a Pattern, rules execute sequentially. Thus, the output of Rule 1 is passed as input to Rule 2, and the output of Rule 2 is passed as input to Rule 3, etc. At the end of the set of rules, the final output is used to replace the text in the editor. 94 | - The plugin provides five commands by default: 95 | - `Apply Patterns: Apply pattern to whole lines` will loop over each line that is selected in the editor, and apply the Pattern to the entirety of each line. 96 | - - `Apply Patterns: Apply pattern to whole document` will apply the Pattern to the entire document, as one (potentially multi-line) string. 97 | - `Apply Patterns: Apply pattern to selection` will apply the Pattern to just the text selected in the editor, as one (potentially multi-line) string. 98 | - `Apply Patterns: Apply pattern to whole clipboard` will apply the Pattern as with "`Apply pattern to whole document`" to the clipboard. 99 | - `Apply Patterns: Apply pattern to clipboard (line-by-line)` will apply the Pattern as with "`Apply pattern to whole lines`" to the clipboard. 100 | - In addition, you can set additional commands in the Settings tab. 101 | - Within the Settings tab: 102 | - Each rule can be disabled, moved up, and moved down in the pattern. 103 | - Clicking the information icon for a rule will open a Notice indicating whether the rule's `From` and `To` elements are valid. 104 | - Both the `From` and `To` text boxes can use `$1`, `$2`, etc. to refer to [capture groups](https://www.regular-expressions.info/refcapture.html) from the `From` box. 105 | - Both the `From` and `To` text boxes understand natural language dates (see below). 106 | - Additional commands can be created from collections of Patterns. If a command only matches one Pattern, it will apply that Pattern when the command is run. If the command matches more than one Pattern, it will ask which Pattern to apply. 107 | - Custom commands can be created to run on whole lines, the current selection, or the whole document. 108 | 109 | ### Dates 110 | 111 | Within the `From` and `To` text boxes in a rule's settings, one can express dates in natural language, using the following format: 112 | `{{date:start|end|format (default "YYYY-MM-DD")|separator (default "|")}}` 113 | 114 | - `start`, `end`, `format`, and `separator` are all optional. `{{date}}` by itself will default to the current day in `YYYY-MM-DD` format. 115 | - `format` can be any [format from DayJS](https://day.js.org/docs/en/parse/string-format#list-of-all-available-parsing-tokens). 116 | - `start` and `end` are both parsed using [`chrono-node`](https://github.com/wanasit/chrono). 117 | - Thus, the following formats are all valid: 118 | 119 | | Date syntax | Output, if processed on 2021-08-03 | 120 | | --------------------------------- | ---------------------------------- | 121 | | {{date}} | 2021-08-03 | 122 | | {{date:today\|\|YYYY-MM}} | 2021-08 | 123 | | {{date:tomorrow}} | 2021-08-04 | 124 | | {{date:two weeks from today}} | 2021-08-17 | 125 | | {{date:today\|two days from now}} | 2021-08-03\|2021-08-04\|2021-08-05 | 126 | | {{date:today\|tomorrow\|DD}} |03\|04| 127 | | {{date:today\|two days from now\|\|, }} |2021-08-03, 2021-08-04, 2021-08-05| 128 | 129 | This approach to expressing dates allows for powerfully searching for date ranges using Regular Expressions, or creating ranges of formatted dates in the output of a Pattern. For example, `[[{{date:today|tomorrow||]], [[}}]]` in the `To` text box of a rule will create the string "\[\[2021-08-03\]\], \[\[2021-08-04\]\]". 130 | 131 | ## Development 132 | Clone the repository, run `yarn` to install the dependencies, and run `yarn dev` to compile the plugin and watch file changes. 133 | 134 | ## License 135 | 136 | This plugin's code and documentation setup is based off of the [Obsidian Tasks](https://github.com/schemar/obsidian-tasks) plugin by [Martin Schenck](https://github.com/schemar). Like that plugin, this plugin is released under the [MIT license](./LICENSE). 137 | 138 | # Todo 139 | 140 | Automated tests are not currently included in this code for this repository. Assistance in this, particularly using the [Obsidian End-to-End testing approach](https://github.com/trashhalo/obsidian-plugin-e2e-test), is especially welcome! 141 | -------------------------------------------------------------------------------- /docs/img/example-task-management.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jglev/obsidian-apply-patterns-plugin/44fb55cec2deaf8026c1ebacd737d4a5abee682d/docs/img/example-task-management.gif -------------------------------------------------------------------------------- /docs/img/settings-add-remove-validate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jglev/obsidian-apply-patterns-plugin/44fb55cec2deaf8026c1ebacd737d4a5abee682d/docs/img/settings-add-remove-validate.gif -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['src/main.ts'], 19 | bundle: true, 20 | external: ['obsidian', 'electron', ...builtins], 21 | format: 'cjs', 22 | watch: !prod, 23 | target: 'es2016', 24 | logLevel: "info", 25 | sourcemap: prod ? false : 'inline', 26 | treeShaking: true, 27 | outfile: 'main.js', 28 | }).catch(() => process.exit(1)); 29 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-apply-patterns", 3 | "name": "Apply Patterns", 4 | "version": "2.1.2", 5 | "minAppVersion": "1.0.0", 6 | "description": "Apply custom patterns of find-and-replace in succession to text.", 7 | "author": "Jacob Levernier", 8 | "authorUrl": "https://github.com/publicus", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-apply-patterns", 3 | "version": "2.1.2", 4 | "description": "An Obsidian plugin for applying patterns of find and replace in succession.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "yarn generate-typeguard-functions && node esbuild.config.mjs", 8 | "build": "yarn generate-typeguard-functions && node esbuild.config.mjs production", 9 | "generate-typeguard-functions": "rm -f ./src/Settings.guard.ts && ts-auto-guard ./src/Settings.ts && sed -i 's/^import {/import type {/' ./src/Settings.guard.ts" 10 | }, 11 | "keywords": [ 12 | "obsidian", 13 | "obsidian-plugin", 14 | "obsidian-apply-patterns", 15 | "regex", 16 | "find-and-replace" 17 | ], 18 | "author": "Jacob Levernier", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@types/lodash.clonedeep": "^4.5.6", 22 | "@types/node": "^14.17.6", 23 | "@typescript-eslint/eslint-plugin": "^5.2.0", 24 | "@typescript-eslint/parser": "^5.2.0", 25 | "builtin-modules": "^3.2.0", 26 | "esbuild": "0.13.12", 27 | "moment": "^2.29.1", 28 | "obsidian": "^0.12.2", 29 | "prettier": "^2.2.1", 30 | "ts-auto-guard": "^1.0.0-alpha.26", 31 | "tslib": "^2.0.3", 32 | "typescript": "^4.2.4" 33 | }, 34 | "dependencies": { 35 | "chrono-node": "^2.3.4", 36 | "lodash.clonedeep": "^4.5.0", 37 | "rrule": "^2.6.8", 38 | "to-regex-range": "^5.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ "$#" -ne 2 ]; then 6 | echo "Must provide exactly two arguments." 7 | echo "First one must be the new version number." 8 | echo "Second one must be the minimum obsidian version for this release." 9 | echo "" 10 | echo "Example usage:" 11 | echo "./release.sh 0.3.0 0.11.13" 12 | echo "Exiting." 13 | 14 | exit 1 15 | fi 16 | 17 | if [[ $(git status --porcelain) ]]; then 18 | echo "Changes in the git repo." 19 | echo "Exiting." 20 | 21 | exit 1 22 | fi 23 | 24 | NEW_VERSION=$1 25 | MINIMUM_OBSIDIAN_VERSION=$2 26 | 27 | echo "Updating to version ${NEW_VERSION} with minimum obsidian version ${MINIMUM_OBSIDIAN_VERSION}" 28 | 29 | read -p "Continue? [y/N] " -n 1 -r 30 | echo 31 | if [[ $REPLY =~ ^[Yy]$ ]] 32 | then 33 | echo "Updating package.json" 34 | TEMP_FILE=$(mktemp) 35 | jq ".version |= \"${NEW_VERSION}\"" package.json > "$TEMP_FILE" || exit 1 36 | mv "$TEMP_FILE" package.json 37 | 38 | echo "Updating manifest.json" 39 | TEMP_FILE=$(mktemp) 40 | jq ".version |= \"${NEW_VERSION}\" | .minAppVersion |= \"${MINIMUM_OBSIDIAN_VERSION}\"" manifest.json > "$TEMP_FILE" || exit 1 41 | mv "$TEMP_FILE" manifest.json 42 | 43 | echo "Updating versions.json" 44 | TEMP_FILE=$(mktemp) 45 | jq ". += {\"${NEW_VERSION}\": \"${MINIMUM_OBSIDIAN_VERSION}\"}" versions.json > "$TEMP_FILE" || exit 1 46 | mv "$TEMP_FILE" versions.json 47 | 48 | read -p "Create git commit, tag, and push? [y/N] " -n 1 -r 49 | echo 50 | if [[ $REPLY =~ ^[Yy]$ ]] 51 | then 52 | 53 | git add -A . 54 | git commit -m"Update to version ${NEW_VERSION}" 55 | git tag "${NEW_VERSION}" 56 | git push 57 | git push --tags 58 | fi 59 | else 60 | echo "Exiting." 61 | exit 1 62 | fi 63 | -------------------------------------------------------------------------------- /src/ApplyPattern.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | Editor, 4 | EditorTransaction, 5 | EditorRangeOrCaret, 6 | MarkdownView, 7 | Notice, 8 | View, 9 | } from 'obsidian'; 10 | 11 | import { validateRuleString } from './ValidateRuleString'; 12 | import { PatternModal } from './PatternsModal'; 13 | import { Command, getSettings } from './Settings'; 14 | 15 | const calculateCursorPoints = ( 16 | minLine: number, 17 | lines: Array, 18 | cursorStartRegex: { valid: boolean | null; string: string }, 19 | cursorEndRegex: { valid: boolean | null; string: string }, 20 | offsetStart?: number, 21 | ): { 22 | from: { line: number; ch: number }; 23 | to?: { line: number; ch: number }; 24 | } => { 25 | let cursorStart = { line: minLine, ch: 0 }; 26 | let cursorEnd = { line: minLine, ch: 0 }; 27 | 28 | let cursorStartMatch = lines 29 | .join('\n') 30 | .match(new RegExp(cursorStartRegex.string)); 31 | 32 | let cursorEndMatch = lines 33 | .join('\n') 34 | .match(new RegExp(cursorEndRegex.string)); 35 | 36 | if (cursorStartMatch === null && cursorEndMatch !== null) { 37 | cursorStartMatch = cursorEndMatch; 38 | } else if (cursorEndMatch === null && cursorStartMatch !== null) { 39 | cursorEndMatch = cursorStartMatch; 40 | } 41 | 42 | if (cursorStartMatch !== null) { 43 | const beforeCursorMatch = lines 44 | .join('\n') 45 | .slice(0, cursorStartMatch.index) 46 | .split('\n'); 47 | cursorStart = { 48 | line: minLine + beforeCursorMatch.length - 1, 49 | ch: 50 | (offsetStart || 0) + 51 | beforeCursorMatch[beforeCursorMatch.length - 1].length, 52 | }; 53 | } 54 | 55 | if (cursorEndMatch !== null) { 56 | const beforeCursorMatch = lines 57 | .join('\n') 58 | .slice(0, cursorEndMatch.index) 59 | .split('\n'); 60 | cursorEnd = { 61 | line: minLine + beforeCursorMatch.length - 1, 62 | ch: beforeCursorMatch[beforeCursorMatch.length - 1].length, 63 | }; 64 | } 65 | 66 | const output: EditorRangeOrCaret = { from: cursorStart }; 67 | 68 | if ( 69 | cursorStart.line !== cursorEnd.line || 70 | cursorStart.ch !== cursorEnd.ch 71 | ) { 72 | output.to = cursorEnd; 73 | } 74 | 75 | return output; 76 | }; 77 | 78 | export const applyPattern = ( 79 | editor: Editor, 80 | view: View, 81 | app: App, 82 | mode: 83 | | 'lines' 84 | | 'selection' 85 | | 'document' 86 | | 'clipboard' 87 | | 'clipboardLines' = 'lines', 88 | command?: Command, 89 | ) => { 90 | if (!(view instanceof MarkdownView)) { 91 | return; 92 | } 93 | 94 | // We are certain we are in the editor due to the check above. 95 | const path = view.file?.path; 96 | if (path === undefined) { 97 | return; 98 | } 99 | 100 | const onChooseItem = (patternIndex: number): void => { 101 | const pattern = getSettings().patterns[patternIndex]; 102 | 103 | // Confirm that each rule's strings are valid: 104 | let allValid = true; 105 | const allRuleStringsValidated: { from: string; to: string }[] = []; 106 | 107 | const noticeTimeoutSeconds = 1000 * 30; // 30 seconds 108 | 109 | for (let ruleIndex = 0; ruleIndex < pattern.rules.length; ruleIndex++) { 110 | const rule = pattern.rules[ruleIndex]; 111 | if (rule.disabled === true) { 112 | // Push a placeholder, to avoid messing up index numbers below: 113 | allRuleStringsValidated.push({ 114 | from: '', 115 | to: '', 116 | }); 117 | continue; 118 | } 119 | const fromValidated = validateRuleString(rule.from); 120 | const toValidated = validateRuleString(rule.to, false); 121 | allRuleStringsValidated.push({ 122 | from: fromValidated.string, 123 | to: toValidated.string, 124 | }); 125 | 126 | if (!fromValidated.valid) { 127 | new Notice( 128 | `Error: "${rule.from}" is not valid: "${fromValidated.string}". Stopping before editing the text.`, 129 | noticeTimeoutSeconds, 130 | ); 131 | allValid = false; 132 | } 133 | if (toValidated.valid === false) { 134 | new Notice( 135 | `Error: "${rule.to}" is not valid: "${toValidated.string}". Stopping before editing the text.`, 136 | noticeTimeoutSeconds, 137 | ); 138 | allValid = false; 139 | } 140 | } 141 | 142 | const cursorStartRegex = validateRuleString(pattern.cursorRegexStart); 143 | const cursorEndRegex = validateRuleString(pattern.cursorRegexEnd); 144 | 145 | if (cursorStartRegex.valid !== true) { 146 | new Notice( 147 | `Error: "${pattern.cursorRegexStart}" is not valid: "${cursorStartRegex.string}". Stopping before editing the text.`, 148 | noticeTimeoutSeconds, 149 | ); 150 | allValid = false; 151 | } 152 | 153 | if (cursorEndRegex.valid !== true) { 154 | new Notice( 155 | `Error: "${pattern.cursorRegexEnd}" is not valid: "${cursorEndRegex.string}". Stopping before editing the text.`, 156 | noticeTimeoutSeconds, 157 | ); 158 | allValid = false; 159 | } 160 | 161 | if (allValid !== true) { 162 | return; // Stop the function prematurely 163 | } 164 | 165 | if (mode === 'clipboard' || mode === 'clipboardLines') { 166 | // This is largely the same as the 'document' mode code, but using 167 | // the clipboard as input. 168 | navigator.clipboard.readText().then((clipboardText) => { 169 | if (mode === 'clipboard') { 170 | pattern.rules.forEach((rule, ruleIndex) => { 171 | clipboardText = clipboardText.replace( 172 | new RegExp( 173 | allRuleStringsValidated[ruleIndex].from, 174 | `u${rule.caseInsensitive ? 'i' : ''}${ 175 | rule.global ? 'g' : '' 176 | }${rule.multiline ? 'm' : ''}${ 177 | rule.sticky ? 's' : '' 178 | }`, 179 | ), 180 | allRuleStringsValidated[ruleIndex].to, 181 | ); 182 | }); 183 | } 184 | if (mode === 'clipboardLines') { 185 | const clipboardTextSplit = clipboardText.split('\n'); 186 | const updatedLines: string[] = []; 187 | for ( 188 | let lineNumber = 0; 189 | lineNumber < clipboardTextSplit.length; 190 | lineNumber++ 191 | ) { 192 | let line = clipboardTextSplit[lineNumber]; 193 | pattern.rules.forEach((rule, ruleIndex) => { 194 | if (rule.disabled === true) { 195 | // Skip the rule if it's disabled: 196 | return; 197 | } 198 | line = line.replace( 199 | new RegExp( 200 | allRuleStringsValidated[ruleIndex].from, 201 | `u${rule.caseInsensitive ? 'i' : ''}${ 202 | rule.global ? 'g' : '' 203 | }${rule.multiline ? 'm' : ''}${ 204 | rule.sticky ? 's' : '' 205 | }`, 206 | ), 207 | allRuleStringsValidated[ruleIndex].to, 208 | ); 209 | }); 210 | updatedLines.push(line); 211 | } 212 | clipboardText = updatedLines.join('\n'); 213 | } 214 | 215 | navigator.clipboard.writeText(clipboardText); 216 | 217 | new Notice('Clipboard updated.'); 218 | }); 219 | return; 220 | } 221 | 222 | const cursorFrom = editor.getCursor('from'); 223 | const cursorTo = editor.getCursor('to'); 224 | const minLine = cursorFrom.line; 225 | const maxLine = cursorTo.line; 226 | 227 | const transaction: EditorTransaction = { 228 | changes: [], 229 | }; 230 | 231 | let finalCursorPositions: EditorRangeOrCaret; 232 | 233 | if (mode === 'lines') { 234 | const updatedLines: string[] = []; 235 | for ( 236 | let lineNumber = minLine; 237 | lineNumber <= maxLine; 238 | lineNumber++ 239 | ) { 240 | let line = editor.getLine(lineNumber); 241 | pattern.rules.forEach((rule, ruleIndex) => { 242 | if (rule.disabled === true) { 243 | // Skip the rule if it's disabled: 244 | return; 245 | } 246 | line = line.replace( 247 | new RegExp( 248 | allRuleStringsValidated[ruleIndex].from, 249 | `u${rule.caseInsensitive ? 'i' : ''}${ 250 | rule.global ? 'g' : '' 251 | }${rule.multiline ? 'm' : ''}${ 252 | rule.sticky ? 's' : '' 253 | }`, 254 | ), 255 | allRuleStringsValidated[ruleIndex].to, 256 | ); 257 | }); 258 | updatedLines.push(line); 259 | } 260 | transaction.changes?.push({ 261 | from: { line: minLine, ch: 0 }, 262 | to: { 263 | line: maxLine, 264 | ch: editor.getLine(maxLine).length, 265 | }, 266 | text: updatedLines.join('\n'), 267 | }); 268 | 269 | finalCursorPositions = calculateCursorPoints( 270 | minLine, 271 | updatedLines, 272 | cursorStartRegex, 273 | cursorEndRegex, 274 | ); 275 | } 276 | 277 | if (mode === 'selection') { 278 | let updatedSelection = editor.getSelection(); 279 | pattern.rules.forEach((rule, ruleIndex) => { 280 | updatedSelection = updatedSelection.replace( 281 | new RegExp( 282 | allRuleStringsValidated[ruleIndex].from, 283 | `u${rule.caseInsensitive ? 'i' : ''}${ 284 | rule.global ? 'g' : '' 285 | }${rule.multiline ? 'm' : ''}${rule.sticky ? 's' : ''}`, 286 | ), 287 | allRuleStringsValidated[ruleIndex].to, 288 | ); 289 | }); 290 | transaction.replaceSelection = updatedSelection; 291 | 292 | const newContentSplit = updatedSelection.split('\n'); 293 | 294 | finalCursorPositions = calculateCursorPoints( 295 | minLine, 296 | newContentSplit, 297 | cursorStartRegex, 298 | cursorEndRegex, 299 | cursorFrom.ch, 300 | ); 301 | 302 | if (finalCursorPositions.to) { 303 | finalCursorPositions.to = { 304 | ...finalCursorPositions.to, 305 | ch: 306 | finalCursorPositions.to.ch + 307 | (cursorFrom.line === cursorTo.line ? cursorFrom.ch : 0), 308 | }; 309 | } 310 | } 311 | 312 | if (mode === 'document') { 313 | const editorLineCount = editor.lineCount(); 314 | const fullDocumentSelector = { 315 | from: { line: 0, ch: 0 }, 316 | to: { 317 | line: editorLineCount - 1, 318 | ch: editor.getLine(editorLineCount - 1).length, 319 | }, 320 | }; 321 | let updatedDocument = editor.getRange( 322 | fullDocumentSelector.from, 323 | fullDocumentSelector.to, 324 | ); 325 | pattern.rules.forEach((rule, ruleIndex) => { 326 | updatedDocument = updatedDocument.replace( 327 | new RegExp( 328 | allRuleStringsValidated[ruleIndex].from, 329 | `u${rule.caseInsensitive ? 'i' : ''}${ 330 | rule.global ? 'g' : '' 331 | }${rule.multiline ? 'm' : ''}${rule.sticky ? 's' : ''}`, 332 | ), 333 | allRuleStringsValidated[ruleIndex].to, 334 | ); 335 | }); 336 | transaction.changes?.push({ 337 | ...fullDocumentSelector, 338 | text: updatedDocument, 339 | }); 340 | const newContentSplit = updatedDocument.split('\n'); 341 | 342 | finalCursorPositions = calculateCursorPoints( 343 | 0, 344 | newContentSplit, 345 | cursorStartRegex, 346 | cursorEndRegex, 347 | ); 348 | } 349 | 350 | transaction.selection = { 351 | from: finalCursorPositions.from, 352 | to: finalCursorPositions.to, 353 | }; 354 | 355 | editor.transaction(transaction); 356 | }; 357 | 358 | // Need to create a new instance every time, as cursor can change. 359 | const patternModal = new PatternModal({ 360 | app, 361 | onChooseItem, 362 | command, 363 | }); 364 | patternModal.open(); 365 | }; 366 | -------------------------------------------------------------------------------- /src/FilterPatterns.ts: -------------------------------------------------------------------------------- 1 | import { Notice } from 'obsidian'; 2 | 3 | import { 4 | Command, 5 | Pattern, 6 | PatternRule, 7 | formatUnnamedPattern, 8 | getSettings, 9 | } from './Settings'; 10 | 11 | export const filterPatterns = (command?: Command): number[] => { 12 | const patterns = getSettings().patterns; 13 | 14 | let patternIndexes: number[] = getSettings() 15 | .patterns 16 | .map((pattern: Pattern, patternIndex: number) => { 17 | return {pattern, patternIndex} 18 | }) 19 | .filter( 20 | (p: {pattern: Pattern, patternIndex: number}) => 21 | p.pattern.rules.length > 0 && 22 | p.pattern.rules.every((rule: PatternRule) => rule.from !== '') 23 | ) 24 | .map((p: {pattern: Pattern, patternIndex: number}) => p.patternIndex ); 25 | 26 | if (command !== undefined && command.patternFilter !== '') { 27 | try { 28 | const patternNameFilterRegex = new RegExp(command.patternFilter); 29 | 30 | patternIndexes = patternIndexes.filter((patternIndex: number) => { 31 | const pattern: Pattern = patterns[patternIndex]; 32 | 33 | if ( 34 | patternNameFilterRegex.test( 35 | pattern.name || formatUnnamedPattern(patternIndex), 36 | ) === true 37 | ) { 38 | return true; 39 | } 40 | return false; 41 | }); 42 | } catch (e) { 43 | new Notice( 44 | `Error using pattern filter "${command.patternFilter}" pattern settings from clipboard. See developer console for more information.`, 45 | ); 46 | console.log(e); 47 | return []; 48 | } 49 | } 50 | return patternIndexes; 51 | }; 52 | 53 | export const formatPatternName = (patternIndex: number): string => { 54 | return ( 55 | getSettings().patterns[patternIndex].name || 56 | formatUnnamedPattern(patternIndex) 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/ParseDateRange.ts: -------------------------------------------------------------------------------- 1 | import * as chrono from 'chrono-node'; 2 | import dayjs from 'dayjs'; 3 | import customParseFormat from 'dayjs/plugin/customParseFormat'; 4 | dayjs.extend(customParseFormat); 5 | 6 | export const ruleDateRegexString = 7 | /* Match the following formats: 8 | '{{date:today|tomorrow|YYYY-MM}}' 9 | '{{date:today|tomorrow}}' 10 | '{{date:today||YYYY-MM}}' 11 | '{{date:today||YYYY-MM|\|}}' 12 | '{{date:tomorrow}}' 13 | '{{date}}' 14 | */ 15 | // Note that the curly brackets are escaped here because this regex pattern 16 | // is meant to be used with 'u' / full Unicode support mode flag set: 17 | '\\{\\{date(?::(.*?)(?:(?:\\|(.*?)(?:\\|(.*?)(?:\\|(.*?))?)?)?)?)?\\}\\}'; 18 | 19 | export const parseDateRange = ( 20 | // E.g., '{{date:last month on the 7th|today|YYYY-MM-DD}}' 21 | dateString: string, 22 | ) => { 23 | const parsedSearchString = dateString.match( 24 | new RegExp(ruleDateRegexString, 'u'), 25 | ); 26 | 27 | if (parsedSearchString === null) { 28 | return; 29 | } 30 | 31 | const dateFormat = parsedSearchString[3] || 'YYYY-MM-DD'; 32 | const joinWith = parsedSearchString[4] || '|'; 33 | // This was previously wrapped in a try/catch block, but is no longer, in 34 | // order to allow the calling function to handle errors. 35 | const usedStartString = 36 | parsedSearchString[1] !== undefined ? parsedSearchString[1] : 'today'; 37 | const start = dayjs(chrono.parseDate(usedStartString)); 38 | let end = null; 39 | 40 | if (!start.isValid()) { 41 | throw `Start date string "${usedStartString}" did not evaluate to a valid date.`; 42 | } 43 | 44 | if ( 45 | parsedSearchString[2] !== undefined && 46 | parsedSearchString[2] !== null && 47 | parsedSearchString[2] !== '' 48 | ) { 49 | end = dayjs(chrono.parseDate(parsedSearchString[2])); 50 | 51 | if (!end.isValid()) { 52 | throw `End date string "${parsedSearchString[2]}" did not evaluate to a valid date.`; 53 | } 54 | } else { 55 | return start.format(dateFormat); 56 | } 57 | 58 | // Get dates between start and end: 59 | const datesBetween = []; 60 | 61 | for ( 62 | let d = dayjs(start); 63 | d.isBefore(end.add(1, 'minute')); 64 | d = d.add(1, 'day') 65 | ) { 66 | datesBetween.push(dayjs(d).format(dateFormat)); 67 | } 68 | 69 | return datesBetween.join(joinWith); 70 | }; 71 | 72 | export default parseDateRange; 73 | -------------------------------------------------------------------------------- /src/PatternsModal.ts: -------------------------------------------------------------------------------- 1 | import { App, FuzzySuggestModal } from 'obsidian'; 2 | import { filterPatterns, formatPatternName } from './FilterPatterns'; 3 | import type { Command } from './Settings'; 4 | 5 | export class PatternModal extends FuzzySuggestModal { 6 | public readonly onChooseItem: (patternIndex: number) => void; 7 | public readonly command?: Command; 8 | 9 | constructor({ 10 | app, 11 | onChooseItem, 12 | command, 13 | }: { 14 | app: App; 15 | onChooseItem: (patternIndex: number) => void; 16 | command?: Command; 17 | }) { 18 | super(app); 19 | this.command = command; 20 | if (this.command !== undefined) { 21 | this.setInstructions([ 22 | { 23 | command: 24 | command?.name || 25 | `Unnamed Apply Patterns Command (${command?.patternFilter})`, 26 | purpose: '', 27 | }, 28 | ]); 29 | } 30 | 31 | this.onChooseItem = (patternIndex: number) => { 32 | onChooseItem(patternIndex); 33 | // Note: Using this.close() here was causing a bug whereby new 34 | // text was unable to be typed until the user had opened another 35 | // modal or switched away from the window. @lishid noted at 36 | // https://github.com/obsidianmd/obsidian-releases/pull/396#issuecomment-894017526 37 | // that the modal is automatically closed at the conclusion of 38 | // onChooseItem. 39 | }; 40 | } 41 | 42 | getItems(): number[] { 43 | const patternIndexes = filterPatterns(this.command); 44 | // If there is only one pattern, run it without offering a selection: 45 | if (this.command !== undefined && patternIndexes.length === 1) { 46 | this.close(); 47 | this.onChooseItem(patternIndexes[0]); 48 | } 49 | return patternIndexes; 50 | } 51 | 52 | getItemText(patternIndex: number): string { 53 | return formatPatternName(patternIndex); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Settings.guard.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated type guards for "Settings.ts". 3 | * WARNING: Do not manually change this file. 4 | */ 5 | import type { Settings, Pattern, PatternRule, Command } from "./Settings"; 6 | 7 | export function isSettings(obj: any, _argumentName?: string): obj is Settings { 8 | return ( 9 | (obj !== null && 10 | typeof obj === "object" || 11 | typeof obj === "function") && 12 | Array.isArray(obj.patterns) && 13 | obj.patterns.every((e: any) => 14 | isPattern(e) as boolean 15 | ) && 16 | typeof obj.filterString === "string" && 17 | typeof obj.commandFilterString === "string" && 18 | Array.isArray(obj.commands) && 19 | obj.commands.every((e: any) => 20 | isCommand(e) as boolean 21 | ) && 22 | typeof obj.defaultCursorRegexStart === "string" && 23 | typeof obj.defaultCursorRegexEnd === "string" && 24 | typeof obj.apiVersion === "number" 25 | ) 26 | } 27 | 28 | export function isPattern(obj: any, _argumentName?: string): obj is Pattern { 29 | return ( 30 | (obj !== null && 31 | typeof obj === "object" || 32 | typeof obj === "function") && 33 | typeof obj.name === "string" && 34 | Array.isArray(obj.rules) && 35 | obj.rules.every((e: any) => 36 | isPatternRule(e) as boolean 37 | ) && 38 | typeof obj.collapsed === "boolean" && 39 | typeof obj.cursorRegexStart === "string" && 40 | typeof obj.cursorRegexEnd === "string" 41 | ) 42 | } 43 | 44 | export function isPatternRule(obj: any, _argumentName?: string): obj is PatternRule { 45 | return ( 46 | (obj !== null && 47 | typeof obj === "object" || 48 | typeof obj === "function") && 49 | typeof obj.from === "string" && 50 | typeof obj.to === "string" && 51 | typeof obj.caseInsensitive === "boolean" && 52 | typeof obj.global === "boolean" && 53 | typeof obj.multiline === "boolean" && 54 | typeof obj.sticky === "boolean" && 55 | typeof obj.disabled === "boolean" 56 | ) 57 | } 58 | 59 | export function isCommand(obj: any, _argumentName?: string): obj is Command { 60 | return ( 61 | (obj !== null && 62 | typeof obj === "object" || 63 | typeof obj === "function") && 64 | typeof obj.name === "string" && 65 | typeof obj.icon === "string" && 66 | typeof obj.patternFilter === "string" && 67 | typeof obj.selection === "boolean" && 68 | typeof obj.lines === "boolean" && 69 | typeof obj.document === "boolean" && 70 | typeof obj.clipboard === "boolean" && 71 | typeof obj.clipboardLines === "boolean" 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /src/Settings.ts: -------------------------------------------------------------------------------- 1 | import cloneDeep from 'lodash.clonedeep'; 2 | import * as Guards from './Settings.guard'; 3 | 4 | // Typeguard functions for the interfaces below (to enable running, e.g., 5 | // isPattern() on a JSON object). When the interfaces below change, these guard 6 | // functions can be regenerated with 7 | // 'yarn generate-typeguard-functions' (see package.json). 8 | export { Guards }; 9 | 10 | /** @see {isSettings} ts-auto-guard:type-guard */ 11 | export interface Settings { 12 | patterns: Pattern[]; 13 | filterString?: string; 14 | commandFilterString?: string; 15 | commands: Command[]; 16 | defaultCursorRegexStart: string; 17 | defaultCursorRegexEnd: string; 18 | apiVersion?: number; 19 | } 20 | 21 | /** @see {isPattern} ts-auto-guard:type-guard */ 22 | export interface Pattern { 23 | name: string; 24 | rules: PatternRule[]; 25 | collapsed: boolean; 26 | cursorRegexStart: string; 27 | cursorRegexEnd: string; 28 | } 29 | 30 | export const defaultPatternSettings: Pattern = { 31 | name: '', 32 | rules: [], 33 | collapsed: false, 34 | cursorRegexStart: '^', 35 | cursorRegexEnd: '$', 36 | }; 37 | 38 | /** @see {isPatternRule} ts-auto-guard:type-guard */ 39 | export interface PatternRule { 40 | from: string; 41 | to: string; 42 | caseInsensitive: boolean; 43 | global: boolean; 44 | multiline: boolean; 45 | sticky: boolean; 46 | disabled?: boolean; 47 | } 48 | 49 | export const defaultPatternRuleSettings: PatternRule = { 50 | from: '', 51 | to: '', 52 | caseInsensitive: false, 53 | global: false, 54 | multiline: false, 55 | sticky: false, 56 | disabled: false, 57 | }; 58 | 59 | export const defaultSettings: Settings = { 60 | patterns: [], 61 | filterString: '', 62 | commandFilterString: '', 63 | commands: [], 64 | apiVersion: 6, 65 | defaultCursorRegexEnd: '^', 66 | defaultCursorRegexStart: '$', 67 | }; 68 | 69 | /** @see {isCommand} ts-auto-guard:type-guard */ 70 | export interface Command { 71 | name: string; 72 | icon?: string; 73 | patternFilter: string; 74 | selection?: boolean; 75 | lines?: boolean; 76 | document?: boolean; 77 | clipboard?: boolean; 78 | clipboardLines?: boolean; 79 | } 80 | 81 | export const defaultCommandSettings: Command = { 82 | name: '', 83 | icon: 'search', 84 | patternFilter: '', 85 | selection: true, 86 | lines: true, 87 | document: true, 88 | clipboard: false, 89 | clipboardLines: false, 90 | }; 91 | 92 | export const formatUnnamedPattern = (patternIndex: number): string => 93 | `Pattern ${patternIndex + 1} (Untitled)`; 94 | 95 | let settings: Settings = { ...defaultSettings }; 96 | 97 | export const getSettings = (): Settings => { 98 | return { ...cloneDeep(settings) }; 99 | }; 100 | 101 | export const clearSettings = () => { 102 | settings = { ...cloneDeep(defaultSettings) }; 103 | return getSettings(); 104 | }; 105 | 106 | export const updateSettings = (newSettings: Partial): Settings => { 107 | settings = { ...cloneDeep(settings), ...cloneDeep(newSettings) }; 108 | settings.commands.forEach((command: Command) => { 109 | command = { ...defaultCommandSettings, ...command }; 110 | }); 111 | settings.patterns.forEach((pattern: Pattern) => { 112 | pattern = { ...defaultPatternSettings, ...pattern }; 113 | pattern.rules.forEach((rule: PatternRule) => { 114 | rule = { ...defaultPatternRuleSettings, ...rule }; 115 | }); 116 | }); 117 | 118 | return getSettings(); 119 | }; 120 | -------------------------------------------------------------------------------- /src/SettingsTab.ts: -------------------------------------------------------------------------------- 1 | import cloneDeep from 'lodash.clonedeep'; 2 | import { DropdownComponent, Notice, PluginSettingTab, Setting } from 'obsidian'; 3 | import { validateRuleString } from './ValidateRuleString'; 4 | import { 5 | Command, 6 | Guards, 7 | Pattern, 8 | PatternRule, 9 | clearSettings, 10 | defaultCommandSettings, 11 | defaultPatternRuleSettings, 12 | defaultPatternSettings, 13 | defaultSettings, 14 | formatUnnamedPattern, 15 | getSettings, 16 | updateSettings, 17 | } from './Settings'; 18 | import { filterPatterns, formatPatternName } from './FilterPatterns'; 19 | import type ApplyPatterns from './main'; 20 | 21 | const moveInArray = (arr: any[], from: number, to: number) => { 22 | const arrClone = cloneDeep(arr); 23 | const item = arrClone[from]; 24 | arrClone.splice(from, 1); 25 | arrClone.splice(to, 0, item); 26 | return arrClone; 27 | }; 28 | 29 | export class SettingsTab extends PluginSettingTab { 30 | private readonly plugin: ApplyPatterns; 31 | 32 | constructor({ plugin }: { plugin: ApplyPatterns }) { 33 | super(plugin.app, plugin); 34 | 35 | this.plugin = plugin; 36 | } 37 | 38 | public display(): void { 39 | const { containerEl } = this; 40 | 41 | containerEl.empty(); 42 | containerEl.createEl('h1', { text: 'Apply Patterns' }); 43 | 44 | const patternsEl = containerEl.createEl('div'); 45 | patternsEl.addClass('patterns'); 46 | const patternsDescEl = patternsEl.createEl('div', { 47 | cls: 'patterns-description-el', 48 | }); 49 | const fragment = document.createDocumentFragment(); 50 | 51 | patternsDescEl.createEl('p').append( 52 | 'This plugin allows setting "patterns" for editing text by applying sequences of "rules." Each "rule" is a find-and-replace operation, using Regular Expressions. If you are unfamiliar with Regular Expressions, you can learn more about them at ', 53 | fragment.createEl('a', { 54 | href: 'https://regex101.com/', 55 | text: 'Regex101', 56 | }), 57 | ', ', 58 | fragment.createEl('a', { 59 | href: 'https://www.regular-expressions.info/', 60 | text: 'Regular Expressions.info', 61 | }), 62 | ', and elsewhere. This plugin uses the ', 63 | fragment.createEl('a', { 64 | href: 'https://www.regular-expressions.info/javascript.html', 65 | text: 'ECMAScript / Javascript flavor', 66 | }), 67 | ' of Regular Expressions.', 68 | ); 69 | 70 | patternsDescEl.createEl( 71 | 'h4', 72 | 'Tips for the "From" and "To" fields for each rule:', 73 | ); 74 | 75 | const patternsDescToTipsEl = patternsDescEl.createEl('ul'); 76 | 77 | patternsDescToTipsEl.createEl('li').append( 78 | fragment.createEl('a', { 79 | href: 'https://www.regular-expressions.info/brackets.html', 80 | text: 'Capture groups', 81 | }), 82 | ' can be referenced using "$1", "$2", etc.', 83 | ); 84 | patternsDescToTipsEl.createEl('li').append( 85 | 'To ', 86 | fragment.createEl('a', { 87 | href: 'https://www.regular-expressions.info/characters.html', 88 | text: 'escape', 89 | }), 90 | ' special characters, only one backslash is needed.', 91 | ); 92 | 93 | patternsDescToTipsEl.createEl('li').append( 94 | 'Both the "From" and "To" fields for each rule ', 95 | fragment.createEl('span', { 96 | text: 'will understand natural-language dates', 97 | cls: 'bold', 98 | }), 99 | ' presented in the following format: ', 100 | fragment.createEl('code', { 101 | text: '{{date:start|end|format|separator}}', 102 | }), 103 | ', where ', 104 | fragment.createEl('code', { text: 'start' }), 105 | ' and ', 106 | fragment.createEl('code', { text: 'end' }), 107 | ' are both ', 108 | fragment.createEl('a', { 109 | href: 'https://github.com/wanasit/chrono', 110 | text: 'English-language dates', 111 | }), 112 | ', and ', 113 | fragment.createEl('code', { text: 'format' }), 114 | ' is a format from ', 115 | fragment.createEl('a', { 116 | href: 'https://day.js.org/docs/en/parse/string-format#list-of-all-available-parsing-tokens', 117 | text: 'DayJS', 118 | }), 119 | ', and ', 120 | fragment.createEl('code', { text: 'separator' }), 121 | ' is the string that will separate lists of dates if ', 122 | fragment.createEl('code', { text: 'start' }), 123 | ' and ', 124 | fragment.createEl('code', { text: 'end' }), 125 | ' are both set and are set to different days. All fields are optional. Using just ', 126 | fragment.createEl('code', { text: '{{date}}' }), 127 | ' (or ', 128 | fragment.createEl('code', { text: '{{date||YYYY-MM-DD}}' }), 129 | "), will evaluate to today's date in YYYY-MM-DD format.", 130 | ); 131 | 132 | patternsEl.createEl('h2', { text: 'Patterns' }); 133 | patternsEl.createEl('div', { 134 | text: 'Combinations of find-and-replace "rules" that can be applied to highlighted lines of text.', 135 | cls: 'setting-item-description', 136 | }); 137 | 138 | const patternsDefaultsEl = patternsEl.createEl('div', { 139 | cls: 'pattern-defaults', 140 | }); 141 | patternsDefaultsEl.createEl('h3', { text: `Pattern defaults` }); 142 | 143 | const patternsDefaultStartEl = patternsDefaultsEl.createEl('div'); 144 | const patternsDefaultStartSetting = new Setting(patternsDefaultStartEl); 145 | const patternsDefaultStartValidEl = 146 | patternsDefaultStartEl.createEl('span'); 147 | patternsDefaultStartValidEl.addClass('validation-text'); 148 | const patternsDefaultStartValid = validateRuleString( 149 | getSettings().defaultCursorRegexStart || '', 150 | ); 151 | if (patternsDefaultStartValid.valid !== true) { 152 | patternsDefaultStartEl.addClass('invalid'); 153 | patternsDefaultStartValidEl.setText( 154 | patternsDefaultStartValid.string, 155 | ); 156 | } 157 | 158 | patternsDefaultStartSetting 159 | .setName('Post-pattern cursor/selection start (Regex)') 160 | .setDesc( 161 | 'A regular expression to determine the default starting location of the cursor after a Pattern has been applied. The cursor will be placed at the ending location of the first match.', 162 | ) 163 | .addText((text) => { 164 | const settings = getSettings(); 165 | text.setValue(settings.defaultCursorRegexStart || '').onChange( 166 | async (value) => { 167 | updateSettings({ 168 | ...cloneDeep(getSettings()), 169 | defaultCursorRegexStart: value, 170 | }); 171 | 172 | await this.plugin.saveSettings(); 173 | 174 | const valueValid = validateRuleString(value); 175 | if (valueValid.valid === true) { 176 | patternsDefaultStartEl.removeClass('invalid'); 177 | patternsDefaultStartValidEl.setText(''); 178 | } else { 179 | patternsDefaultStartEl.addClass('invalid'); 180 | patternsDefaultStartValidEl.setText( 181 | valueValid.string, 182 | ); 183 | } 184 | }, 185 | ); 186 | }); 187 | 188 | const patternsDefaultEndEl = patternsDefaultsEl.createEl('div'); 189 | const patternsDefaultEndSetting = new Setting(patternsDefaultEndEl); 190 | const patternsDefaultEndValidEl = patternsDefaultEndEl.createEl('span'); 191 | patternsDefaultEndValidEl.addClass('validation-text'); 192 | const patternsDefaultEndValid = validateRuleString( 193 | getSettings().defaultCursorRegexEnd || '', 194 | ); 195 | if (patternsDefaultEndValid.valid !== true) { 196 | patternsDefaultEndEl.addClass('invalid'); 197 | patternsDefaultEndValidEl.setText(patternsDefaultEndValid.string); 198 | } 199 | 200 | patternsDefaultEndSetting 201 | .setName('Post-pattern cursor/selection end (Regex)') 202 | .setDesc( 203 | 'A regular expression to determine the default ending location of the cursor after the Pattern has been applied. The cursor will be placed at the ending location of the first match.', 204 | ) 205 | .addText((text) => { 206 | const settings = getSettings(); 207 | text.setValue(settings.defaultCursorRegexEnd || '').onChange( 208 | async (value) => { 209 | updateSettings({ 210 | ...cloneDeep(getSettings()), 211 | defaultCursorRegexEnd: value, 212 | }); 213 | 214 | await this.plugin.saveSettings(); 215 | 216 | const valueValid = validateRuleString(value); 217 | if (valueValid.valid === true) { 218 | patternsDefaultEndEl.removeClass('invalid'); 219 | patternsDefaultEndValidEl.setText(''); 220 | } else { 221 | patternsDefaultEndEl.addClass('invalid'); 222 | patternsDefaultEndValidEl.setText( 223 | valueValid.string, 224 | ); 225 | } 226 | }, 227 | ); 228 | }); 229 | 230 | const patternsFilterEl = patternsEl.createEl('div', { 231 | cls: 'pattern-filters', 232 | }); 233 | patternsFilterEl.createEl('h3', { text: `Filter patterns` }); 234 | 235 | new Setting(patternsFilterEl) 236 | .setName('Filter patterns by name') 237 | .addText((text) => { 238 | const settings = getSettings(); 239 | text.setValue(settings.filterString || '').onChange( 240 | async (value) => { 241 | updateSettings({ 242 | ...cloneDeep(getSettings()), 243 | filterString: value, 244 | }); 245 | 246 | await this.plugin.saveSettings(); 247 | }, 248 | ); 249 | }); 250 | 251 | new Setting(patternsFilterEl) 252 | .setName('Apply filter') 253 | .addButton((button) => { 254 | button 255 | .setIcon('magnifying-glass') 256 | .setTooltip('Filter Patterns') 257 | .onClick(async () => { 258 | this.display(); 259 | }); 260 | }); 261 | 262 | new Setting(patternsFilterEl) 263 | .setName('Expand / Collapse all patterns') 264 | .addExtraButton((button) => { 265 | const settings = getSettings(); 266 | const patternFilterString = settings.filterString; 267 | const patterns = settings.patterns; 268 | 269 | const visiblePatterns = patterns 270 | .map((pattern: Pattern, patternIndex) => { 271 | if ( 272 | patternFilterString !== undefined && 273 | patternFilterString !== '' 274 | ) { 275 | if ( 276 | pattern.name 277 | .toLowerCase() 278 | .includes(patternFilterString.toLowerCase()) 279 | ) { 280 | return { 281 | index: patternIndex, 282 | collapsed: pattern.collapsed === true, 283 | pattern, 284 | }; 285 | } 286 | } else { 287 | return { 288 | index: patternIndex, 289 | collapsed: pattern.collapsed === true, 290 | pattern, 291 | }; 292 | } 293 | return null; 294 | }) 295 | .filter((e) => e !== null); 296 | 297 | const collapsedStatus = visiblePatterns.map( 298 | (e) => e !== null && e.collapsed === true, 299 | ); 300 | 301 | button 302 | .setIcon('expand-vertically') 303 | .setTooltip( 304 | collapsedStatus.every((e) => e === true) 305 | ? 'Expand all' 306 | : 'Collapse all', 307 | ) 308 | .onClick(async () => { 309 | const settings = getSettings(); 310 | const patternFilterString = settings.filterString; 311 | const patterns = settings.patterns; 312 | 313 | const visiblePatterns = patterns 314 | .map((pattern: Pattern, patternIndex) => { 315 | if ( 316 | patternFilterString !== undefined && 317 | patternFilterString !== '' 318 | ) { 319 | if ( 320 | pattern.name 321 | .toLowerCase() 322 | .includes( 323 | patternFilterString.toLowerCase(), 324 | ) 325 | ) { 326 | return { 327 | index: patternIndex, 328 | collapsed: 329 | pattern.collapsed === true, 330 | pattern, 331 | }; 332 | } 333 | } else { 334 | return { 335 | index: patternIndex, 336 | collapsed: pattern.collapsed === true, 337 | pattern, 338 | }; 339 | } 340 | return null; 341 | }) 342 | .filter((e) => e !== null); 343 | 344 | const collapsedStatus = visiblePatterns.map( 345 | (e) => e !== null && e.collapsed === true, 346 | ); 347 | 348 | if (collapsedStatus.every((e) => e === true)) { 349 | for (const visiblePattern of visiblePatterns) { 350 | if (visiblePattern !== null) { 351 | settings.patterns[ 352 | visiblePattern.index 353 | ].collapsed = false; 354 | } 355 | } 356 | } else if (collapsedStatus.some((e) => e === true)) { 357 | for (const visiblePattern of visiblePatterns) { 358 | if (visiblePattern !== null) { 359 | settings.patterns[ 360 | visiblePattern.index 361 | ].collapsed = true; 362 | } 363 | } 364 | } else { 365 | for (const visiblePattern of visiblePatterns) { 366 | if (visiblePattern !== null) { 367 | settings.patterns[ 368 | visiblePattern.index 369 | ].collapsed = true; 370 | } 371 | } 372 | } 373 | 374 | updateSettings({ 375 | patterns: settings.patterns, 376 | }); 377 | await this.plugin.saveSettings(); 378 | this.display(); 379 | }); 380 | }); 381 | 382 | const patterns = getSettings().patterns; 383 | for (const [patternIndex, pattern] of patterns.entries()) { 384 | const settings = getSettings(); 385 | const patternFilterString = settings.filterString; 386 | if ( 387 | patternFilterString !== undefined && 388 | patternFilterString !== '' 389 | ) { 390 | if ( 391 | !pattern.name 392 | .toLowerCase() 393 | .includes(patternFilterString.toLowerCase()) 394 | ) { 395 | continue; 396 | } 397 | } 398 | 399 | const patternEl = patternsEl.createEl('div'); 400 | patternEl.addClass('pattern'); 401 | 402 | patternEl.createEl('h3', { text: `Pattern ${patternIndex + 1}` }); 403 | 404 | new Setting(patternEl).setName('Pattern name').addText((text) => { 405 | text.setPlaceholder('') 406 | .setValue(pattern.name) 407 | .onChange(async (value) => { 408 | const newPatterns = cloneDeep(getSettings().patterns); 409 | newPatterns.splice(patternIndex, 1, { 410 | ...patterns[patternIndex], 411 | name: value, 412 | }); 413 | updateSettings({ 414 | patterns: newPatterns, 415 | }); 416 | 417 | await this.plugin.saveSettings(); 418 | }); 419 | }); 420 | 421 | let deletePatternPrimed = false; 422 | let patternDeletePrimerTimer: ReturnType | null; 423 | 424 | new Setting(patternEl) 425 | .setName('Pattern meta controls') 426 | .addExtraButton((button) => { 427 | button 428 | .setIcon('up-chevron-glyph') 429 | .setTooltip('Move Pattern up') 430 | .setDisabled(patternIndex === 0) 431 | .onClick(async () => { 432 | let newPatterns = cloneDeep(getSettings().patterns); 433 | newPatterns = moveInArray( 434 | newPatterns, 435 | patternIndex, 436 | patternIndex - 1, 437 | ); 438 | updateSettings({ 439 | patterns: newPatterns, 440 | }); 441 | 442 | await this.plugin.saveSettings(); 443 | this.display(); 444 | }); 445 | }) 446 | .addExtraButton((button) => { 447 | button 448 | .setIcon('down-chevron-glyph') 449 | .setTooltip('Move Pattern down') 450 | .setDisabled(patternIndex === patterns.length - 1) 451 | .onClick(async () => { 452 | let newPatterns = cloneDeep(getSettings().patterns); 453 | newPatterns = moveInArray( 454 | newPatterns, 455 | patternIndex, 456 | patternIndex + 1, 457 | ); 458 | updateSettings({ 459 | patterns: newPatterns, 460 | }); 461 | 462 | await this.plugin.saveSettings(); 463 | this.display(); 464 | }); 465 | }) 466 | .addExtraButton((button) => { 467 | button 468 | .setIcon('cross-in-box') 469 | .setTooltip('Delete pattern') 470 | .onClick(async () => { 471 | if (patternDeletePrimerTimer) { 472 | clearTimeout(patternDeletePrimerTimer); 473 | } 474 | if (deletePatternPrimed === true) { 475 | const newPatterns = cloneDeep( 476 | getSettings().patterns, 477 | ); 478 | newPatterns.splice(patternIndex, 1); 479 | updateSettings({ 480 | patterns: newPatterns, 481 | }); 482 | 483 | await this.plugin.saveSettings(); 484 | this.display(); 485 | return; 486 | } 487 | 488 | patternDeletePrimerTimer = setTimeout( 489 | () => { 490 | deletePatternPrimed = false; 491 | patternEl.removeClass('primed'); 492 | }, 493 | 1000 * 4, // 4 second timeout 494 | ); 495 | deletePatternPrimed = true; 496 | patternEl.addClass('primed'); 497 | 498 | new Notice( 499 | `Click again to delete Pattern ${ 500 | patternIndex + 1 501 | }`, 502 | ); 503 | }); 504 | }) 505 | .addExtraButton((button) => { 506 | const updatedSettings = 507 | getSettings().patterns[patternIndex]; 508 | button 509 | .setIcon('expand-vertically') 510 | .setTooltip( 511 | updatedSettings.collapsed !== false 512 | ? 'Expand' 513 | : 'Collapse', 514 | ) 515 | .onClick(async () => { 516 | const newPatterns = cloneDeep( 517 | getSettings().patterns, 518 | ); 519 | newPatterns[patternIndex].collapsed = 520 | !newPatterns[patternIndex].collapsed; 521 | updateSettings({ 522 | patterns: newPatterns, 523 | }); 524 | 525 | await this.plugin.saveSettings(); 526 | this.display(); 527 | }); 528 | }); 529 | 530 | const patternRulesEl = patternEl.createEl('div'); 531 | patternRulesEl.addClass('pattern-rules'); 532 | 533 | const rulesEl = patternRulesEl.createEl('div'); 534 | rulesEl.addClass('rules'); 535 | 536 | if (getSettings().patterns[patternIndex].collapsed === true) { 537 | patternEl.addClass('collapsed'); 538 | } else { 539 | patternEl.removeClass('collapsed'); 540 | } 541 | 542 | pattern.rules.forEach((rule: PatternRule, ruleIndex) => { 543 | const ruleEl = rulesEl.createEl('div'); 544 | ruleEl.addClass('rule'); 545 | if (rule.disabled === true) { 546 | ruleEl.addClass('disabled'); 547 | } 548 | 549 | ruleEl.createEl('h4', { text: `Rule ${ruleIndex + 1}` }); 550 | 551 | new Setting(ruleEl) 552 | .setName('Toggle rule') 553 | .addButton((button) => { 554 | button 555 | .setIcon( 556 | rule.disabled === true ? 'broken-link' : 'link', 557 | ) 558 | .setTooltip( 559 | rule.disabled === true ? 'Enable' : 'Disable', 560 | ) 561 | .onClick(async () => { 562 | const newPatterns = cloneDeep( 563 | getSettings().patterns, 564 | ); 565 | newPatterns[patternIndex].rules[ 566 | ruleIndex 567 | ].disabled = 568 | newPatterns[patternIndex].rules[ruleIndex] 569 | .disabled === true 570 | ? false 571 | : true; 572 | updateSettings({ 573 | patterns: newPatterns, 574 | }); 575 | await this.plugin.saveSettings(); 576 | this.display(); 577 | }); 578 | }); 579 | 580 | const ruleFromEl = ruleEl.createEl('div'); 581 | const ruleFromElSetting = new Setting(ruleFromEl); 582 | const ruleFromValidEl = ruleFromEl.createEl('span'); 583 | ruleFromValidEl.addClass('validation-text'); 584 | const ruleFromValid = validateRuleString(rule.from); 585 | if (ruleFromValid.valid !== true) { 586 | ruleFromEl.addClass('invalid'); 587 | ruleFromValidEl.setText(ruleFromValid.string); 588 | } 589 | 590 | ruleFromElSetting 591 | .setName('Matching text (Regex)') 592 | .addText((text) => { 593 | text.setPlaceholder('') 594 | .setValue(rule.from) 595 | .onChange(async (value) => { 596 | const newPatterns = cloneDeep( 597 | getSettings().patterns, 598 | ); 599 | newPatterns[patternIndex].rules.splice( 600 | ruleIndex, 601 | 1, 602 | { 603 | ...newPatterns[patternIndex].rules[ 604 | ruleIndex 605 | ], 606 | from: value || '', 607 | }, 608 | ); 609 | updateSettings({ 610 | patterns: newPatterns, 611 | }); 612 | 613 | await this.plugin.saveSettings(); 614 | 615 | const valueValid = validateRuleString(value); 616 | if (valueValid.valid === true) { 617 | ruleFromEl.removeClass('invalid'); 618 | ruleFromValidEl.setText(''); 619 | } else { 620 | ruleFromEl.addClass('invalid'); 621 | ruleFromValidEl.setText(valueValid.string); 622 | } 623 | }); 624 | }); 625 | 626 | new Setting(ruleEl) 627 | .setName('Case-insensitive') 628 | .setDesc('Regex mode "i"') 629 | .addToggle((toggle) => { 630 | toggle 631 | .setTooltip('Case-insensitive') 632 | .setValue(rule.caseInsensitive) 633 | .onChange(async (value) => { 634 | const newPatterns = cloneDeep( 635 | getSettings().patterns, 636 | ); 637 | newPatterns[patternIndex].rules[ 638 | ruleIndex 639 | ].caseInsensitive = value; 640 | updateSettings({ 641 | patterns: newPatterns, 642 | }); 643 | 644 | await this.plugin.saveSettings(); 645 | }); 646 | }); 647 | 648 | new Setting(ruleEl) 649 | .setName('Global') 650 | .setDesc('Regex mode "g"') 651 | .addToggle((toggle) => { 652 | toggle 653 | .setTooltip('Global') 654 | .setValue(rule.global) 655 | .onChange(async (value) => { 656 | const newPatterns = cloneDeep( 657 | getSettings().patterns, 658 | ); 659 | newPatterns[patternIndex].rules[ 660 | ruleIndex 661 | ].global = value; 662 | updateSettings({ 663 | patterns: newPatterns, 664 | }); 665 | 666 | await this.plugin.saveSettings(); 667 | }); 668 | }); 669 | 670 | new Setting(ruleEl) 671 | .setName('Multiline') 672 | .setDesc('Regex mode "m"') 673 | .addToggle((toggle) => { 674 | toggle 675 | .setTooltip('Multiline') 676 | .setValue(rule.multiline) 677 | .onChange(async (value) => { 678 | const newPatterns = cloneDeep( 679 | getSettings().patterns, 680 | ); 681 | newPatterns[patternIndex].rules[ 682 | ruleIndex 683 | ].multiline = value; 684 | updateSettings({ 685 | patterns: newPatterns, 686 | }); 687 | 688 | await this.plugin.saveSettings(); 689 | }); 690 | }); 691 | new Setting(ruleEl) 692 | .setName('Sticky') 693 | .setDesc('Regex mode "s"') 694 | .addToggle((toggle) => { 695 | toggle 696 | .setTooltip('Sticky') 697 | .setValue(rule.sticky) 698 | .onChange(async (value) => { 699 | const newPatterns = cloneDeep( 700 | getSettings().patterns, 701 | ); 702 | newPatterns[patternIndex].rules[ 703 | ruleIndex 704 | ].sticky = value; 705 | updateSettings({ 706 | patterns: newPatterns, 707 | }); 708 | 709 | await this.plugin.saveSettings(); 710 | }); 711 | }); 712 | 713 | const ruleToEl = ruleEl.createEl('div'); 714 | const ruleToElSetting = new Setting(ruleToEl); 715 | const ruleToValidEl = ruleFromEl.createEl('span'); 716 | ruleToValidEl.addClass('validation-text'); 717 | const ruleToValid = validateRuleString(rule.to, false); 718 | 719 | if (ruleToValid.valid !== null) { 720 | ruleToEl.addClass('invalid'); 721 | ruleToValidEl.setText(ruleToValid.string); 722 | } 723 | 724 | ruleToElSetting.setName('Replacement text').addText((text) => { 725 | text.setPlaceholder('') 726 | .setValue(rule.to) 727 | .onChange(async (value) => { 728 | const newPatterns = cloneDeep( 729 | getSettings().patterns, 730 | ); 731 | newPatterns[patternIndex].rules.splice( 732 | ruleIndex, 733 | 1, 734 | { 735 | ...newPatterns[patternIndex].rules[ 736 | ruleIndex 737 | ], 738 | to: value || '', 739 | }, 740 | ); 741 | updateSettings({ 742 | patterns: newPatterns, 743 | }); 744 | 745 | await this.plugin.saveSettings(); 746 | 747 | const valueValid = validateRuleString(value, false); 748 | 749 | if (valueValid.valid === null) { 750 | ruleToEl.removeClass('invalid'); 751 | ruleToValidEl.setText(''); 752 | } else { 753 | ruleToEl.addClass('invalid'); 754 | ruleToValidEl.setText(valueValid.string); 755 | } 756 | }); 757 | }); 758 | 759 | let deleteRulePrimed = false; 760 | let ruleDeletePrimerTimer: ReturnType | null; 761 | 762 | new Setting(ruleEl) 763 | .setName('Rule meta controls') 764 | .addExtraButton((button) => { 765 | button 766 | .setIcon('info') 767 | .setTooltip('View compiled From ⇨ To') 768 | .onClick(async () => { 769 | const updatedRule = 770 | getSettings().patterns[patternIndex].rules[ 771 | ruleIndex 772 | ]; 773 | 774 | const fromValidated = validateRuleString( 775 | updatedRule.from, 776 | ); 777 | const toValidated = validateRuleString( 778 | updatedRule.to, 779 | false, 780 | ); 781 | 782 | const noticeTimeoutSeconds = 1000 * 30; // 30 seconds 783 | if (!fromValidated.valid) { 784 | new Notice( 785 | `"From" pattern is invalid: ${fromValidated.string}`, 786 | noticeTimeoutSeconds, 787 | ); 788 | return; 789 | } 790 | 791 | if (toValidated.valid === false) { 792 | new Notice( 793 | `"To" pattern is invalid: ${toValidated.string}`, 794 | noticeTimeoutSeconds, 795 | ); 796 | return; 797 | } 798 | 799 | new Notice( 800 | new RegExp( 801 | fromValidated.string, 802 | 'u' + 803 | (updatedRule.caseInsensitive 804 | ? 'i' 805 | : '') + 806 | (updatedRule.global ? 'g' : '') + 807 | (updatedRule.multiline ? 'm' : '') + 808 | (updatedRule.sticky ? 's' : ''), 809 | ).toString() + 810 | '\n⇩\n' + 811 | (toValidated.string !== '' 812 | ? '"' + toValidated.string + '"' 813 | : '[Remove]'), 814 | noticeTimeoutSeconds, 815 | ); 816 | }); 817 | }) 818 | .addExtraButton((button) => { 819 | button 820 | .setIcon('up-chevron-glyph') 821 | .setTooltip('Move Rule up') 822 | .setDisabled(ruleIndex === 0) 823 | .onClick(async () => { 824 | const newPatterns = cloneDeep( 825 | getSettings().patterns, 826 | ); 827 | newPatterns[patternIndex].rules = moveInArray( 828 | newPatterns[patternIndex].rules, 829 | ruleIndex, 830 | ruleIndex - 1, 831 | ); 832 | updateSettings({ 833 | patterns: newPatterns, 834 | }); 835 | 836 | await this.plugin.saveSettings(); 837 | this.display(); 838 | }); 839 | }) 840 | .addExtraButton((button) => { 841 | button 842 | .setIcon('down-chevron-glyph') 843 | .setTooltip('Move Rule down') 844 | .setDisabled(ruleIndex === pattern.rules.length - 1) 845 | .onClick(async () => { 846 | const newPatterns = cloneDeep( 847 | getSettings().patterns, 848 | ); 849 | newPatterns[patternIndex].rules = moveInArray( 850 | newPatterns[patternIndex].rules, 851 | ruleIndex, 852 | ruleIndex + 1, 853 | ); 854 | updateSettings({ 855 | patterns: newPatterns, 856 | }); 857 | 858 | await this.plugin.saveSettings(); 859 | this.display(); 860 | }); 861 | }) 862 | .addExtraButton((button) => { 863 | button 864 | .setIcon('cross-in-box') 865 | .setTooltip('Delete rule') 866 | .onClick(async () => { 867 | if (ruleDeletePrimerTimer) { 868 | clearTimeout(ruleDeletePrimerTimer); 869 | } 870 | if (deleteRulePrimed === true) { 871 | const newPatterns = cloneDeep( 872 | getSettings().patterns, 873 | ); 874 | newPatterns[patternIndex].rules.splice( 875 | ruleIndex, 876 | 1, 877 | ); 878 | updateSettings({ 879 | patterns: newPatterns, 880 | }); 881 | 882 | await this.plugin.saveSettings(); 883 | this.display(); 884 | return; 885 | } 886 | 887 | ruleDeletePrimerTimer = setTimeout( 888 | () => { 889 | deleteRulePrimed = false; 890 | ruleEl.removeClass('primed'); 891 | }, 892 | 1000 * 4, // 4 second timeout 893 | ); 894 | deleteRulePrimed = true; 895 | ruleEl.addClass('primed'); 896 | 897 | new Notice( 898 | `Click again to delete Rule ${ 899 | ruleIndex + 1 900 | }`, 901 | ); 902 | }); 903 | }); 904 | }); 905 | 906 | const addRuleButtonEl = patternRulesEl.createDiv('add-rule-button'); 907 | 908 | new Setting(addRuleButtonEl).addButton((button) => { 909 | button 910 | .setButtonText('Add find / replace rule') 911 | .setClass('add-rule-button') 912 | .onClick(async () => { 913 | const newPatterns = cloneDeep(getSettings().patterns); 914 | newPatterns[patternIndex].rules.push({ 915 | ...defaultPatternRuleSettings, 916 | }); 917 | updateSettings({ 918 | patterns: newPatterns, 919 | }); 920 | await this.plugin.saveSettings(); 921 | this.display(); 922 | }); 923 | }); 924 | 925 | const patternCursorEl = patternEl.createEl('div'); 926 | patternCursorEl.addClass('pattern-cursor'); 927 | 928 | const patternCursorStartEl = patternCursorEl.createEl('div'); 929 | const patternCursorStartSetting = new Setting(patternCursorStartEl); 930 | const patternCursorStartValidEl = 931 | patternCursorStartEl.createEl('span'); 932 | patternCursorStartValidEl.addClass('validation-text'); 933 | const patternCursorStartValid = validateRuleString( 934 | pattern.cursorRegexStart, 935 | ); 936 | if (patternCursorStartValid.valid !== true) { 937 | patternCursorStartEl.addClass('invalid'); 938 | patternCursorStartValidEl.setText( 939 | patternCursorStartValid.string, 940 | ); 941 | } 942 | 943 | patternCursorStartSetting 944 | .setName('Cursor/selection start (Regex)') 945 | .setDesc( 946 | 'A regular expression to determine the starting location of the cursor after the Pattern has been applied. The cursor will be placed at the ending location of the first match.', 947 | ) 948 | .addText((text) => { 949 | text.setPlaceholder('') 950 | .setValue(pattern.cursorRegexStart) 951 | .onChange(async (value) => { 952 | const newPatterns = cloneDeep( 953 | getSettings().patterns, 954 | ); 955 | newPatterns.splice(patternIndex, 1, { 956 | ...newPatterns[patternIndex], 957 | cursorRegexStart: value, 958 | }); 959 | updateSettings({ 960 | patterns: newPatterns, 961 | }); 962 | 963 | await this.plugin.saveSettings(); 964 | 965 | const valueValid = validateRuleString(value); 966 | if (valueValid.valid === true) { 967 | patternCursorStartEl.removeClass('invalid'); 968 | patternCursorStartValidEl.setText(''); 969 | } else { 970 | patternCursorStartEl.addClass('invalid'); 971 | patternCursorStartValidEl.setText( 972 | valueValid.string, 973 | ); 974 | } 975 | }); 976 | }); 977 | 978 | const patternCursorEndEl = patternCursorEl.createEl('div'); 979 | const patternCursorEndSetting = new Setting(patternCursorEndEl); 980 | const patternCursorEndValidEl = patternCursorEndEl.createEl('span'); 981 | patternCursorEndValidEl.addClass('validation-text'); 982 | const patternCursorEndValid = validateRuleString( 983 | pattern.cursorRegexEnd, 984 | ); 985 | if (patternCursorEndValid.valid !== true) { 986 | patternCursorEndEl.addClass('invalid'); 987 | patternCursorEndValidEl.setText(patternCursorEndValid.string); 988 | } 989 | 990 | patternCursorEndSetting 991 | .setName('Cursor/selection end (Regex)') 992 | .setDesc( 993 | 'A regular expression to determine the ending location of the cursor after the Pattern has been applied. The cursor will be placed at the ending location of the first match.', 994 | ) 995 | .addText((text) => { 996 | text.setPlaceholder('') 997 | .setValue(pattern.cursorRegexEnd) 998 | .onChange(async (value) => { 999 | const newPatterns = cloneDeep( 1000 | getSettings().patterns, 1001 | ); 1002 | newPatterns.splice(patternIndex, 1, { 1003 | ...newPatterns[patternIndex], 1004 | cursorRegexEnd: value, 1005 | }); 1006 | updateSettings({ 1007 | patterns: newPatterns, 1008 | }); 1009 | 1010 | await this.plugin.saveSettings(); 1011 | 1012 | const valueValid = validateRuleString(value); 1013 | if (valueValid.valid === true) { 1014 | patternCursorEndEl.removeClass('invalid'); 1015 | patternCursorEndValidEl.setText(''); 1016 | } else { 1017 | patternCursorEndEl.addClass('invalid'); 1018 | patternCursorEndValidEl.setText( 1019 | valueValid.string, 1020 | ); 1021 | } 1022 | }); 1023 | }); 1024 | } 1025 | 1026 | const addPatternButtonEl = patternsEl.createEl('div', { 1027 | cls: 'add-pattern-button-el', 1028 | }); 1029 | 1030 | new Setting(addPatternButtonEl).addButton((button) => { 1031 | button 1032 | .setButtonText('Add pattern') 1033 | .setClass('add-pattern-button') 1034 | .onClick(async () => { 1035 | updateSettings({ 1036 | patterns: [ 1037 | ...getSettings().patterns, 1038 | { 1039 | ...defaultPatternSettings, 1040 | cursorRegexStart: 1041 | getSettings().defaultCursorRegexStart || 1042 | defaultSettings.defaultCursorRegexStart, 1043 | cursorRegexEnd: 1044 | getSettings().defaultCursorRegexEnd || 1045 | defaultSettings.defaultCursorRegexEnd, 1046 | }, 1047 | ], 1048 | }); 1049 | await this.plugin.saveSettings(); 1050 | this.display(); 1051 | }); 1052 | }); 1053 | 1054 | const commandsEl = containerEl.createEl('div'); 1055 | commandsEl.addClass('commands'); 1056 | commandsEl.createEl('h2', { text: 'Commands' }); 1057 | const commandsDescriptionEl = commandsEl.createEl('div'); 1058 | commandsDescriptionEl.addClass('setting-item-description'); 1059 | commandsDescriptionEl.append( 1060 | 'Commands for the command palette. ', 1061 | fragment.createEl('span', { 1062 | text: 'Changes to this section are not reflected outside of this settings window until Obsidian is reloaded.', 1063 | cls: 'bold', 1064 | }), 1065 | fragment.createEl('br'), 1066 | ' Each command is populated by filtering the Pattern names above. Untitled patterns are given placeholder names of the form ', 1067 | fragment.createEl('code', { text: formatUnnamedPattern(1) }), 1068 | '."', 1069 | fragment.createEl('br'), 1070 | 'If a command matches only one Pattern, it will automatically run that Pattern when the command is called. If the command matches more than one Pattern, a submenu will open, asking which Pattern you would like to run.', 1071 | ); 1072 | 1073 | new Setting(commandsEl) 1074 | .setName('Filter commands by name') 1075 | .addText((text) => { 1076 | const settings = getSettings(); 1077 | text.setValue(settings.commandFilterString || '').onChange( 1078 | async (value) => { 1079 | updateSettings({ 1080 | ...cloneDeep(getSettings()), 1081 | commandFilterString: value, 1082 | }); 1083 | 1084 | await this.plugin.saveSettings(); 1085 | }, 1086 | ); 1087 | }); 1088 | 1089 | new Setting(commandsEl).setName('Apply filter').addButton((button) => { 1090 | button 1091 | .setIcon('magnifying-glass') 1092 | .setTooltip('Filter Commands') 1093 | .onClick(async () => { 1094 | this.display(); 1095 | }); 1096 | }); 1097 | 1098 | const commands = getSettings().commands; 1099 | for (const [commandIndex, command] of commands.entries()) { 1100 | const settings = getSettings(); 1101 | const commandFilterString = settings.commandFilterString; 1102 | if ( 1103 | commandFilterString !== undefined && 1104 | commandFilterString !== '' 1105 | ) { 1106 | if ( 1107 | !command.name 1108 | .toLowerCase() 1109 | .includes(commandFilterString.toLowerCase()) 1110 | ) { 1111 | continue; 1112 | } 1113 | } 1114 | 1115 | const commandEl = commandsEl.createEl('div'); 1116 | commandEl.addClass('command'); 1117 | 1118 | commandEl.createEl('h3', { text: `Command ${commandIndex + 1}` }); 1119 | 1120 | new Setting(commandEl) 1121 | .setName('Command Palette name') 1122 | .addText((text) => { 1123 | text.setPlaceholder('') 1124 | .setValue(command.name) 1125 | .onChange(async (value) => { 1126 | const newCommands = cloneDeep( 1127 | getSettings().commands, 1128 | ); 1129 | newCommands.splice(commandIndex, 1, { 1130 | ...newCommands[commandIndex], 1131 | name: value, 1132 | }); 1133 | updateSettings({ 1134 | commands: newCommands, 1135 | }); 1136 | 1137 | await this.plugin.saveSettings(); 1138 | }); 1139 | }); 1140 | 1141 | new Setting(commandEl) 1142 | .setName('Command Palette icon (mobile)') 1143 | .addDropdown((dropdown) => { 1144 | // This list comes from 1145 | // https://forum.obsidian.md/t/list-of-available-icons-for-component-seticon/16332/4?u=l.j 1146 | dropdown 1147 | .addOptions({ 1148 | 'any-key': 'any-key', 1149 | 'audio-file': 'audio-file', 1150 | blocks: 'blocks', 1151 | 'bold-glyph': 'bold-glyph', 1152 | 'bracket-glyph': 'bracket-glyph', 1153 | 'broken-link': 'broken-link', 1154 | 'bullet-list': 'bullet-list', 1155 | 'bullet-list-glyph': 'bullet-list-glyph', 1156 | 'calendar-with-checkmark': 1157 | 'calendar-with-checkmark', 1158 | 'check-in-circle': 'check-in-circle', 1159 | 'check-small': 'check-small', 1160 | 'checkbox-glyph': 'checkbox-glyph', 1161 | checkmark: 'checkmark', 1162 | clock: 'clock', 1163 | cloud: 'cloud', 1164 | 'code-glyph': 'code-glyph', 1165 | 'create-new': 'create-new', 1166 | cross: 'cross', 1167 | 'cross-in-box': 'cross-in-box', 1168 | 'crossed-star': 'crossed-star', 1169 | csv: 'csv', 1170 | deleteColumn: 'deleteColumn', 1171 | deleteRow: 'deleteRow', 1172 | dice: 'dice', 1173 | document: 'document', 1174 | documents: 'documents', 1175 | 'dot-network': 'dot-network', 1176 | 'double-down-arrow-glyph': 1177 | 'double-down-arrow-glyph', 1178 | 'double-up-arrow-glyph': 'double-up-arrow-glyph', 1179 | 'down-arrow-with-tail': 'down-arrow-with-tail', 1180 | 'down-chevron-glyph': 'down-chevron-glyph', 1181 | enter: 'enter', 1182 | 'exit-fullscreen': 'exit-fullscreen', 1183 | 'expand-vertically': 'expand-vertically', 1184 | 'filled-pin': 'filled-pin', 1185 | folder: 'folder', 1186 | formula: 'formula', 1187 | 'forward-arrow': 'forward-arrow', 1188 | fullscreen: 'fullscreen', 1189 | gear: 'gear', 1190 | 'go-to-file': 'go-to-file', 1191 | hashtag: 'hashtag', 1192 | 'heading-glyph': 'heading-glyph', 1193 | help: 'help', 1194 | 'highlight-glyph': 'highlight-glyph', 1195 | 'horizontal-split': 'horizontal-split', 1196 | 'image-file': 'image-file', 1197 | 'image-glyph': 'image-glyph', 1198 | 'indent-glyph': 'indent-glyph', 1199 | info: 'info', 1200 | insertColumn: 'insertColumn', 1201 | insertRow: 'insertRow', 1202 | install: 'install', 1203 | 'italic-glyph': 'italic-glyph', 1204 | 'keyboard-glyph': 'keyboard-glyph', 1205 | languages: 'languages', 1206 | 'left-arrow': 'left-arrow', 1207 | 'left-arrow-with-tail': 'left-arrow-with-tail', 1208 | 'left-chevron-glyph': 'left-chevron-glyph', 1209 | 'lines-of-text': 'lines-of-text', 1210 | link: 'link', 1211 | 'link-glyph': 'link-glyph', 1212 | 'logo-crystal': 'logo-crystal', 1213 | 'magnifying-glass': 'magnifying-glass', 1214 | microphone: 'microphone', 1215 | 'microphone-filled': 'microphone-filled', 1216 | 'minus-with-circle': 'minus-with-circle', 1217 | moveColumnLeft: 'moveColumnLeft', 1218 | moveColumnRight: 'moveColumnRight', 1219 | moveRowDown: 'moveRowDown', 1220 | moveRowUp: 'moveRowUp', 1221 | 'note-glyph': 'note-glyph', 1222 | 'number-list-glyph': 'number-list-glyph', 1223 | 'open-vault': 'open-vault', 1224 | 'pane-layout': 'pane-layout', 1225 | 'paper-plane': 'paper-plane', 1226 | paused: 'paused', 1227 | 'pdf-file': 'pdf-file', 1228 | pencil: 'pencil', 1229 | 'percent-sign-glyph': 'percent-sign-glyph', 1230 | pin: 'pin', 1231 | 'plus-with-circle': 'plus-with-circle', 1232 | 'popup-open': 'popup-open', 1233 | presentation: 'presentation', 1234 | 'price-tag-glyph': 'price-tag-glyph', 1235 | 'quote-glyph': 'quote-glyph', 1236 | 'redo-glyph': 'redo-glyph', 1237 | reset: 'reset', 1238 | 'right-arrow': 'right-arrow', 1239 | 'right-arrow-with-tail': 'right-arrow-with-tail', 1240 | 'right-chevron-glyph': 'right-chevron-glyph', 1241 | 'right-triangle': 'right-triangle', 1242 | 'run-command': 'run-command', 1243 | search: 'search', 1244 | 'sheets-in-box': 'sheets-in-box', 1245 | sortAsc: 'sortAsc', 1246 | sortDesc: 'sortDesc', 1247 | spreadsheet: 'spreadsheet', 1248 | 'stacked-levels': 'stacked-levels', 1249 | star: 'star', 1250 | 'star-list': 'star-list', 1251 | 'strikethrough-glyph': 'strikethrough-glyph', 1252 | switch: 'switch', 1253 | sync: 'sync', 1254 | 'sync-small': 'sync-small', 1255 | 'tag-glyph': 'tag-glyph', 1256 | 'three-horizontal-bars': 'three-horizontal-bars', 1257 | trash: 'trash', 1258 | 'undo-glyph': 'undo-glyph', 1259 | 'unindent-glyph': 'unindent-glyph', 1260 | 'up-and-down-arrows': 'up-and-down-arrows', 1261 | 'up-arrow-with-tail': 'up-arrow-with-tail', 1262 | 'up-chevron-glyph': 'up-chevron-glyph', 1263 | 'uppercase-lowercase-a': 'uppercase-lowercase-a', 1264 | vault: 'vault', 1265 | 'vertical-split': 'vertical-split', 1266 | 'vertical-three-dots': 'vertical-three-dots', 1267 | 'wrench-screwdriver-glyph': 1268 | 'wrench-screwdriver-glyph', 1269 | }) 1270 | .setValue(command?.icon) 1271 | .onChange(async (value) => { 1272 | const newCommands = cloneDeep( 1273 | getSettings().commands, 1274 | ); 1275 | newCommands[commandIndex].icon = value; 1276 | updateSettings({ 1277 | commands: newCommands, 1278 | }); 1279 | 1280 | await this.plugin.saveSettings(); 1281 | }); 1282 | }); 1283 | 1284 | let deleteCommandPrimed = false; 1285 | let commandDeletePrimerTimer: ReturnType | null; 1286 | 1287 | new Setting(commandEl) 1288 | .setName('Command meta controls') 1289 | .addExtraButton((button) => { 1290 | button 1291 | .setIcon('info') 1292 | .setTooltip('View matching patterns') 1293 | .onClick(async () => { 1294 | const settings = getSettings(); 1295 | const command = settings.commands[commandIndex]; 1296 | 1297 | const noticeTimeoutSeconds = 1000 * 30; // 30 seconds 1298 | const matchingPatterns = filterPatterns( 1299 | command, 1300 | ).map((patternIndex: number) => 1301 | formatPatternName(patternIndex), 1302 | ); 1303 | const numMatchingPatterns = matchingPatterns.length; 1304 | new Notice( 1305 | `${numMatchingPatterns} matching pattern${ 1306 | numMatchingPatterns !== 1 ? 's' : '' 1307 | }${ 1308 | numMatchingPatterns > 0 1309 | ? '\n - "' + 1310 | matchingPatterns.join('"\n - "') + 1311 | '"' 1312 | : '' 1313 | }`, 1314 | noticeTimeoutSeconds, 1315 | ); 1316 | }); 1317 | }) 1318 | .addExtraButton((button) => { 1319 | button 1320 | .setIcon('up-chevron-glyph') 1321 | .setTooltip('Move Command up') 1322 | .setDisabled(commandIndex === 0) 1323 | .onClick(async () => { 1324 | let newCommands = cloneDeep(getSettings().commands); 1325 | newCommands = moveInArray( 1326 | newCommands, 1327 | commandIndex, 1328 | commandIndex - 1, 1329 | ); 1330 | updateSettings({ 1331 | commands: newCommands, 1332 | }); 1333 | 1334 | await this.plugin.saveSettings(); 1335 | this.display(); 1336 | }); 1337 | }) 1338 | .addExtraButton((button) => { 1339 | button 1340 | .setIcon('down-chevron-glyph') 1341 | .setTooltip('Move Command down') 1342 | .setDisabled(commandIndex === commands.length - 1) 1343 | .onClick(async () => { 1344 | let newCommands = cloneDeep(getSettings().commands); 1345 | newCommands = moveInArray( 1346 | newCommands, 1347 | commandIndex, 1348 | commandIndex + 1, 1349 | ); 1350 | updateSettings({ 1351 | commands: newCommands, 1352 | }); 1353 | 1354 | await this.plugin.saveSettings(); 1355 | this.display(); 1356 | }); 1357 | }) 1358 | .addExtraButton((button) => { 1359 | button 1360 | .setIcon('cross-in-box') 1361 | .setTooltip('Delete command') 1362 | .onClick(async () => { 1363 | if (commandDeletePrimerTimer) { 1364 | clearTimeout(commandDeletePrimerTimer); 1365 | } 1366 | if (deleteCommandPrimed === true) { 1367 | const newCommands = cloneDeep( 1368 | getSettings().commands, 1369 | ); 1370 | newCommands.splice(commandIndex, 1); 1371 | updateSettings({ 1372 | commands: newCommands, 1373 | }); 1374 | 1375 | await this.plugin.saveSettings(); 1376 | this.display(); 1377 | return; 1378 | } 1379 | 1380 | commandDeletePrimerTimer = setTimeout( 1381 | () => { 1382 | deleteCommandPrimed = false; 1383 | commandEl.removeClass('primed'); 1384 | }, 1385 | 1000 * 4, // 4 second timeout 1386 | ); 1387 | deleteCommandPrimed = true; 1388 | commandEl.addClass('primed'); 1389 | 1390 | new Notice( 1391 | `Click again to delete Command ${ 1392 | commandIndex + 1 1393 | }`, 1394 | ); 1395 | }); 1396 | }); 1397 | 1398 | const commandPatternNameEl = commandEl.createEl('div'); 1399 | const commandPatternNameSetting = new Setting(commandPatternNameEl); 1400 | const commandPatternNameValidEl = 1401 | commandPatternNameEl.createEl('span'); 1402 | commandPatternNameValidEl.addClass('validation-text'); 1403 | const commandPatternNameValid = validateRuleString( 1404 | command.patternFilter, 1405 | ); 1406 | 1407 | if (commandPatternNameValid.valid !== true) { 1408 | commandPatternNameEl.addClass('invalid'); 1409 | commandPatternNameValidEl.setText( 1410 | commandPatternNameValid.string, 1411 | ); 1412 | } 1413 | 1414 | commandPatternNameSetting 1415 | .setName('Pattern name filter') 1416 | .addText((text) => { 1417 | text.setPlaceholder('') 1418 | .setValue(command.patternFilter) 1419 | .onChange(async (value) => { 1420 | const newCommands = cloneDeep( 1421 | getSettings().commands, 1422 | ); 1423 | newCommands.splice(commandIndex, 1, { 1424 | ...newCommands[commandIndex], 1425 | patternFilter: value, 1426 | }); 1427 | updateSettings({ 1428 | commands: newCommands, 1429 | }); 1430 | 1431 | await this.plugin.saveSettings(); 1432 | 1433 | const valueValid = validateRuleString(value); 1434 | 1435 | if (valueValid.valid === true) { 1436 | commandPatternNameEl.removeClass('invalid'); 1437 | commandPatternNameValidEl.setText(''); 1438 | } else { 1439 | commandPatternNameEl.addClass('invalid'); 1440 | commandPatternNameValidEl.setText( 1441 | valueValid.string, 1442 | ); 1443 | } 1444 | }); 1445 | }); 1446 | 1447 | new Setting(commandEl) 1448 | .setName('Apply to selection') 1449 | .addToggle((toggle) => { 1450 | toggle 1451 | .setTooltip('Apply to selection') 1452 | .setValue(command.selection || false) 1453 | .onChange(async (value) => { 1454 | const newCommands = cloneDeep( 1455 | getSettings().commands, 1456 | ); 1457 | newCommands[commandIndex].selection = value; 1458 | updateSettings({ 1459 | commands: newCommands, 1460 | }); 1461 | 1462 | await this.plugin.saveSettings(); 1463 | }); 1464 | }); 1465 | new Setting(commandEl) 1466 | .setName('Apply to whole lines') 1467 | .addToggle((toggle) => { 1468 | toggle 1469 | .setTooltip('Apply to whole lines') 1470 | .setValue(command.lines || false) 1471 | .onChange(async (value) => { 1472 | const newCommands = cloneDeep( 1473 | getSettings().commands, 1474 | ); 1475 | newCommands[commandIndex].lines = value; 1476 | updateSettings({ 1477 | commands: newCommands, 1478 | }); 1479 | 1480 | await this.plugin.saveSettings(); 1481 | }); 1482 | }); 1483 | 1484 | new Setting(commandEl) 1485 | .setName('Apply to whole document') 1486 | .addToggle((toggle) => { 1487 | toggle 1488 | .setTooltip('Apply to whole document') 1489 | .setValue(command.document || false) 1490 | .onChange(async (value) => { 1491 | const newCommands = cloneDeep( 1492 | getSettings().commands, 1493 | ); 1494 | newCommands[commandIndex].document = value; 1495 | updateSettings({ 1496 | commands: newCommands, 1497 | }); 1498 | 1499 | await this.plugin.saveSettings(); 1500 | }); 1501 | }); 1502 | 1503 | new Setting(commandEl) 1504 | .setName('Apply to whole clipboard') 1505 | .setDesc( 1506 | 'Apply the Pattern as with "Apply to whole document" to the clipboard.', 1507 | ) 1508 | .addToggle((toggle) => { 1509 | toggle 1510 | .setTooltip('Apply to whole whole clipboard') 1511 | .setValue(command.clipboard || false) 1512 | .onChange(async (value) => { 1513 | const newCommands = cloneDeep( 1514 | getSettings().commands, 1515 | ); 1516 | newCommands[commandIndex].clipboard = value; 1517 | updateSettings({ 1518 | commands: newCommands, 1519 | }); 1520 | 1521 | await this.plugin.saveSettings(); 1522 | }); 1523 | }); 1524 | 1525 | new Setting(commandEl) 1526 | .setName('Apply to clipboard (line-by-line)') 1527 | .setDesc( 1528 | 'Apply the Pattern as with "Apply to whole lines" to the clipboard.', 1529 | ) 1530 | .addToggle((toggle) => { 1531 | toggle 1532 | .setTooltip('Apply to whole whole clipboard') 1533 | .setValue(command.clipboardLines || false) 1534 | .onChange(async (value) => { 1535 | const newCommands = cloneDeep( 1536 | getSettings().commands, 1537 | ); 1538 | newCommands[commandIndex].clipboardLines = value; 1539 | updateSettings({ 1540 | commands: newCommands, 1541 | }); 1542 | 1543 | await this.plugin.saveSettings(); 1544 | }); 1545 | }); 1546 | } 1547 | 1548 | const addCommandButtonEl = commandsEl.createEl('div', { 1549 | cls: 'add-command-button-el', 1550 | }); 1551 | 1552 | new Setting(addCommandButtonEl).addButton((button) => { 1553 | button 1554 | .setButtonText('Add command') 1555 | .setClass('add-command-button') 1556 | .onClick(async () => { 1557 | updateSettings({ 1558 | commands: [ 1559 | ...getSettings().commands, 1560 | { 1561 | ...defaultCommandSettings, 1562 | }, 1563 | ], 1564 | }); 1565 | await this.plugin.saveSettings(); 1566 | this.display(); 1567 | }); 1568 | }); 1569 | 1570 | const importExportEl = containerEl.createDiv(); 1571 | importExportEl.addClass('import-export-div'); 1572 | importExportEl.createEl('h2', { 1573 | text: 'Import / Export / Clear', 1574 | }); 1575 | importExportEl.createEl('h3', { 1576 | text: 'Patterns', 1577 | }); 1578 | importExportEl.createEl('p', { 1579 | text: 'You can import and export patterns as JSON through the clipboard, in order to more readily share patterns with other users.', 1580 | }); 1581 | new Setting(importExportEl) 1582 | .setName('Import patterns from clipboard') 1583 | .addButton((button) => { 1584 | button 1585 | // .setIcon('right-arrow-with-tail') 1586 | .setButtonText('Import') 1587 | .onClick(async () => { 1588 | try { 1589 | const newSettings: Pattern[] = JSON.parse( 1590 | await navigator.clipboard.readText(), 1591 | ); 1592 | 1593 | // Check the structure of the data to import: 1594 | if (!Array.isArray(newSettings)) { 1595 | throw 'Settings are not in array format.'; 1596 | } 1597 | newSettings.forEach( 1598 | (pattern: Pattern, patternIndex) => { 1599 | if (!Guards.isPattern(pattern)) { 1600 | throw `Pattern ${patternIndex} is not structured correctly.`; 1601 | } 1602 | pattern.rules.forEach( 1603 | (rule: PatternRule, ruleIndex) => { 1604 | if (!Guards.isPatternRule(rule)) { 1605 | throw `Rule ${ruleIndex} in Pattern ${patternIndex} is not structured correctly.`; 1606 | } 1607 | }, 1608 | ); 1609 | }, 1610 | ); 1611 | 1612 | updateSettings({ 1613 | patterns: [ 1614 | ...getSettings().patterns, 1615 | ...newSettings, 1616 | ], 1617 | }); 1618 | await this.plugin.saveSettings(); 1619 | this.display(); 1620 | new Notice( 1621 | 'Imported pattern settings from clipboard!', 1622 | ); 1623 | } catch (error) { 1624 | new Notice( 1625 | 'Error importing pattern settings from clipboard. See developer console for more information.', 1626 | ); 1627 | console.log(error); 1628 | } 1629 | }); 1630 | }); 1631 | 1632 | new Setting(importExportEl) 1633 | .setName('Export patterns to clipboard') 1634 | .addButton((button) => { 1635 | button.setButtonText('Export').onClick(async () => { 1636 | try { 1637 | const settings = getSettings().patterns; 1638 | await navigator.clipboard.writeText( 1639 | JSON.stringify(settings, null, 2), 1640 | ); 1641 | new Notice( 1642 | 'Copied pattern settings as JSON to clipboard!', 1643 | ); 1644 | } catch (error) { 1645 | new Notice( 1646 | 'Error copying pattern settings as JSON to clipboard. See developer console for more information.', 1647 | ); 1648 | console.log(error); 1649 | } 1650 | }); 1651 | }); 1652 | importExportEl.createEl('h3', { 1653 | text: 'Commands', 1654 | }); 1655 | importExportEl.createEl('p', { 1656 | text: 'You can import and export commands as JSON through the clipboard, in order to more readily share commands with other users.', 1657 | }); 1658 | 1659 | new Setting(importExportEl) 1660 | .setName('Import commands from clipboard') 1661 | .addButton((button) => { 1662 | button.setButtonText('Import').onClick(async () => { 1663 | try { 1664 | const newSettings: Command[] = JSON.parse( 1665 | await navigator.clipboard.readText(), 1666 | ); 1667 | 1668 | // Check the structure of the data to import: 1669 | if (!Array.isArray(newSettings)) { 1670 | throw 'Settings are not in array format.'; 1671 | } 1672 | newSettings.forEach( 1673 | (command: Command, commandIndex) => { 1674 | if (!Guards.isCommand(command)) { 1675 | throw `Command ${commandIndex} is not structured correctly.`; 1676 | } 1677 | }, 1678 | ); 1679 | 1680 | updateSettings({ 1681 | commands: [ 1682 | ...getSettings().commands, 1683 | ...newSettings, 1684 | ], 1685 | }); 1686 | await this.plugin.saveSettings(); 1687 | this.display(); 1688 | new Notice('Imported command settings from clipboard!'); 1689 | } catch (error) { 1690 | new Notice( 1691 | 'Error importing command settings from clipboard. See developer console for more information.', 1692 | ); 1693 | console.log(error); 1694 | } 1695 | }); 1696 | }); 1697 | new Setting(importExportEl) 1698 | .setName('Export commands to clipboard') 1699 | .addButton((button) => { 1700 | button 1701 | // .setIcon('right-arrow-with-tail') 1702 | .setButtonText('Export') 1703 | .onClick(async () => { 1704 | try { 1705 | const settings = getSettings().commands; 1706 | await navigator.clipboard.writeText( 1707 | JSON.stringify(settings, null, 2), 1708 | ); 1709 | new Notice( 1710 | 'Copied command settings as JSON to clipboard!', 1711 | ); 1712 | } catch (error) { 1713 | new Notice( 1714 | 'Error copying command settings as JSON to clipboard. See developer console for more information.', 1715 | ); 1716 | console.log(error); 1717 | } 1718 | }); 1719 | }); 1720 | 1721 | importExportEl.createEl('h3', { 1722 | text: 'Clear all', 1723 | }); 1724 | let clearAllSettingsPrimed = false; 1725 | let primerTimer: ReturnType | null; 1726 | const clearButtonEl = importExportEl.createEl('span'); 1727 | clearButtonEl.addClass('clear-settings-button'); 1728 | new Setting(clearButtonEl) 1729 | .setName('Clear and reset all patterns and commands.') 1730 | .addButton((button) => { 1731 | button.setButtonText('Delete all').onClick(async () => { 1732 | if (primerTimer) { 1733 | clearTimeout(primerTimer); 1734 | } 1735 | if (clearAllSettingsPrimed === true) { 1736 | const settingsBackup = cloneDeep(getSettings()); 1737 | try { 1738 | clearSettings(); 1739 | await this.plugin.saveSettings(); 1740 | this.display(); 1741 | new Notice('Apply Patterns plugin settings reset.'); 1742 | } catch (error) { 1743 | new Notice( 1744 | 'Error clearing and resetting plugin settings.', 1745 | ); 1746 | console.log(error); 1747 | updateSettings(settingsBackup); 1748 | } 1749 | return; 1750 | } 1751 | primerTimer = setTimeout( 1752 | () => { 1753 | button.setButtonText( 1754 | 'Clear all settings for this plugin', 1755 | ); 1756 | clearAllSettingsPrimed = false; 1757 | clearButtonEl.removeClass('primed'); 1758 | }, 1759 | 1000 * 4, // 4 second timeout 1760 | ); 1761 | clearAllSettingsPrimed = true; 1762 | clearButtonEl.addClass('primed'); 1763 | button.setButtonText('Click again to clear settings'); 1764 | }); 1765 | }); 1766 | } 1767 | } 1768 | -------------------------------------------------------------------------------- /src/ValidateRuleString.ts: -------------------------------------------------------------------------------- 1 | import { parseDateRange, ruleDateRegexString } from './ParseDateRange'; 2 | 3 | export const validateRuleString = ( 4 | ruleString: string, 5 | validateRegex: boolean = true, 6 | ): { valid: boolean | null; string: string } => { 7 | const dateMatches = [ 8 | ...ruleString.matchAll(new RegExp(ruleDateRegexString, 'g')), 9 | ]; 10 | let updatedRuleString = ruleString 11 | // Because the plugin's settings are stored in JSON, characters like 12 | // \n get double-escaped, and then do not get replaced automatically 13 | // on use. This was causing To strings not to parse \n, etc. 14 | .replace(/\\n/g, '\n') 15 | .replace(/\\t/g, '\t') 16 | .replace(/\\r/g, '\r'); 17 | // Iterate over dateMatches backwards, in order to be able to make changes 18 | // to updatedRuleString in-place without affecting index numbers from 19 | // ruleString: 20 | dateMatches.reverse(); 21 | for (const dateMatch of dateMatches) { 22 | if (dateMatch.index !== undefined) { 23 | try { 24 | const parsedDateRange = parseDateRange(dateMatch[0]); 25 | if (parsedDateRange === undefined) { 26 | return { valid: false, string: '' }; 27 | } 28 | updatedRuleString = 29 | updatedRuleString.substring(0, dateMatch.index) + 30 | (parsedDateRange || '') + 31 | updatedRuleString.substring( 32 | dateMatch.index + dateMatch[0].length, 33 | ); 34 | } catch (e) { 35 | return { 36 | valid: false, 37 | string: `Error processing date "${dateMatch[0]}": "${e}"`, 38 | }; 39 | } 40 | } 41 | } 42 | if (validateRegex === false) { 43 | return { valid: null, string: updatedRuleString }; 44 | } 45 | 46 | try { 47 | new RegExp(updatedRuleString, 'u'); 48 | return { valid: true, string: updatedRuleString }; 49 | } catch (e) { 50 | return { 51 | valid: false, 52 | string: `(Invalid regular expression) "${updatedRuleString}": "${e}"`, 53 | }; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, Plugin, View } from 'obsidian'; 2 | 3 | import { applyPattern } from './ApplyPattern'; 4 | import { 5 | Command, 6 | defaultCommandSettings, 7 | defaultPatternSettings, 8 | defaultPatternRuleSettings, 9 | defaultSettings, 10 | getSettings, 11 | Pattern, 12 | PatternRule, 13 | updateSettings, 14 | } from './Settings'; 15 | import { SettingsTab } from './SettingsTab'; 16 | 17 | export default class ApplyPatternsPlugin extends Plugin { 18 | async onload() { 19 | console.log('loading plugin "apply-patterns"'); 20 | 21 | await this.loadSettings(); 22 | 23 | this.addSettingTab(new SettingsTab({ plugin: this })); 24 | 25 | this.addCommand({ 26 | id: 'apply-pattern-to-lines', 27 | name: 'Apply pattern to whole lines', 28 | icon: 'lines-of-text', 29 | editorCallback: (editor: Editor, view: View) => { 30 | return applyPattern(editor, view, this.app, 'lines'); 31 | }, 32 | }); 33 | 34 | this.addCommand({ 35 | id: 'apply-pattern-to-selection', 36 | name: 'Apply pattern to selection', 37 | icon: 'sheets-in-box', 38 | editorCallback: (editor: Editor, view: View) => { 39 | return applyPattern(editor, view, this.app, 'selection'); 40 | }, 41 | }); 42 | 43 | this.addCommand({ 44 | id: 'apply-pattern-to-document', 45 | name: 'Apply pattern to whole document', 46 | icon: 'document', 47 | editorCallback: (editor: Editor, view: View) => { 48 | return applyPattern(editor, view, this.app, 'document'); 49 | }, 50 | }); 51 | 52 | this.addCommand({ 53 | id: 'apply-pattern-to-clipboard-document', 54 | name: 'Apply pattern to whole clipboard', 55 | icon: 'document', 56 | editorCallback: (editor: Editor, view: View) => { 57 | return applyPattern(editor, view, this.app, 'clipboard'); 58 | }, 59 | }); 60 | 61 | this.addCommand({ 62 | id: 'apply-pattern-to-clipboard-lines', 63 | name: 'Apply pattern to clipboard (line-by-line)', 64 | icon: 'lines-of-text', 65 | editorCallback: (editor: Editor, view: View) => { 66 | return applyPattern(editor, view, this.app, 'clipboardLines'); 67 | }, 68 | }); 69 | 70 | const settingsCommands = getSettings().commands || []; 71 | settingsCommands.forEach((command: Command, commandIndex) => { 72 | if (command.patternFilter !== '') { 73 | for (const [type, plainLanguage] of Object.entries({ 74 | selection: 'selection', 75 | lines: 'whole lines', 76 | document: 'whole document', 77 | clipboard: 'whole clipboard', 78 | clipboardLines: 'clipboard (line-by-line)', 79 | })) { 80 | // Get TypeScript to understand type as a key, rather than 81 | // as a string. See https://stackoverflow.com/a/62438434 82 | // for an explanation. 83 | const commandTypeKey = type as keyof Command; 84 | if (command[commandTypeKey] === true) { 85 | this.addCommand({ 86 | id: `apply-pattern-${commandIndex}-${type}`, 87 | icon: command?.icon, 88 | name: `${ 89 | command.name || 90 | 'Unnamed command ' + commandIndex 91 | } on ${plainLanguage}`, 92 | editorCallback: async ( 93 | editor: Editor, 94 | view: View, 95 | ) => { 96 | return applyPattern( 97 | editor, 98 | view, 99 | this.app, 100 | type, 101 | command, 102 | ); 103 | }, 104 | }); 105 | } 106 | } 107 | } 108 | }); 109 | } 110 | 111 | onunload() { 112 | console.log('unloading plugin "apply-patterns"'); 113 | } 114 | 115 | async loadSettings() { 116 | let userSettings = await this.loadData(); 117 | 118 | // Update settings if the API version has been incremented: 119 | if ( 120 | userSettings === null || 121 | userSettings.apiVersion === null || 122 | userSettings.apiVersion < defaultSettings.apiVersion 123 | ) { 124 | userSettings = { ...defaultSettings, ...userSettings }; 125 | 126 | userSettings.patterns.forEach( 127 | (pattern: Pattern, patternIndex: number) => { 128 | pattern.rules.forEach( 129 | (rule: PatternRule, ruleIndex: number) => { 130 | pattern.rules[ruleIndex] = { 131 | ...defaultPatternRuleSettings, 132 | ...rule, 133 | }; 134 | }, 135 | ); 136 | 137 | return (userSettings.patterns[patternIndex] = { 138 | ...defaultPatternSettings, 139 | ...pattern, 140 | }); 141 | }, 142 | ); 143 | 144 | userSettings.commands.forEach( 145 | (command: Command, commandIndex: number) => { 146 | return (userSettings.commands[commandIndex] = { 147 | ...defaultCommandSettings, 148 | ...command, 149 | }); 150 | }, 151 | ); 152 | } 153 | 154 | updateSettings(userSettings); 155 | } 156 | 157 | async saveSettings() { 158 | await this.saveData(getSettings()); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .import-export-div .setting-item { 2 | border-top: none; 3 | } 4 | 5 | .pattern, 6 | .command { 7 | border-top: 2px solid var(--interactive-accent); 8 | } 9 | 10 | .pattern:nth-last-child(2), 11 | .command:nth-last-child(2) { 12 | border-bottom: 2px solid var(--interactive-accent); 13 | margin-bottom: 1em; 14 | } 15 | 16 | .patterns .rule.disabled, 17 | .patterns .pattern .is-disabled, 18 | .patterns .rule .is-disabled, 19 | .commands .command .is-disabled { 20 | opacity: 50%; 21 | } 22 | 23 | .bold { 24 | font-weight: bold; 25 | } 26 | 27 | .pattern.collapsed [aria-label="Expand"] { 28 | color: var(--interactive-success); 29 | } 30 | 31 | .pattern.collapsed .pattern-rules, 32 | .pattern.collapsed .pattern-cursor { 33 | display: none; 34 | } 35 | 36 | .pattern.primed div[aria-label="Delete pattern"], 37 | .rule.primed div[aria-label="Delete rule"], 38 | .command.primed div[aria-label="Delete command"] { 39 | color: var(--text-error) !important; 40 | } 41 | 42 | .clear-settings-button.primed button { 43 | background-color: var(--background-modifier-error) !important; 44 | color: var(--text-error) !important; 45 | } 46 | 47 | .commands .bold { 48 | color: var(--text-error); 49 | font-size: 1.15em; 50 | } 51 | 52 | .invalid input[type='text'] { 53 | background: rgb(255, 0, 0, 0.2) !important; 54 | } 55 | 56 | .validation-text { 57 | color: var(--text-error) !important; 58 | text-align: right; 59 | display: block; 60 | font-size: small; 61 | font-style: italic; 62 | } 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "allowSyntheticDefaultImports": true, 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "allowJs": true, 10 | "noImplicitAny": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "lib": [ 14 | "ES2020", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "**/*.ts" 20 | ] 21 | } -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.12.10", 3 | "1.1.0": "0.12.10", 4 | "1.1.1": "0.12.10", 5 | "1.2.0": "0.12.0", 6 | "1.2.1": "0.12.0", 7 | "1.3.0": "0.12.0", 8 | "1.3.1": "0.12.0", 9 | "1.4.0": "0.13.9", 10 | "1.4.1": "0.13.9", 11 | "2.0.0": "0.13.9", 12 | "2.1.0": "0.14.5", 13 | "2.1.1": "0.14.5", 14 | "2.1.2": "1.0.0" 15 | } 16 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@dsherret/to-absolute-glob@^2.0.2": 6 | version "2.0.2" 7 | resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" 8 | integrity sha1-H2R13IvZdM6gei2vOGSzF7HdMyw= 9 | dependencies: 10 | is-absolute "^1.0.0" 11 | is-negated-glob "^1.0.0" 12 | 13 | "@nodelib/fs.scandir@2.1.5": 14 | version "2.1.5" 15 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 16 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 17 | dependencies: 18 | "@nodelib/fs.stat" "2.0.5" 19 | run-parallel "^1.1.9" 20 | 21 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 22 | version "2.0.5" 23 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 24 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 25 | 26 | "@nodelib/fs.walk@^1.2.3": 27 | version "1.2.8" 28 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 29 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 30 | dependencies: 31 | "@nodelib/fs.scandir" "2.1.5" 32 | fastq "^1.6.0" 33 | 34 | "@ts-morph/common@~0.7.0": 35 | version "0.7.5" 36 | resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.7.5.tgz#d81603abd4b86d0099d69239cbbcdf990a5dfb25" 37 | integrity sha512-nlFunSKAsFWI0Ol/uPxJcpVqXxTGNuaWXTmoQDhcnwj1UM4QmBSUVWzqoQ0OzUlqo4sV1gobfFBkMHuZVemMAQ== 38 | dependencies: 39 | "@dsherret/to-absolute-glob" "^2.0.2" 40 | fast-glob "^3.2.5" 41 | is-negated-glob "^1.0.0" 42 | mkdirp "^1.0.4" 43 | multimatch "^5.0.0" 44 | typescript "~4.1.3" 45 | 46 | "@types/codemirror@0.0.108": 47 | version "0.0.108" 48 | resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.108.tgz#e640422b666bf49251b384c390cdeb2362585bde" 49 | integrity sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw== 50 | dependencies: 51 | "@types/tern" "*" 52 | 53 | "@types/command-line-args@^5.0.0": 54 | version "5.2.0" 55 | resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6" 56 | integrity sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA== 57 | 58 | "@types/command-line-usage@^5.0.1": 59 | version "5.0.2" 60 | resolved "https://registry.yarnpkg.com/@types/command-line-usage/-/command-line-usage-5.0.2.tgz#ba5e3f6ae5a2009d466679cc431b50635bf1a064" 61 | integrity sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg== 62 | 63 | "@types/estree@*": 64 | version "0.0.51" 65 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" 66 | integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== 67 | 68 | "@types/json-schema@^7.0.9": 69 | version "7.0.11" 70 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" 71 | integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== 72 | 73 | "@types/lodash.clonedeep@^4.5.6": 74 | version "4.5.6" 75 | resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b" 76 | integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA== 77 | dependencies: 78 | "@types/lodash" "*" 79 | 80 | "@types/lodash@*": 81 | version "4.14.181" 82 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d" 83 | integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag== 84 | 85 | "@types/minimatch@^3.0.3": 86 | version "3.0.5" 87 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" 88 | integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== 89 | 90 | "@types/node@^14.17.6": 91 | version "14.18.13" 92 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.13.tgz#6ad4d9db59e6b3faf98dcfe4ca9d2aec84443277" 93 | integrity sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA== 94 | 95 | "@types/strip-bom@^3.0.0": 96 | version "3.0.0" 97 | resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" 98 | integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= 99 | 100 | "@types/strip-json-comments@0.0.30": 101 | version "0.0.30" 102 | resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" 103 | integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== 104 | 105 | "@types/tern@*": 106 | version "0.23.4" 107 | resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb" 108 | integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg== 109 | dependencies: 110 | "@types/estree" "*" 111 | 112 | "@typescript-eslint/eslint-plugin@^5.2.0": 113 | version "5.19.0" 114 | resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz#9608a4b6d0427104bccf132f058cba629a6553c0" 115 | integrity sha512-w59GpFqDYGnWFim9p6TGJz7a3qWeENJuAKCqjGSx+Hq/bwq3RZwXYqy98KIfN85yDqz9mq6QXiY5h0FjGQLyEg== 116 | dependencies: 117 | "@typescript-eslint/scope-manager" "5.19.0" 118 | "@typescript-eslint/type-utils" "5.19.0" 119 | "@typescript-eslint/utils" "5.19.0" 120 | debug "^4.3.2" 121 | functional-red-black-tree "^1.0.1" 122 | ignore "^5.1.8" 123 | regexpp "^3.2.0" 124 | semver "^7.3.5" 125 | tsutils "^3.21.0" 126 | 127 | "@typescript-eslint/parser@^5.2.0": 128 | version "5.19.0" 129 | resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.19.0.tgz#05e587c1492868929b931afa0cb5579b0f728e75" 130 | integrity sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ== 131 | dependencies: 132 | "@typescript-eslint/scope-manager" "5.19.0" 133 | "@typescript-eslint/types" "5.19.0" 134 | "@typescript-eslint/typescript-estree" "5.19.0" 135 | debug "^4.3.2" 136 | 137 | "@typescript-eslint/scope-manager@5.19.0": 138 | version "5.19.0" 139 | resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz#97e59b0bcbcb54dbcdfba96fc103b9020bbe9cb4" 140 | integrity sha512-Fz+VrjLmwq5fbQn5W7cIJZ066HxLMKvDEmf4eu1tZ8O956aoX45jAuBB76miAECMTODyUxH61AQM7q4/GOMQ5g== 141 | dependencies: 142 | "@typescript-eslint/types" "5.19.0" 143 | "@typescript-eslint/visitor-keys" "5.19.0" 144 | 145 | "@typescript-eslint/type-utils@5.19.0": 146 | version "5.19.0" 147 | resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.19.0.tgz#80f2125b0dfe82494bbae1ea99f1c0186d420282" 148 | integrity sha512-O6XQ4RI4rQcBGshTQAYBUIGsKqrKeuIOz9v8bckXZnSeXjn/1+BDZndHLe10UplQeJLXDNbaZYrAytKNQO2T4Q== 149 | dependencies: 150 | "@typescript-eslint/utils" "5.19.0" 151 | debug "^4.3.2" 152 | tsutils "^3.21.0" 153 | 154 | "@typescript-eslint/types@5.19.0": 155 | version "5.19.0" 156 | resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.19.0.tgz#12d3d600d754259da771806ee8b2c842d3be8d12" 157 | integrity sha512-zR1ithF4Iyq1wLwkDcT+qFnhs8L5VUtjgac212ftiOP/ZZUOCuuF2DeGiZZGQXGoHA50OreZqLH5NjDcDqn34w== 158 | 159 | "@typescript-eslint/typescript-estree@5.19.0": 160 | version "5.19.0" 161 | resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz#fc987b8f62883f9ea6a5b488bdbcd20d33c0025f" 162 | integrity sha512-dRPuD4ocXdaE1BM/dNR21elSEUPKaWgowCA0bqJ6YbYkvtrPVEvZ+zqcX5a8ECYn3q5iBSSUcBBD42ubaOp0Hw== 163 | dependencies: 164 | "@typescript-eslint/types" "5.19.0" 165 | "@typescript-eslint/visitor-keys" "5.19.0" 166 | debug "^4.3.2" 167 | globby "^11.0.4" 168 | is-glob "^4.0.3" 169 | semver "^7.3.5" 170 | tsutils "^3.21.0" 171 | 172 | "@typescript-eslint/utils@5.19.0": 173 | version "5.19.0" 174 | resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.19.0.tgz#fe87f1e3003d9973ec361ed10d36b4342f1ded1e" 175 | integrity sha512-ZuEckdupXpXamKvFz/Ql8YnePh2ZWcwz7APICzJL985Rp5C2AYcHO62oJzIqNhAMtMK6XvrlBTZeNG8n7gS3lQ== 176 | dependencies: 177 | "@types/json-schema" "^7.0.9" 178 | "@typescript-eslint/scope-manager" "5.19.0" 179 | "@typescript-eslint/types" "5.19.0" 180 | "@typescript-eslint/typescript-estree" "5.19.0" 181 | eslint-scope "^5.1.1" 182 | eslint-utils "^3.0.0" 183 | 184 | "@typescript-eslint/visitor-keys@5.19.0": 185 | version "5.19.0" 186 | resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz#c84ebc7f6c744707a361ca5ec7f7f64cd85b8af6" 187 | integrity sha512-Ym7zZoMDZcAKWsULi2s7UMLREdVQdScPQ/fKWMYefarCztWlHPFVJo8racf8R0Gc8FAEJ2eD4of8As1oFtnQlQ== 188 | dependencies: 189 | "@typescript-eslint/types" "5.19.0" 190 | eslint-visitor-keys "^3.0.0" 191 | 192 | ansi-styles@^3.2.1: 193 | version "3.2.1" 194 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 195 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 196 | dependencies: 197 | color-convert "^1.9.0" 198 | 199 | array-back@^3.0.1, array-back@^3.1.0: 200 | version "3.1.0" 201 | resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" 202 | integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== 203 | 204 | array-back@^4.0.1, array-back@^4.0.2: 205 | version "4.0.2" 206 | resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" 207 | integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== 208 | 209 | array-differ@^3.0.0: 210 | version "3.0.0" 211 | resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" 212 | integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== 213 | 214 | array-union@^2.1.0: 215 | version "2.1.0" 216 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 217 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 218 | 219 | arrify@^2.0.1: 220 | version "2.0.1" 221 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" 222 | integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== 223 | 224 | balanced-match@^1.0.0: 225 | version "1.0.2" 226 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 227 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 228 | 229 | brace-expansion@^1.1.7: 230 | version "1.1.11" 231 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 232 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 233 | dependencies: 234 | balanced-match "^1.0.0" 235 | concat-map "0.0.1" 236 | 237 | braces@^3.0.2: 238 | version "3.0.2" 239 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 240 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 241 | dependencies: 242 | fill-range "^7.0.1" 243 | 244 | builtin-modules@^3.2.0: 245 | version "3.2.0" 246 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" 247 | integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== 248 | 249 | chalk@^2.4.2: 250 | version "2.4.2" 251 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 252 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 253 | dependencies: 254 | ansi-styles "^3.2.1" 255 | escape-string-regexp "^1.0.5" 256 | supports-color "^5.3.0" 257 | 258 | chrono-node@^2.3.4: 259 | version "2.3.8" 260 | resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.3.8.tgz#c53ab3a9fd55ec1f78623582570de07da2e23c6f" 261 | integrity sha512-cOCUKFHkGKJ//2VK0Vjwd8qh/tDJNraZHYb4DNB48mRUyfL7ag9lCDXgos30fPmV1pha4sP4qHLYItKNS0YpRw== 262 | dependencies: 263 | dayjs "^1.10.0" 264 | 265 | code-block-writer@^10.1.1: 266 | version "10.1.1" 267 | resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f" 268 | integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw== 269 | 270 | color-convert@^1.9.0: 271 | version "1.9.3" 272 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 273 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 274 | dependencies: 275 | color-name "1.1.3" 276 | 277 | color-name@1.1.3: 278 | version "1.1.3" 279 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 280 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 281 | 282 | command-line-args@^5.1.1: 283 | version "5.2.1" 284 | resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" 285 | integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== 286 | dependencies: 287 | array-back "^3.1.0" 288 | find-replace "^3.0.0" 289 | lodash.camelcase "^4.3.0" 290 | typical "^4.0.0" 291 | 292 | command-line-usage@^6.1.0: 293 | version "6.1.3" 294 | resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" 295 | integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== 296 | dependencies: 297 | array-back "^4.0.2" 298 | chalk "^2.4.2" 299 | table-layout "^1.0.2" 300 | typical "^5.2.0" 301 | 302 | concat-map@0.0.1: 303 | version "0.0.1" 304 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 305 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 306 | 307 | dayjs@^1.10.0: 308 | version "1.11.1" 309 | resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.1.tgz#90b33a3dda3417258d48ad2771b415def6545eb0" 310 | integrity sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA== 311 | 312 | debug@^4.3.2: 313 | version "4.3.4" 314 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 315 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 316 | dependencies: 317 | ms "2.1.2" 318 | 319 | deep-extend@~0.6.0: 320 | version "0.6.0" 321 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 322 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 323 | 324 | dir-glob@^3.0.1: 325 | version "3.0.1" 326 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 327 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 328 | dependencies: 329 | path-type "^4.0.0" 330 | 331 | esbuild-android-arm64@0.13.12: 332 | version "0.13.12" 333 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.12.tgz#e1f199dc05405cdc6670c00fb6c793822bf8ae4c" 334 | integrity sha512-TSVZVrb4EIXz6KaYjXfTzPyyRpXV5zgYIADXtQsIenjZ78myvDGaPi11o4ZSaHIwFHsuwkB6ne5SZRBwAQ7maw== 335 | 336 | esbuild-darwin-64@0.13.12: 337 | version "0.13.12" 338 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.12.tgz#f5c59e622955c01f050e5a7ac9c1d41db714b94d" 339 | integrity sha512-c51C+N+UHySoV2lgfWSwwmlnLnL0JWj/LzuZt9Ltk9ub1s2Y8cr6SQV5W3mqVH1egUceew6KZ8GyI4nwu+fhsw== 340 | 341 | esbuild-darwin-arm64@0.13.12: 342 | version "0.13.12" 343 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.12.tgz#8abae74c2956a8aa568fc52c78829338c4a4b988" 344 | integrity sha512-JvAMtshP45Hd8A8wOzjkY1xAnTKTYuP/QUaKp5eUQGX+76GIie3fCdUUr2ZEKdvpSImNqxiZSIMziEiGB5oUmQ== 345 | 346 | esbuild-freebsd-64@0.13.12: 347 | version "0.13.12" 348 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.12.tgz#6ad2ab8c0364ee7dd2d6e324d876a8e60ae75d12" 349 | integrity sha512-r6On/Skv9f0ZjTu6PW5o7pdXr8aOgtFOEURJZYf1XAJs0IQ+gW+o1DzXjVkIoT+n1cm3N/t1KRJfX71MPg/ZUA== 350 | 351 | esbuild-freebsd-arm64@0.13.12: 352 | version "0.13.12" 353 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.12.tgz#6f38155f4c300ac4c8adde1fde3cc6a4440a8294" 354 | integrity sha512-F6LmI2Q1gii073kmBE3NOTt/6zLL5zvZsxNLF8PMAwdHc+iBhD1vzfI8uQZMJA1IgXa3ocr3L3DJH9fLGXy6Yw== 355 | 356 | esbuild-linux-32@0.13.12: 357 | version "0.13.12" 358 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.12.tgz#b1d15e330188a8c21de75c3f0058628a3eefade7" 359 | integrity sha512-U1UZwG3UIwF7/V4tCVAo/nkBV9ag5KJiJTt+gaCmLVWH3bPLX7y+fNlhIWZy8raTMnXhMKfaTvWZ9TtmXzvkuQ== 360 | 361 | esbuild-linux-64@0.13.12: 362 | version "0.13.12" 363 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.12.tgz#25bd64b66162b02348e32d8f12e4c9ee61f1d070" 364 | integrity sha512-YpXSwtu2NxN3N4ifJxEdsgd6Q5d8LYqskrAwjmoCT6yQnEHJSF5uWcxv783HWN7lnGpJi9KUtDvYsnMdyGw71Q== 365 | 366 | esbuild-linux-arm64@0.13.12: 367 | version "0.13.12" 368 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.12.tgz#ba582298457cc5c9ac823a275de117620c06537f" 369 | integrity sha512-sgDNb8kb3BVodtAlcFGgwk+43KFCYjnFOaOfJibXnnIojNWuJHpL6aQJ4mumzNWw8Rt1xEtDQyuGK9f+Y24jGA== 370 | 371 | esbuild-linux-arm@0.13.12: 372 | version "0.13.12" 373 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.12.tgz#6bc81c957bff22725688cc6359c29a25765be09b" 374 | integrity sha512-SyiT/JKxU6J+DY2qUiSLZJqCAftIt3uoGejZ0HDnUM2MGJqEGSGh7p1ecVL2gna3PxS4P+j6WAehCwgkBPXNIw== 375 | 376 | esbuild-linux-mips64le@0.13.12: 377 | version "0.13.12" 378 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.12.tgz#ef3c4aba3e585d847cbade5945a8b4a5c62c7ce2" 379 | integrity sha512-qQJHlZBG+QwVIA8AbTEtbvF084QgDi4DaUsUnA+EolY1bxrG+UyOuGflM2ZritGhfS/k7THFjJbjH2wIeoKA2g== 380 | 381 | esbuild-linux-ppc64le@0.13.12: 382 | version "0.13.12" 383 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.12.tgz#a21fb64e80c38bef06122e48283990fc6db578e1" 384 | integrity sha512-2dSnm1ldL7Lppwlo04CGQUpwNn5hGqXI38OzaoPOkRsBRWFBozyGxTFSee/zHFS+Pdh3b28JJbRK3owrrRgWNw== 385 | 386 | esbuild-netbsd-64@0.13.12: 387 | version "0.13.12" 388 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.12.tgz#1ea7fc8cfce88a20a4047b867ef184049a6641ae" 389 | integrity sha512-D4raxr02dcRiQNbxOLzpqBzcJNFAdsDNxjUbKkDMZBkL54Z0vZh4LRndycdZAMcIdizC/l/Yp/ZsBdAFxc5nbA== 390 | 391 | esbuild-openbsd-64@0.13.12: 392 | version "0.13.12" 393 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.12.tgz#adde32f2f1b05dc4bd4fc544d6ea5a4379f9ca4d" 394 | integrity sha512-KuLCmYMb2kh05QuPJ+va60bKIH5wHL8ypDkmpy47lzwmdxNsuySeCMHuTv5o2Af1RUn5KLO5ZxaZeq4GEY7DaQ== 395 | 396 | esbuild-sunos-64@0.13.12: 397 | version "0.13.12" 398 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.12.tgz#a7ecaf52b7364fbee76dc8aa707fa3e1cff3342c" 399 | integrity sha512-jBsF+e0woK3miKI8ufGWKG3o3rY9DpHvCVRn5eburMIIE+2c+y3IZ1srsthKyKI6kkXLvV4Cf/E7w56kLipMXw== 400 | 401 | esbuild-windows-32@0.13.12: 402 | version "0.13.12" 403 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.12.tgz#a8756033dc905c4b7bea19be69f7ee68809f8770" 404 | integrity sha512-L9m4lLFQrFeR7F+eLZXG82SbXZfUhyfu6CexZEil6vm+lc7GDCE0Q8DiNutkpzjv1+RAbIGVva9muItQ7HVTkQ== 405 | 406 | esbuild-windows-64@0.13.12: 407 | version "0.13.12" 408 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.12.tgz#ae694aa66ca078acb8509b2da31197ed1f40f798" 409 | integrity sha512-k4tX4uJlSbSkfs78W5d9+I9gpd+7N95W7H2bgOMFPsYREVJs31+Q2gLLHlsnlY95zBoPQMIzHooUIsixQIBjaQ== 410 | 411 | esbuild-windows-arm64@0.13.12: 412 | version "0.13.12" 413 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.12.tgz#782c5a8bd6d717ea55aaafe648f9926ca36a4a88" 414 | integrity sha512-2tTv/BpYRIvuwHpp2M960nG7uvL+d78LFW/ikPItO+2GfK51CswIKSetSpDii+cjz8e9iSPgs+BU4o8nWICBwQ== 415 | 416 | esbuild@0.13.12: 417 | version "0.13.12" 418 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.12.tgz#9cac641594bf03cf34145258c093d743ebbde7ca" 419 | integrity sha512-vTKKUt+yoz61U/BbrnmlG9XIjwpdIxmHB8DlPR0AAW6OdS+nBQBci6LUHU2q9WbBobMEIQxxDpKbkmOGYvxsow== 420 | optionalDependencies: 421 | esbuild-android-arm64 "0.13.12" 422 | esbuild-darwin-64 "0.13.12" 423 | esbuild-darwin-arm64 "0.13.12" 424 | esbuild-freebsd-64 "0.13.12" 425 | esbuild-freebsd-arm64 "0.13.12" 426 | esbuild-linux-32 "0.13.12" 427 | esbuild-linux-64 "0.13.12" 428 | esbuild-linux-arm "0.13.12" 429 | esbuild-linux-arm64 "0.13.12" 430 | esbuild-linux-mips64le "0.13.12" 431 | esbuild-linux-ppc64le "0.13.12" 432 | esbuild-netbsd-64 "0.13.12" 433 | esbuild-openbsd-64 "0.13.12" 434 | esbuild-sunos-64 "0.13.12" 435 | esbuild-windows-32 "0.13.12" 436 | esbuild-windows-64 "0.13.12" 437 | esbuild-windows-arm64 "0.13.12" 438 | 439 | escape-string-regexp@^1.0.5: 440 | version "1.0.5" 441 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 442 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 443 | 444 | eslint-scope@^5.1.1: 445 | version "5.1.1" 446 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 447 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 448 | dependencies: 449 | esrecurse "^4.3.0" 450 | estraverse "^4.1.1" 451 | 452 | eslint-utils@^3.0.0: 453 | version "3.0.0" 454 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" 455 | integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== 456 | dependencies: 457 | eslint-visitor-keys "^2.0.0" 458 | 459 | eslint-visitor-keys@^2.0.0: 460 | version "2.1.0" 461 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 462 | integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 463 | 464 | eslint-visitor-keys@^3.0.0: 465 | version "3.3.0" 466 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" 467 | integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== 468 | 469 | esrecurse@^4.3.0: 470 | version "4.3.0" 471 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 472 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 473 | dependencies: 474 | estraverse "^5.2.0" 475 | 476 | estraverse@^4.1.1: 477 | version "4.3.0" 478 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 479 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 480 | 481 | estraverse@^5.2.0: 482 | version "5.3.0" 483 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 484 | integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 485 | 486 | fast-glob@^3.2.5, fast-glob@^3.2.9: 487 | version "3.2.11" 488 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" 489 | integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== 490 | dependencies: 491 | "@nodelib/fs.stat" "^2.0.2" 492 | "@nodelib/fs.walk" "^1.2.3" 493 | glob-parent "^5.1.2" 494 | merge2 "^1.3.0" 495 | micromatch "^4.0.4" 496 | 497 | fastq@^1.6.0: 498 | version "1.13.0" 499 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" 500 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== 501 | dependencies: 502 | reusify "^1.0.4" 503 | 504 | fill-range@^7.0.1: 505 | version "7.0.1" 506 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 507 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 508 | dependencies: 509 | to-regex-range "^5.0.1" 510 | 511 | find-replace@^3.0.0: 512 | version "3.0.0" 513 | resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" 514 | integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== 515 | dependencies: 516 | array-back "^3.0.1" 517 | 518 | functional-red-black-tree@^1.0.1: 519 | version "1.0.1" 520 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 521 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 522 | 523 | glob-parent@^5.1.2: 524 | version "5.1.2" 525 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 526 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 527 | dependencies: 528 | is-glob "^4.0.1" 529 | 530 | globby@^11.0.4: 531 | version "11.1.0" 532 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" 533 | integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 534 | dependencies: 535 | array-union "^2.1.0" 536 | dir-glob "^3.0.1" 537 | fast-glob "^3.2.9" 538 | ignore "^5.2.0" 539 | merge2 "^1.4.1" 540 | slash "^3.0.0" 541 | 542 | has-flag@^3.0.0: 543 | version "3.0.0" 544 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 545 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 546 | 547 | ignore@^5.1.8, ignore@^5.2.0: 548 | version "5.2.0" 549 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" 550 | integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== 551 | 552 | is-absolute@^1.0.0: 553 | version "1.0.0" 554 | resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" 555 | integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== 556 | dependencies: 557 | is-relative "^1.0.0" 558 | is-windows "^1.0.1" 559 | 560 | is-extglob@^2.1.1: 561 | version "2.1.1" 562 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 563 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 564 | 565 | is-glob@^4.0.1, is-glob@^4.0.3: 566 | version "4.0.3" 567 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 568 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 569 | dependencies: 570 | is-extglob "^2.1.1" 571 | 572 | is-negated-glob@^1.0.0: 573 | version "1.0.0" 574 | resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" 575 | integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= 576 | 577 | is-number@^7.0.0: 578 | version "7.0.0" 579 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 580 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 581 | 582 | is-relative@^1.0.0: 583 | version "1.0.0" 584 | resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" 585 | integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== 586 | dependencies: 587 | is-unc-path "^1.0.0" 588 | 589 | is-unc-path@^1.0.0: 590 | version "1.0.0" 591 | resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" 592 | integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== 593 | dependencies: 594 | unc-path-regex "^0.1.2" 595 | 596 | is-windows@^1.0.1: 597 | version "1.0.2" 598 | resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" 599 | integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== 600 | 601 | lodash.camelcase@^4.3.0: 602 | version "4.3.0" 603 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" 604 | integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= 605 | 606 | lodash.clonedeep@^4.5.0: 607 | version "4.5.0" 608 | resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" 609 | integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= 610 | 611 | lru-cache@^6.0.0: 612 | version "6.0.0" 613 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 614 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 615 | dependencies: 616 | yallist "^4.0.0" 617 | 618 | luxon@^1.21.3: 619 | version "1.28.0" 620 | resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" 621 | integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== 622 | 623 | merge2@^1.3.0, merge2@^1.4.1: 624 | version "1.4.1" 625 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 626 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 627 | 628 | micromatch@^4.0.4: 629 | version "4.0.5" 630 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" 631 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 632 | dependencies: 633 | braces "^3.0.2" 634 | picomatch "^2.3.1" 635 | 636 | minimatch@^3.0.4: 637 | version "3.1.2" 638 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 639 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 640 | dependencies: 641 | brace-expansion "^1.1.7" 642 | 643 | mkdirp@^1.0.4: 644 | version "1.0.4" 645 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 646 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 647 | 648 | moment@2.29.1: 649 | version "2.29.1" 650 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" 651 | integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== 652 | 653 | moment@^2.29.1: 654 | version "2.29.2" 655 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" 656 | integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== 657 | 658 | ms@2.1.2: 659 | version "2.1.2" 660 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 661 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 662 | 663 | multimatch@^5.0.0: 664 | version "5.0.0" 665 | resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" 666 | integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== 667 | dependencies: 668 | "@types/minimatch" "^3.0.3" 669 | array-differ "^3.0.0" 670 | array-union "^2.1.0" 671 | arrify "^2.0.1" 672 | minimatch "^3.0.4" 673 | 674 | obsidian@^0.12.2: 675 | version "0.12.17" 676 | resolved "https://registry.yarnpkg.com/obsidian/-/obsidian-0.12.17.tgz#8efe75310d0e3988cdeccfbb176d3a8ff7b363c7" 677 | integrity sha512-YvCAlRym9D8zNPXt6Ez8QubSTVGoChx6lb58zqI13Dcrz3l1lgUO+pcOGDiD5Qa67nzDZLXo3aV2rqkCCpTvGQ== 678 | dependencies: 679 | "@types/codemirror" "0.0.108" 680 | moment "2.29.1" 681 | 682 | path-type@^4.0.0: 683 | version "4.0.0" 684 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 685 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 686 | 687 | picomatch@^2.3.1: 688 | version "2.3.1" 689 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 690 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 691 | 692 | prettier@^2.2.1: 693 | version "2.6.2" 694 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" 695 | integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== 696 | 697 | queue-microtask@^1.2.2: 698 | version "1.2.3" 699 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 700 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 701 | 702 | reduce-flatten@^2.0.0: 703 | version "2.0.0" 704 | resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" 705 | integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== 706 | 707 | regexpp@^3.2.0: 708 | version "3.2.0" 709 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" 710 | integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== 711 | 712 | reusify@^1.0.4: 713 | version "1.0.4" 714 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 715 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 716 | 717 | rrule@^2.6.8: 718 | version "2.6.9" 719 | resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.6.9.tgz#8ee4ee261451e84852741f92ded769245580744a" 720 | integrity sha512-PE4ErZDMfAcRnc1B35bZgPGS9mbn7Z9bKDgk6+XgrIwvBjeWk7JVEYsqKwHYTrDGzsHPtZTpaon8IyeKzAhj5w== 721 | dependencies: 722 | tslib "^1.10.0" 723 | optionalDependencies: 724 | luxon "^1.21.3" 725 | 726 | run-parallel@^1.1.9: 727 | version "1.2.0" 728 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 729 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 730 | dependencies: 731 | queue-microtask "^1.2.2" 732 | 733 | semver@^7.3.5: 734 | version "7.3.7" 735 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" 736 | integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== 737 | dependencies: 738 | lru-cache "^6.0.0" 739 | 740 | slash@^3.0.0: 741 | version "3.0.0" 742 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 743 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 744 | 745 | strip-bom@^3.0.0: 746 | version "3.0.0" 747 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 748 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 749 | 750 | strip-json-comments@^2.0.0: 751 | version "2.0.1" 752 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 753 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 754 | 755 | supports-color@^5.3.0: 756 | version "5.5.0" 757 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 758 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 759 | dependencies: 760 | has-flag "^3.0.0" 761 | 762 | table-layout@^1.0.2: 763 | version "1.0.2" 764 | resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" 765 | integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== 766 | dependencies: 767 | array-back "^4.0.1" 768 | deep-extend "~0.6.0" 769 | typical "^5.2.0" 770 | wordwrapjs "^4.0.0" 771 | 772 | to-regex-range@^5.0.1: 773 | version "5.0.1" 774 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 775 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 776 | dependencies: 777 | is-number "^7.0.0" 778 | 779 | ts-auto-guard@^1.0.0-alpha.26: 780 | version "1.0.0" 781 | resolved "https://registry.yarnpkg.com/ts-auto-guard/-/ts-auto-guard-1.0.0.tgz#3a5b60039ea45dd471324b22d714b3e295669fc5" 782 | integrity sha512-LDjuDegeYj8DQywSmLVJ/yrLjOH+rpWjG+aj+yRRplDfcsLw0R+lEHyyTLV/tu+MchUAx0mhQB3AQpmyLPZMEg== 783 | dependencies: 784 | "@types/command-line-args" "^5.0.0" 785 | "@types/command-line-usage" "^5.0.1" 786 | command-line-args "^5.1.1" 787 | command-line-usage "^6.1.0" 788 | ts-morph "^9.1.0" 789 | tsconfig "^7.0.0" 790 | 791 | ts-morph@^9.1.0: 792 | version "9.1.0" 793 | resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-9.1.0.tgz#10d2088387c71f3c674f82492a3cec1e3538f0dd" 794 | integrity sha512-sei4u651MBenr27sD6qLDXN3gZ4thiX71E3qV7SuVtDas0uvK2LtgZkIYUf9DKm/fLJ6AB/+yhRJ1vpEBJgy7Q== 795 | dependencies: 796 | "@dsherret/to-absolute-glob" "^2.0.2" 797 | "@ts-morph/common" "~0.7.0" 798 | code-block-writer "^10.1.1" 799 | 800 | tsconfig@^7.0.0: 801 | version "7.0.0" 802 | resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" 803 | integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== 804 | dependencies: 805 | "@types/strip-bom" "^3.0.0" 806 | "@types/strip-json-comments" "0.0.30" 807 | strip-bom "^3.0.0" 808 | strip-json-comments "^2.0.0" 809 | 810 | tslib@^1.10.0, tslib@^1.8.1: 811 | version "1.14.1" 812 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 813 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 814 | 815 | tslib@^2.0.3: 816 | version "2.3.1" 817 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" 818 | integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== 819 | 820 | tsutils@^3.21.0: 821 | version "3.21.0" 822 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 823 | integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 824 | dependencies: 825 | tslib "^1.8.1" 826 | 827 | typescript@^4.2.4: 828 | version "4.6.3" 829 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" 830 | integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== 831 | 832 | typescript@~4.1.3: 833 | version "4.1.6" 834 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138" 835 | integrity sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow== 836 | 837 | typical@^4.0.0: 838 | version "4.0.0" 839 | resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" 840 | integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== 841 | 842 | typical@^5.2.0: 843 | version "5.2.0" 844 | resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" 845 | integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== 846 | 847 | unc-path-regex@^0.1.2: 848 | version "0.1.2" 849 | resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" 850 | integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= 851 | 852 | wordwrapjs@^4.0.0: 853 | version "4.0.1" 854 | resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" 855 | integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== 856 | dependencies: 857 | reduce-flatten "^2.0.0" 858 | typical "^5.2.0" 859 | 860 | yallist@^4.0.0: 861 | version "4.0.0" 862 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 863 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 864 | --------------------------------------------------------------------------------