├── .commitlintrc.yml ├── .czrc ├── .env.example ├── .eslintignore ├── .eslintrc.yml ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── prepare-commit-msg ├── .node-version ├── .npmrc ├── .prettierrc.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── docs ├── README_EN.md └── images │ └── sample_image.png ├── lib └── worker │ └── textlint-worker.js ├── manifest.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── scripts ├── generate-worker.sh └── textlintrc.json ├── src ├── extension │ └── tooltip │ │ ├── TooltipView.tsx │ │ ├── helpers.ts │ │ ├── tooltipField.ts │ │ ├── underlineExtension.ts │ │ └── underlineStateField.ts ├── index.ts ├── lib │ ├── sentry.ts │ └── textlint.ts ├── rules.ts ├── settings │ └── index.ts └── styles.css ├── tailwind.config.js ├── tsconfig.json ├── versions.json └── webpack.config.ts /.commitlintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - '@commitlint/config-conventional' 3 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "@commitlint/cz-commitlint" 3 | } 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SENTRY_AUTH_TOKEN=changeme 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build 3 | lib 4 | main.js 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | parserOptions: 3 | ecmaVersion: latest 4 | sourceType: module 5 | env: 6 | es2018: true 7 | node: true 8 | extends: eslint:recommended 9 | ignorePatterns: 10 | - 'node_modules/*' 11 | rules: {} 12 | settings: 13 | import/ignore: 14 | - 'src/worker/worker/textlint-worker\\.js$' 15 | overrides: 16 | - files: 17 | - '**/*.ts' 18 | - '**/*.tsx' 19 | parser: '@typescript-eslint/parser' 20 | parserOptions: 21 | project: ./tsconfig.json 22 | plugins: 23 | - prefer-arrow 24 | extends: 25 | - plugin:import/errors 26 | - plugin:import/warnings 27 | - plugin:import/typescript 28 | - plugin:react/recommended 29 | - plugin:jsx-a11y/recommended 30 | - plugin:@typescript-eslint/recommended 31 | - plugin:prettier/recommended 32 | rules: 33 | '@typescript-eslint/explicit-function-return-type': 34 | - 'off' 35 | '@typescript-eslint/explicit-module-boundary-types': 36 | - 'off' 37 | '@typescript-eslint/no-empty-function': 38 | - 'off' 39 | '@typescript-eslint/no-explicit-any': 40 | - 'off' 41 | '@typescript-eslint/no-unused-vars': 42 | - warn 43 | - args: after-used 44 | argsIgnorePattern: _ 45 | ignoreRestSiblings: false 46 | vars: all 47 | varsIgnorePattern: _ 48 | '@typescript-eslint/no-use-before-define': 49 | - error 50 | '@typescript-eslint/prefer-nullish-coalescing': error 51 | '@typescript-eslint/prefer-optional-chain': error 52 | import/default: off 53 | import/extensions: 54 | - error 55 | - ignorePackages 56 | - ts: never 57 | tsx: never 58 | import/first: error 59 | import/newline-after-import: error 60 | import/no-duplicates: error 61 | import/no-named-as-default: off 62 | import/no-named-as-default-member: off 63 | import/no-unresolved: off 64 | import/order: 65 | - error 66 | - alphabetize: 67 | caseInsensitive: true 68 | order: asc 69 | groups: 70 | - builtin 71 | - external 72 | - internal 73 | - parent 74 | - sibling 75 | - index 76 | - object 77 | newlines-between: always 78 | import/prefer-default-export: off 79 | linebreak-style: 80 | - error 81 | - unix 82 | lines-between-class-members: 83 | - error 84 | - always 85 | - exceptAfterSingleLine: true 86 | no-unused-vars: off 87 | no-use-before-define: off 88 | no-void: 89 | - error 90 | - allowAsStatement: true 91 | padding-line-between-statements: 92 | - error 93 | - blankLine: always 94 | next: return 95 | prev: '*' 96 | prefer-arrow/prefer-arrow-functions: 97 | - error 98 | - classPropertiesAllowed: false 99 | disallowPrototype: true 100 | singleReturnOnly: false 101 | prettier/prettier: 102 | - error 103 | - {} 104 | - usePrettierrc: true 105 | react/function-component-definition: 106 | - 2 107 | - namedComponents: arrow-function 108 | react/jsx-filename-extension: 109 | - 1 110 | - extensions: 111 | - .tsx 112 | react/prop-types: off 113 | react/react-in-jsx-scope: off 114 | settings: 115 | import/resolver: 116 | typescript: {} 117 | react: 118 | version: detect 119 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian Plugin 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | workflow_dispatch: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Set node version 14 | run: echo "NODE_VERSION=$(cat .node-version)" >> $GITHUB_ENV 15 | - name: Set pnpm version 16 | shell: bash 17 | run: | 18 | pnpm=$(cat package.json | jq -r .packageManager | cut -f 2 -d "@") 19 | echo "PNPM_VERSION=${pnpm}" >> $GITHUB_ENV 20 | - uses: pnpm/action-setup@v2 21 | name: Install pnpm 22 | with: 23 | version: ${{ env.PNPM_VERSION }} 24 | - name: Setup Node 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ env.NODE_VERSION }} 28 | cache: 'pnpm' 29 | - name: Install Dependencies 30 | run: pnpm install --frozen-lockfile 31 | - name: Build 32 | id: build 33 | env: 34 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 35 | run: pnpm build:prod 36 | - name: Package 37 | run: | 38 | mkdir ${{ github.event.repository.name }} 39 | cp main.js manifest.json styles.css README.md ${{ github.event.repository.name }} 40 | zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }} 41 | # Create the release on github 42 | - name: Release 43 | uses: 'marvinpinto/action-automatic-releases@latest' 44 | with: 45 | repo_token: '${{ secrets.GITHUB_TOKEN }}' 46 | prerelease: false 47 | files: | 48 | ${{ github.event.repository.name }}.zip 49 | main.js 50 | manifest.json 51 | styles.css 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode 2 | .vscode/* 3 | !/.vscode/settings.json 4 | 5 | # npm 6 | node_modules 7 | 8 | # build 9 | dist 10 | main.js 11 | main.js.map 12 | 13 | styles.css 14 | 15 | # obsidian 16 | data.json 17 | 18 | # env 19 | .env 20 | 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx cz --hook || true 5 | 6 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18.12.1 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | auto-install-peers=true 3 | strict-peer-dependencies=false 4 | tag-version-prefix="" 5 | message="chore(release): %s :tada:" 6 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | arrowParens: always 2 | bracketSameLine: true 3 | bracketSpacing: true 4 | endOfLine: auto 5 | htmlWhitespaceSensitivity: ignore 6 | printWidth: 80 7 | singleQuote: true 8 | tabWidth: 2 9 | trailingComma: all 10 | useTabs: false 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.2](https://github.com/shivase/obsidian-textlint/compare/0.0.1...0.0.2) (2022-12-30) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * change to popup message text color because it's the same color as the background ([03be81a](https://github.com/shivase/obsidian-textlint/commit/03be81ac5227c6976944b2f233534554fd081a53)) 7 | 8 | ## 0.0.1 (2022-12-30) 9 | 10 | 11 | ### Features 12 | 13 | * initial release ([42c7c73](https://github.com/shivase/obsidian-textlint/commit/42c7c7303d793721ba31917684ff01ef6c0106f7)) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Victor Tao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Textlint 2 | 3 | [![download](https://img.shields.io/github/downloads/shivase/obsidian-textlint/total)](https://github.com/shivase/obsidian-textlint/releases) 4 | 5 | [README in English is Here](https://github.com/shivase/obsidian-textlint/blob/master/docs/README_EN.md) 6 | 7 | Obsidian 用の[textlint](https://github.com/textlint/textlint)プラグインです。 8 | 9 | 導入することで、日本語の文章校正を行ってくれます。 10 | (textlint 自体は日本語だけではなく自然言語一般を対象としていますが、現時点では日本語向けの textlint プラグインがメインです) 11 | 12 | ![sample image](docs/images/sample_image.png) 13 | 14 | ## インストール方法 15 | 16 | 現時点ではコミュニティプラグインとして登録されていないため、マニュアルでの導入になります。 17 | 18 | ### マニュアルインストール 19 | 20 | 1. [latest release](https://github.com/shivase/obsidian-textlint/releases/latest) より最新版の zip をダウンロード 21 | 1. `main.js`, `styles.css`, `manifest.json` を `VaultFolder/.obsidian/plugins/obsidian-textlint/` 配下にコピー (VaultFolder は各自の Vault ディレクトリを指定) 22 | 1. Obsidian をリロード 23 | 24 | ## 使い方 25 | 26 | `COMMAND + P`を押してコマンド入力画面を呼び出し、`textlint`を入力して実行して下さい。 27 | 28 | ## 有効化されている textlint のプラグイン 29 | 30 | このプラグインで動作する、textlint のデフォルトプラグインは以下の通りです。 31 | [@shivase](https://github.com/shivase) の使っているものを入れているので、あまり万人向けとして精査していません。 32 | 33 | - [@textlint-ja/no-synonyms](https://github.com/textlint-ja/textlint-rule-no-synonyms) 34 | - [@textlint-ja/textlint-rule-no-dropping-i](https://github.com/textlint-ja/textlint-rule-no-dropping-i) 35 | - [@textlint-ja/textlint-rule-no-insert-dropping-sa](https://github.com/textlint-ja/textlint-rule-no-insert-dropping-sa) 36 | - [abbr-within-parentheses](https://github.com/azu/textlint-rule-abbr-within-parentheses) 37 | - [footnote-order](https://github.com/textlint-rule/textlint-rule-footnote-order) 38 | - [ja-hiragana-keishikimeishi](https://github.com/lostandfound/textlint-rule-ja-hiragana-keishikimeishi) 39 | - [no-mixed-zenkaku-and-hankaku-alphabet](https://github.com/textlint-ja/textlint-rule-no-mixed-zenkaku-and-hankaku-alphabet) 40 | - [period-in-list-item](https://github.com/textlint-rule/textlint-rule-period-in-list-item) 41 | - [prefer-tari-tari](https://github.com/textlint-ja/textlint-rule-prefer-tari-tari) 42 | - [preset-ja-spacing](https://github.com/textlint-ja/textlint-rule-preset-ja-spacing) 43 | - [preset-ja-technical-writing](https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing) 44 | - [preset-jtf-style](https://github.com/textlint-ja/textlint-rule-preset-JTF-style) 45 | - [textlint-rule-date-weekday-mismatch](https://github.com/textlint-rule/textlint-rule-date-weekday-mismatch) 46 | - [textlint-rule-ja-no-inappropriate-words](https://github.com/textlint-ja/textlint-rule-ja-no-inappropriate-words) 47 | - [textlint-rule-ja-no-orthographic-variants](https://github.com/textlint-ja/textlint-rule-ja-no-orthographic-variants) 48 | - [textlint-rule-use-si-units](https://github.com/kn1cht/textlint-rule-use-si-units) 49 | - [textlint-rule-write-good](https://github.com/textlint-rule/textlint-rule-write-good) 50 | 51 | **後述する本プラグインの設定画面より、各プラグインの有効化・無効化、及び詳細の設定を追加できるようになっています。** 52 | 53 | ## 設定 54 | 55 | 現時点の設定できる項目は以下の通り 56 | 57 | - Lint on save 58 | ON にすることで、ファイルの保存時に自動的に Textlint が実行されます 59 | - Folder to ignore 60 | 対象外となるフォルダを設定できます。箇条書きで対象フォルダを記載して下さい 61 | - Override textlintrc 62 | textlint の設定を上書きたい場合に**textlintrc.json**形式で記載して下さい。デフォルト設定とマージ処理されるため、記載の無い項目はデフォルト値が優先されます。 63 | 実際に設定しているデフォルト値はこちら -> [textlintrc.json](https://github.com/shivase/obsidian-textlint/blob/master/scripts/textlintrc.json) 64 | 65 | ## TODO 66 | 67 | - とにかくテスト書く 68 | - textlint が推奨する値に変更するボタンを追加する 69 | - エラーを無視できるようにする 70 | - textlint の設定を json 形式で上書きさせるのではなく、それぞれ個別に ON/OFF できるようにする 71 | - 無理矢理 react 部分をどうにかする 72 | - tooltip の位置が右端にでた時に隠れるのを修正する 73 | 74 | ## Plugin Development 75 | 76 | 1. cd `VaultFolder/.obsidian/plugins/` 77 | 1. Clone this repo. 78 | 1. `pnpm` to install dependencies 79 | 1. `pnpm build:dev` to build main program 80 | 1. `pnpm generate-worker:dev` to build textlint worker. 81 | command this after installing textlint plugins or updating scripts/textlintrc.json. 82 | 83 | ## Contact 84 | 85 | 質問や要望・バグ報告は、気軽に Github issue や Twitter([@shivasek5](https://twitter.com/shivasek5))の DM 等をご利用下さい。 86 | 87 | If you want to support me ([shivase](https://github.com/shivase)) you can support me on Ko-fi 88 | 89 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/shivase) 90 | -------------------------------------------------------------------------------- /docs/README_EN.md: -------------------------------------------------------------------------------- 1 | # Obsidian Textlint 2 | 3 | This is a [textlint](https://github.com/textlint/textlint) plugin for Obsidian. 4 | 5 | By installing this plugin, you can proofread Japanese text. 6 | (textlint itself targets not only Japanese but also natural languages in general, but at the moment it is mainly a textlint plugin for Japanese.) 7 | 8 | ![sample image](./images/sample_image.png) 9 | 10 | ## How to install 11 | 12 | At this time, it is not registered as a community plugin, so it must be installed manually. 13 | 14 | ## Manual installation 15 | 16 | 1. Download the [latest release](https://github.com/shivase/obsidian-textlint/releases/latest) 17 | 1. Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/obsidian-textlint/`. 18 | 1. Reload Obsidian 19 | 20 | ## Usage 21 | 22 | Press `COMMAND + P` to call up the command input screen, enter `textlint` and run it. 23 | 24 | ## Activated textlint plugin 25 | 26 | Here are the default textlint plugins that work with this plugin. 27 | 28 | - [@textlint-ja/no-synonyms](https://github.com/textlint-ja/textlint-rule-no-synonyms) 29 | - [@textlint-ja/textlint-rule-no-dropping-i](https://github.com/textlint-ja/textlint-rule-no-dropping-i) 30 | - [@textlint-ja/textlint-rule-no-insert-dropping-sa](https://github.com/textlint-ja/textlint-rule-no-insert-dropping-sa) 31 | - [abbr-within-parentheses](https://github.com/azu/textlint-rule-abbr-within-parentheses) 32 | - [footnote-order](https://github.com/textlint-rule/textlint-rule-footnote-order) 33 | - [ja-hiragana-keishikimeishi](https://github.com/lostandfound/textlint-rule-ja-hiragana-keishikimeishi) 34 | - [no-mixed-zenkaku-and-hankaku-alphabet](https://github.com/textlint-ja/textlint-rule-no-mixed-zenkaku-and-hankaku-alphabet) 35 | - [period-in-list-item](https://github.com/textlint-rule/textlint-rule-period-in-list-item) 36 | - [prefer-tari-tari](https://github.com/textlint-ja/textlint-rule-prefer-tari-tari) 37 | - [preset-ja-spacing](https://github.com/textlint-ja/textlint-rule-preset-ja-spacing) 38 | - [preset-ja-technical-writing](https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing) 39 | - [preset-jtf-style](https://github.com/textlint-ja/textlint-rule-preset-JTF-style) 40 | - [textlint-rule-date-weekday-mismatch](https://github.com/textlint-rule/textlint-rule-date-weekday-mismatch) 41 | - [textlint-rule-ja-no-inappropriate-words](https://github.com/textlint-ja/textlint-rule-ja-no-inappropriate-words) 42 | - [textlint-rule-ja-no-orthographic-variants](https://github.com/textlint-ja/textlint-rule-ja-no-orthographic-variants) 43 | - [textlint-rule-use-si-units](https://github.com/kn1cht/textlint-rule-use-si-units) 44 | - [textlint-rule-write-good](https://github.com/textlint-rule/textlint-rule-write-good) 45 | 46 | ## Plugin settings 47 | 48 | Currently, the following settings are available 49 | 50 | - Lint on save 51 | If ON, Textlint will be automatically executed when the file is saved. 52 | - Folder to ignore 53 | You can set exclude folders 54 | - Override textlintrc 55 | If you want to override the textlint settings, please describe in **textlintrc.json** format. The values are merged with the default settings. 56 | The default are here -> [textlintrc.json](https://github.com/shivase/obsidian-textlint/blob/master/scripts/textlintrc.json) 57 | 58 | ## Requests and bug reports 59 | 60 | Please feel free to comment at [issue](https://github.com/shivase/obsidian-textlint/issues) 61 | 62 | Some textlint plugins are not web-supported, so we may not be able to fulfill your request. 63 | 64 | ## Plugin Development 65 | 66 | 1. cd `VaultFolder/.obsidian/plugins/` 67 | 1. Clone this repo. 68 | 1. `yarn` to install dependencies 69 | 1. `yarn build:dev` to build main program 70 | 1. `yarn generate-worker:dev` to build textlint worker. 71 | command this after installing textlint plugins or updating scripts/textlintrc.json. 72 | 73 | use [pjeby/hot-reload: Automatically reload Obsidian plugins in development when their files are changed](https://github.com/pjeby/hot-reload) is recommended to develop plugins 74 | -------------------------------------------------------------------------------- /docs/images/sample_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shivase/obsidian-textlint/d3751c7ecc3f28d37c0ca3d559f7ca00265bf79a/docs/images/sample_image.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-textlint", 3 | "name": "Textlint", 4 | "version": "0.0.2", 5 | "minAppVersion": "0.15.0", 6 | "description": "Proofread your notes by textlint(https://github.com/textlint/textlint)", 7 | "author": "shivase", 8 | "authorUrl": "https://github.com/shivase", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-textlint", 3 | "version": "0.0.2", 4 | "main": "src/index.ts", 5 | "description": "Proofread your notes by textlint(https://github.com/textlint/textlint)", 6 | "homepage": "https://github.com/shivase/obsidian-textlint", 7 | "keywords": [ 8 | "obsidian", 9 | "textlint" 10 | ], 11 | "author": "shivase", 12 | "scripts": { 13 | "prepare": "husky install", 14 | "generate-css:dev": "NODE_ENV=development postcss src/styles.css -o styles.css", 15 | "generate-css:prod": "NODE_ENV=production postcss src/styles.css -o styles.css", 16 | "generate-worker:dev": "MODE=development ./scripts/generate-worker.sh", 17 | "generate-worker:prod": "./scripts/generate-worker.sh", 18 | "lint": "run-p lint:*", 19 | "lint:eslint": "eslint --fix 'src/**/*.{js,jsx,ts,tsx,json,html}'", 20 | "lint:prettier": "prettier --no-error-on-unmatched-pattern --write src/**/*.{json,js,jsx,ts,tsx,html}", 21 | "build:dev": "pnpm generate-css:dev && webpack && cp ./dist/main.js* .", 22 | "build:prod": "pnpm generate-css:prod && webpack && cp ./dist/main.js* .", 23 | "version": "node version-bump.mjs && git add manifest.json versions.json", 24 | "changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s" 25 | }, 26 | "packageManager": "pnpm@7.20.0", 27 | "engines": { 28 | "pnpm": ">=7" 29 | }, 30 | "dependencies": { 31 | "@codemirror/language": "https://github.com/lishid/cm-language", 32 | "@codemirror/state": "^6.1.4", 33 | "@codemirror/view": "^6.7.0", 34 | "obsidian": "0.16.3" 35 | }, 36 | "devDependencies": { 37 | "@commitlint/cli": "^17.3.0", 38 | "@commitlint/config-conventional": "^17.3.0", 39 | "@commitlint/cz-commitlint": "^17.3.0", 40 | "@sentry/browser": "^7.24.2", 41 | "@sentry/tracing": "^7.24.2", 42 | "@sentry/webpack-plugin": "^1.20.0", 43 | "@textlint-ja/textlint-rule-no-dropping-i": "^2.0.0", 44 | "@textlint-ja/textlint-rule-no-insert-dropping-sa": "^2.0.1", 45 | "@textlint-ja/textlint-rule-no-synonyms": "^1.3.0", 46 | "@textlint/script-compiler": "^0.12.1", 47 | "@textlint/types": "^12.2.3", 48 | "@types/eslint": "^8.4.10", 49 | "@types/eslint-plugin-prettier": "^3.1.0", 50 | "@types/node": "^18.11.12", 51 | "@types/postcss-import": "^14.0.0", 52 | "@types/prettier": "^2.7.1", 53 | "@types/react": "^18.0.26", 54 | "@types/react-dom": "^18.0.9", 55 | "@types/uglify-js": "^3.17.1", 56 | "@types/webpack-node-externals": "^2.5.3", 57 | "@typescript-eslint/eslint-plugin": "5.46.0", 58 | "@typescript-eslint/parser": "5.46.0", 59 | "autoprefixer": "^10.4.13", 60 | "builtin-modules": "3.3.0", 61 | "clsx": "^1.2.1", 62 | "commitizen": "^4.2.5", 63 | "conventional-changelog-cli": "^2.2.2", 64 | "copy-webpack-plugin": "^11.0.0", 65 | "cssnano": "^5.1.14", 66 | "cz-customizable": "^7.0.0", 67 | "daisyui": "^2.43.0", 68 | "dotenv": "^16.0.3", 69 | "esbuild": "0.16.3", 70 | "eslint": "^8.29.0", 71 | "eslint-config-prettier": "^8.5.0", 72 | "eslint-import-resolver-typescript": "^3.5.2", 73 | "eslint-plugin-import": "^2.26.0", 74 | "eslint-plugin-jsx-a11y": "^6.6.1", 75 | "eslint-plugin-prefer-arrow": "^1.2.3", 76 | "eslint-plugin-prettier": "^4.2.1", 77 | "eslint-plugin-react": "^7.31.11", 78 | "eslint-plugin-tailwindcss": "^3.7.1", 79 | "husky": "^8.0.2", 80 | "lint-staged": "^13.1.0", 81 | "npm-run-all": "^4.1.5", 82 | "postcss": "^8.4.19", 83 | "postcss-cli": "^10.1.0", 84 | "postcss-import": "^15.1.0", 85 | "prettier": "^2.8.0", 86 | "prettier-plugin-tailwindcss": "^0.2.0", 87 | "react": "^18.2.0", 88 | "react-dom": "^18.2.0", 89 | "sudachi-synonyms-dictionary": "^12.0.0", 90 | "tailwindcss": "^3.2.4", 91 | "terser-webpack-plugin": "^5.3.6", 92 | "textlint": "^12.2.4", 93 | "textlint-rule-abbr-within-parentheses": "^1.0.2", 94 | "textlint-rule-date-weekday-mismatch": "^1.0.6", 95 | "textlint-rule-footnote-order": "^1.0.3", 96 | "textlint-rule-ja-hiragana-keishikimeishi": "^1.1.0", 97 | "textlint-rule-ja-no-inappropriate-words": "^2.0.0", 98 | "textlint-rule-ja-no-orthographic-variants": "^2.0.0", 99 | "textlint-rule-no-mixed-zenkaku-and-hankaku-alphabet": "^1.0.1", 100 | "textlint-rule-period-in-list-item": "^0.3.3", 101 | "textlint-rule-prefer-tari-tari": "^1.0.3", 102 | "textlint-rule-preset-ja-spacing": "^2.2.0", 103 | "textlint-rule-preset-ja-technical-writing": "^7.0.0", 104 | "textlint-rule-preset-jtf-style": "^2.3.13", 105 | "textlint-rule-use-si-units": "^1.0.2", 106 | "textlint-rule-write-good": "^2.0.0", 107 | "ts-loader": "^9.4.2", 108 | "ts-node": "^10.9.1", 109 | "tslib": "2.4.1", 110 | "typescript": "4.9.4", 111 | "uglify-js": "^3.17.4", 112 | "webpack": "^5.75.0", 113 | "webpack-cli": "^5.0.1", 114 | "webpack-node-externals": "^3.0.0", 115 | "worker-loader": "^3.0.8" 116 | }, 117 | "lint-staged": { 118 | "src/**/*.{ts,tsx,js,jsx,json,html}": [ 119 | "prettier --write --loglevel=error", 120 | "eslint --fix" 121 | ] 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const cssnano = require('cssnano'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-import'), 6 | require('tailwindcss'), 7 | process.env.NODE_ENV === 'production' ? require('autoprefixer') : null, 8 | process.env.NODE_ENV === 'production' 9 | ? cssnano({ preset: 'default' }) 10 | : null, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /scripts/generate-worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CWD=$(pwd) 4 | 5 | VERSION="0.0.1" 6 | OUTPUT_DIR="lib/worker" 7 | TEXTLINTRC_PATH="scripts/textlintrc.json" 8 | 9 | if [ ! -e "${CWD}/${TEXTLINTRC_PATH}" ]; then 10 | echo "textlintrc.yml does not exist." 11 | exit 1 12 | fi 13 | 14 | if [[ -z ${MODE} ]]; 15 | then 16 | MODE="production" 17 | fi 18 | 19 | echo "RUNNINNG AS ${MODE} mode..." 20 | 21 | npx textlint-script-compiler \ 22 | --cwd "${CWD}" \ 23 | --mode ${MODE} \ 24 | --output-dir ${OUTPUT_DIR} \ 25 | --textlintrc ${TEXTLINTRC_PATH} 26 | 27 | -------------------------------------------------------------------------------- /scripts/textlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@textlint-ja/no-synonyms": true, 4 | "@textlint-ja/textlint-rule-no-dropping-i": true, 5 | "@textlint-ja/textlint-rule-no-insert-dropping-sa": true, 6 | "abbr-within-parentheses": true, 7 | "footnote-order": true, 8 | "ja-hiragana-keishikimeishi": true, 9 | "no-mixed-zenkaku-and-hankaku-alphabet": true, 10 | "period-in-list-item": true, 11 | "prefer-tari-tari": true, 12 | "preset-ja-spacing": true, 13 | "preset-ja-technical-writing": true, 14 | "preset-jtf-style": true, 15 | "textlint-rule-date-weekday-mismatch": true, 16 | "textlint-rule-ja-no-inappropriate-words": true, 17 | "textlint-rule-ja-no-orthographic-variants": true, 18 | "textlint-rule-use-si-units": true, 19 | "textlint-rule-write-good": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/extension/tooltip/TooltipView.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { FC } from 'react'; 3 | import React from 'react'; 4 | 5 | import type { UnderlineEffect } from './underlineStateField'; 6 | 7 | export type TooltipViewProps = { 8 | underline: UnderlineEffect; 9 | }; 10 | 11 | export const TooltipView: FC = (props) => { 12 | const underline = props.underline; 13 | const lintMessage = underline.match; 14 | 15 | const type = lintMessage.type; 16 | const ruleId = lintMessage.ruleId; 17 | const message = lintMessage.message; 18 | const severity = lintMessage.severity; 19 | 20 | let severityString = 'info'; 21 | switch (severity) { 22 | case 1: 23 | severityString = 'warning'; 24 | break; 25 | case 2: 26 | severityString = 'error'; 27 | break; 28 | } 29 | 30 | /* TODO: Add replace and ignore button */ 31 | return ( 32 |
39 |
40 |
41 |
48 | {severityString} 49 |
50 | {type}:{ruleId} 51 |
52 |

53 | {message} 54 |

55 |
56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/extension/tooltip/helpers.ts: -------------------------------------------------------------------------------- 1 | export const ignoreListRegEx = 2 | /frontmatter|code|math|templater|blockid|hashtag|internal/; 3 | 4 | export const hashString = (value: string) => { 5 | let hash = 0; 6 | if (value.length === 0) { 7 | return hash; 8 | } 9 | for (let i = 0; i < value.length; i++) { 10 | const char = value.charCodeAt(i); 11 | hash = (hash << 5) - hash + char; 12 | hash &= hash; // Convert to 32bit integer 13 | } 14 | 15 | return hash; 16 | }; 17 | -------------------------------------------------------------------------------- /src/extension/tooltip/tooltipField.ts: -------------------------------------------------------------------------------- 1 | import { StateField, EditorState } from '@codemirror/state'; 2 | import { Tooltip, showTooltip } from '@codemirror/view'; 3 | import * as React from 'react'; 4 | import { renderToStaticMarkup } from 'react-dom/server'; 5 | 6 | import type TextlintPlugin from '../..'; 7 | 8 | import { TooltipView } from './TooltipView'; 9 | import { UnderlineEffect, underlineField } from './underlineStateField'; 10 | 11 | const constructTooltipView = (underline: UnderlineEffect) => { 12 | return createDiv('', (element) => { 13 | /* TODO: this rendering way is not good. need to fix */ 14 | const staticElement = renderToStaticMarkup( 15 | React.createElement(TooltipView, { underline: underline }), 16 | ); 17 | element.innerHTML = staticElement; 18 | }); 19 | }; 20 | 21 | const getTooltip = ( 22 | tooltips: readonly Tooltip[], 23 | plugin: TextlintPlugin, 24 | state: EditorState, 25 | ): readonly Tooltip[] => { 26 | const underlines = state.field(underlineField); 27 | 28 | if (underlines.size === 0 || state.selection.ranges.length > 1) { 29 | return []; 30 | } 31 | 32 | let primaryUnderline: UnderlineEffect | null = null; 33 | 34 | underlines.between( 35 | state.selection.main.from, 36 | state.selection.main.to, 37 | (from, to, value) => { 38 | primaryUnderline = { 39 | from, 40 | to, 41 | match: value.spec.match, 42 | } as UnderlineEffect; 43 | }, 44 | ); 45 | 46 | if (primaryUnderline !== null) { 47 | const { from, to } = primaryUnderline as UnderlineEffect; 48 | 49 | if (tooltips.length) { 50 | const tooltip = tooltips[0]; 51 | 52 | if (tooltip.pos === from && tooltip.end === to) { 53 | return tooltips; 54 | } 55 | } 56 | 57 | return [ 58 | { 59 | pos: from, 60 | end: to, 61 | above: false, 62 | strictSide: false, 63 | arrow: false, 64 | create: (_) => { 65 | return { 66 | dom: constructTooltipView(primaryUnderline as UnderlineEffect), 67 | }; 68 | }, 69 | }, 70 | ]; 71 | } 72 | 73 | return []; 74 | }; 75 | 76 | export const buildTooltipField = (plugin: TextlintPlugin) => { 77 | return StateField.define({ 78 | create: (state) => getTooltip([], plugin, state), 79 | update: (tooltips, tr) => getTooltip(tooltips, plugin, tr.state), 80 | provide: (f) => showTooltip.computeN([f], (state) => state.field(f)), 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /src/extension/tooltip/underlineExtension.ts: -------------------------------------------------------------------------------- 1 | import { tooltips } from '@codemirror/view'; 2 | 3 | import type TextlintPlugin from '../../'; 4 | 5 | import { buildTooltipField } from './tooltipField'; 6 | import { underlineField } from './underlineStateField'; 7 | 8 | export const buildUnderlineExtension = (plugin: TextlintPlugin) => { 9 | return [ 10 | tooltips({ 11 | position: 'absolute', 12 | tooltipSpace: (view) => { 13 | const rect = view.dom.getBoundingClientRect(); 14 | 15 | return { 16 | top: rect.top, 17 | left: rect.left, 18 | bottom: rect.bottom, 19 | right: rect.right, 20 | }; 21 | }, 22 | }), 23 | underlineField, 24 | buildTooltipField(plugin), 25 | ]; 26 | }; 27 | -------------------------------------------------------------------------------- /src/extension/tooltip/underlineStateField.ts: -------------------------------------------------------------------------------- 1 | import { syntaxTree, tokenClassNodeProp } from '@codemirror/language'; 2 | import { StateField, StateEffect } from '@codemirror/state'; 3 | import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; 4 | import type { Tree } from '@lezer/common'; 5 | import type { TextlintMessage } from '@textlint/types'; 6 | 7 | import { ignoreListRegEx } from './helpers'; 8 | 9 | export interface UnderlineEffect { 10 | from: number; 11 | to: number; 12 | match: TextlintMessage; 13 | reset?: boolean; 14 | } 15 | 16 | export const addUnderline = StateEffect.define(); 17 | export const resetUnderline = StateEffect.define(); 18 | 19 | const filterUnderlines = ( 20 | decorationStart: number, 21 | decorationEnd: number, 22 | rangeStart: number, 23 | rangeEnd: number, 24 | ) => { 25 | // Decoration begins in defined range 26 | if (decorationStart >= rangeStart && decorationStart <= rangeEnd) { 27 | return false; 28 | } 29 | 30 | // Decoration ends in defined range 31 | if (decorationEnd >= rangeStart && decorationEnd <= rangeEnd) { 32 | return false; 33 | } 34 | 35 | // Defined range begins within decoration 36 | if (rangeStart >= decorationStart && rangeStart <= decorationEnd) { 37 | return false; 38 | } 39 | 40 | // Defined range ends within decoration 41 | if (rangeEnd >= decorationStart && rangeEnd <= decorationEnd) { 42 | return false; 43 | } 44 | 45 | return true; 46 | }; 47 | 48 | export const underlineField = StateField.define({ 49 | create: () => { 50 | return Decoration.none; 51 | }, 52 | update: (underlines, tr) => { 53 | const seenRanges = new Set(); 54 | 55 | const seenPositions: Record = {}; 56 | let tree: Tree | null = null; 57 | 58 | underlines = underlines.map(tr.changes); 59 | 60 | const canDecorate = (pos: number) => { 61 | if (seenPositions[pos] !== undefined) { 62 | return seenPositions[pos]; 63 | } 64 | 65 | if (!tree) tree = syntaxTree(tr.state); 66 | 67 | const nodeProps = tree.resolveInner(pos, 1).type.prop(tokenClassNodeProp); 68 | 69 | if (nodeProps && ignoreListRegEx.test(nodeProps)) { 70 | seenPositions[pos] = false; 71 | } else { 72 | seenPositions[pos] = true; 73 | } 74 | 75 | return seenPositions[pos]; 76 | }; 77 | 78 | if (tr.docChanged && tr.selection && underlines.size) { 79 | underlines = underlines.update({ 80 | filter: (from, to) => { 81 | return filterUnderlines( 82 | from, 83 | to, 84 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 85 | tr.selection!.main.from, 86 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 87 | tr.selection!.main.to, 88 | ); 89 | }, 90 | }); 91 | } 92 | 93 | for (const e of tr.effects) { 94 | if (e.is(resetUnderline) && e.value) { 95 | underlines = Decoration.none; 96 | } else if (e.is(addUnderline)) { 97 | const { from, to, match } = e.value; 98 | const key = `${from},${to}`; 99 | 100 | let severityClass = 'info'; 101 | if (match.severity === 1) { 102 | severityClass = 'warning'; 103 | } else if (match.severity === 2) { 104 | severityClass = 'error'; 105 | } 106 | 107 | if (!seenRanges.has(key) && canDecorate(from) && canDecorate(to)) { 108 | seenRanges.add(key); 109 | underlines = underlines.update({ 110 | add: [ 111 | Decoration.mark({ 112 | class: `textlint-underline ${severityClass}`, 113 | match, 114 | }).range(from, to), 115 | ], 116 | }); 117 | } 118 | } 119 | } 120 | 121 | return underlines; 122 | }, 123 | provide: (f) => EditorView.decorations.from(f), 124 | }); 125 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { StateEffect } from '@codemirror/state'; 2 | import type { EditorView } from '@codemirror/view'; 3 | import type { TextlintResult } from '@textlint/types'; 4 | import { MarkdownView, Plugin, TFile } from 'obsidian'; 5 | 6 | import { buildUnderlineExtension } from './extension/tooltip/underlineExtension'; 7 | import { 8 | addUnderline, 9 | UnderlineEffect, 10 | resetUnderline, 11 | } from './extension/tooltip/underlineStateField'; 12 | import { TextlintWorker } from './lib/textlint'; 13 | import type { TextlintSettings } from './rules'; 14 | import { SettingTab } from './settings'; 15 | 16 | import './lib/sentry'; 17 | 18 | const DEFAULT_SETTINGS: Partial = { 19 | lintOnSave: false, 20 | foldersToIgnore: [], 21 | }; 22 | 23 | export default class TextlintPlugin extends Plugin { 24 | settings: TextlintSettings; 25 | private isEnabled = true; 26 | private worker: TextlintWorker; 27 | 28 | async onload() { 29 | console.log('Loading Textlint plugin'); 30 | this.isEnabled = true; 31 | await this.loadSettings(); 32 | 33 | this.worker = TextlintWorker.getInstance(); 34 | if (this.settings.textlintrc) { 35 | this.worker.setTextlintrc(this.settings.textlintrc); 36 | } 37 | 38 | this.registerEditorExtension(buildUnderlineExtension(this)); 39 | this.registerCommands(); 40 | 41 | this.addSettingTab(new SettingTab(this.app, this)); 42 | this.registerEventsAndSaveCallback(); 43 | } 44 | 45 | onunload() { 46 | console.log('Unloading Textlint plugin'); 47 | this.isEnabled = false; 48 | this.worker.terminate(); 49 | } 50 | 51 | registerCommands() { 52 | this.addCommand({ 53 | id: 'textlint-text', 54 | name: 'TextLint Text', 55 | editorCallback: (editor, view) => { 56 | this.runTextlint((editor as any).cm as EditorView, view).catch((e) => { 57 | console.log(e); 58 | }); 59 | }, 60 | }); 61 | } 62 | 63 | registerEventsAndSaveCallback() { 64 | const saveCommandDefinition = (this.app as any).commands?.commands?.[ 65 | 'editor:save-file' 66 | ]; 67 | const save = saveCommandDefinition?.callback; 68 | if (typeof save === 'function') { 69 | saveCommandDefinition.callback = () => { 70 | if (this.settings.lintOnSave && this.isEnabled) { 71 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 72 | if (!view) { 73 | return; 74 | } 75 | const editor = (view.editor as any).cm as EditorView; 76 | editor.dispatch({}); 77 | 78 | const file = this.app.workspace.getActiveFile(); 79 | 80 | if (file && !this.shouldIgnoreFile(file)) { 81 | this.runTextlint(editor, view); 82 | } 83 | } 84 | }; 85 | } 86 | } 87 | 88 | async loadSettings() { 89 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 90 | } 91 | 92 | async saveSettings() { 93 | await this.saveData(this.settings); 94 | } 95 | 96 | async runTextlint(editor: EditorView, view: MarkdownView) { 97 | const text = view.data; 98 | 99 | this.worker.startTextlint(text, (result: TextlintResult) => { 100 | const effects: StateEffect[] = []; 101 | 102 | // clear all underlines 103 | effects.push(resetUnderline.of(true)); 104 | 105 | if (result.messages) { 106 | for (const message of result.messages) { 107 | const start = message.range[0]; 108 | const end = message.range[1]; 109 | effects.push( 110 | addUnderline.of({ 111 | from: start, 112 | to: end, 113 | match: message, 114 | }), 115 | ); 116 | } 117 | } 118 | 119 | if (effects.length) { 120 | editor.dispatch({ 121 | effects, 122 | }); 123 | } 124 | }); 125 | } 126 | 127 | shouldIgnoreFile(file: TFile) { 128 | for (const folder of this.settings.foldersToIgnore) { 129 | if (folder.length > 0 && file.path.startsWith(folder)) { 130 | return true; 131 | } 132 | } 133 | 134 | return false; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/lib/sentry.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/browser'; 2 | import { Integrations } from '@sentry/tracing'; 3 | 4 | const DSN = 5 | 'https://2896cd79c49a4fa2b4b33381312e7e15@o4504295771865088.ingest.sentry.io/4504295806402560'; 6 | 7 | declare const VERSION: string; 8 | declare const PACKAGE_NAME: string; 9 | declare const PRODUCTION: boolean; 10 | 11 | Sentry.init({ 12 | dsn: DSN, 13 | release: `${PACKAGE_NAME}@${VERSION}`, 14 | environment: PRODUCTION ? 'production' : 'dev', 15 | integrations: [new Integrations.BrowserTracing()], 16 | tracesSampleRate: 1.0, 17 | }); 18 | -------------------------------------------------------------------------------- /src/lib/textlint.ts: -------------------------------------------------------------------------------- 1 | import type { TextlintResult } from '@textlint/types'; 2 | import { Notice } from 'obsidian'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 5 | /* @ts-ignore */ 6 | // eslint-disable-next-line import/namespace 7 | import Worker from '../../lib/worker/textlint-worker.js'; 8 | import textlintrc from '../../scripts/textlintrc.json'; 9 | 10 | export class TextlintWorker { 11 | private static instance: TextlintWorker | null; 12 | private _worker: Worker; 13 | 14 | private constructor() { 15 | this._worker = new Worker(); 16 | this.setTextlintrc(JSON.stringify(textlintrc)); 17 | } 18 | 19 | static getInstance() { 20 | if (this.instance) { 21 | return this.instance; 22 | } 23 | this.instance = new TextlintWorker(); 24 | return this.instance; 25 | } 26 | 27 | setTextlintrc(textlintrc: string) { 28 | try { 29 | this._worker.postMessage({ 30 | command: 'merge-config', 31 | textlintrc: JSON.parse(textlintrc), 32 | }); 33 | } catch (e) { 34 | new Notice('textlintrc Format Error: ' + e); 35 | } 36 | } 37 | 38 | startTextlint(text: string, callback: (result: TextlintResult) => void) { 39 | this.registerTextlintCallback(callback); 40 | this.postToTextlint(text); 41 | } 42 | 43 | terminate() { 44 | this._worker.terminate(); 45 | TextlintWorker.instance = null; 46 | } 47 | 48 | private registerTextlintCallback(callback: (result: TextlintResult) => void) { 49 | this._worker.onmessage = (event: any) => { 50 | if (event.data.command === 'lint:result') { 51 | callback(event.data.result); 52 | } 53 | }; 54 | } 55 | 56 | private postToTextlint = async (text: string) => { 57 | this._worker.postMessage({ 58 | command: 'lint', 59 | text: text, 60 | ext: '.txt', 61 | }); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/rules.ts: -------------------------------------------------------------------------------- 1 | export interface TextlintSettings { 2 | lintOnSave: boolean; 3 | textlintrc: string; 4 | foldersToIgnore: string[]; 5 | } 6 | 7 | /* TODO: Add textlint rules here * */ 8 | -------------------------------------------------------------------------------- /src/settings/index.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, SearchComponent, Setting } from 'obsidian'; 2 | 3 | import type TextlintPlugin from '..'; 4 | import { TextlintWorker } from '../lib/textlint'; 5 | 6 | export class SettingTab extends PluginSettingTab { 7 | plugin: TextlintPlugin; 8 | search: SearchComponent; 9 | 10 | constructor(app: App, plugin: TextlintPlugin) { 11 | super(app, plugin); 12 | this.plugin = plugin; 13 | } 14 | 15 | display(): void { 16 | const { containerEl } = this; 17 | 18 | containerEl.empty(); 19 | 20 | /* Header */ 21 | const linterHeader = containerEl.createDiv('textlint-setting-title'); 22 | linterHeader.createEl('h1').setText('Textlint Settings'); 23 | 24 | /* TODO: Search Bar */ 25 | //const searchSetting = new Setting(containerEl); 26 | //searchSetting.settingEl.style.border = 'none'; 27 | //this.search.setPlaceholder('Search all settings'); 28 | 29 | //searchSetting.addSearch((s: SearchComponent) => { 30 | // this.search = s; 31 | //}); 32 | 33 | //this.search.onChange((value: string) => { 34 | // this.searchSettings(value.toLowerCase()); 35 | //}); 36 | 37 | new Setting(containerEl) 38 | .setName('Lint on save') 39 | .setDesc('Lint the file on manual save') 40 | .addToggle((toggle) => 41 | toggle 42 | .setValue(this.plugin.settings.lintOnSave) 43 | .onChange(async (value) => { 44 | this.plugin.settings.lintOnSave = value; 45 | await this.plugin.saveSettings(); 46 | }), 47 | ); 48 | 49 | new Setting(containerEl) 50 | .setName('Folder to ignore') 51 | .setDesc('Textlintを無効にしたいフォルダを指定して下さい') 52 | .addTextArea((textarea) => { 53 | textarea 54 | .setValue(this.plugin.settings.foldersToIgnore.join('\n')) 55 | .onChange(async (value) => { 56 | this.plugin.settings.foldersToIgnore = value.split('\n'); 57 | await this.plugin.saveSettings(); 58 | }); 59 | }); 60 | 61 | /* TODO: Allow individual values to be set instead of text areas */ 62 | new Setting(containerEl) 63 | .setName('Override textlintrc') 64 | .setDesc( 65 | 'textlintrcの上書きをしたい場合にJSONフォーマットで記載して下さい。', 66 | ) 67 | .addTextArea((textarea) => 68 | textarea 69 | .setValue(this.plugin.settings.textlintrc) 70 | .onChange(async (value) => { 71 | this.plugin.settings.textlintrc = value; 72 | await this.plugin.saveSettings(); 73 | 74 | if (value.trim()) { 75 | const worker = TextlintWorker.getInstance(); 76 | worker.setTextlintrc(value); 77 | } 78 | }), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | .textlint-underline { 6 | cursor: pointer; 7 | transition: background-color 100ms ease-out; 8 | } 9 | 10 | .textlint-underline.info { 11 | box-shadow: inset 0 -2px #8981f3; 12 | } 13 | 14 | .textlint-underline.warning { 15 | box-shadow: inset 0 -2px #e9b35f; 16 | } 17 | 18 | .textlint-underline.error { 19 | box-shadow: inset 0 -2px #da615c; 20 | } 21 | 22 | .textlint-underline.warning:hover { 23 | background-color: #e9b35f21; 24 | } 25 | 26 | .textlint-underline.error:hover { 27 | background-color: #da615c21; 28 | } 29 | 30 | .textlint-underline.info:hover { 31 | background-color: #8981f321; 32 | } 33 | 34 | .textlint-setting-title { 35 | font-size: large; 36 | font-weight: bold; 37 | } 38 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | prefix: 'tailwind-', 4 | corePlugins: { 5 | preflight: false, 6 | }, 7 | content: ['./src/**/*.{ts,tsx}'], 8 | theme: {}, 9 | plugins: [require('daisyui')], 10 | purge: false, 11 | daisyui: { 12 | prefix: 'daisy-', 13 | styled: true, 14 | themes: false, 15 | base: true, 16 | utils: true, 17 | logs: true, 18 | rtl: false, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "module": "ESNext", 8 | "target": "es2017", 9 | "allowJs": true, 10 | "jsx": "react", 11 | "noImplicitAny": true, 12 | "moduleResolution": "node", 13 | "preserveValueImports": true, 14 | "importsNotUsedAsValues": "error", 15 | "importHelpers": true, 16 | "skipLibCheck": true, 17 | "strictNullChecks": true, 18 | "allowSyntheticDefaultImports": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "esModuleInterop": true, 21 | "resolveJsonModule": true, 22 | "lib": ["dom", "es5", "es2018", "es2021"], 23 | "types": ["node"] 24 | }, 25 | "include": ["src", "webpack.config.ts", "lib/worker/textlint-worker.js"], 26 | "ts-node": { 27 | "compilerOptions": { 28 | "module": "commonjs" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.2": "0.15.0" 3 | } 4 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import SentryWebpackPlugin from '@sentry/webpack-plugin'; 4 | import CopyPlugin from 'copy-webpack-plugin'; 5 | import dotenv from 'dotenv'; 6 | import TerserPlugin from 'terser-webpack-plugin'; 7 | import { Configuration, DefinePlugin, ExternalsPlugin } from 'webpack'; 8 | 9 | import pack from './package.json'; 10 | 11 | const dependencies = Object.keys(pack.dependencies); 12 | 13 | dotenv.config(); 14 | 15 | const isProduction = process.env.NODE_ENV === 'production'; 16 | const releaseVersion = pack.version; 17 | 18 | const sentryPlugin = new SentryWebpackPlugin({ 19 | authToken: process.env.SENTRY_AUTH_TOKEN, 20 | org: 'shivase', 21 | project: 'obsidian-textlint', 22 | release: releaseVersion, 23 | ignore: ['node_modules', 'webpack.config.ts'], 24 | include: 'dist', 25 | }); 26 | 27 | const config: Configuration = { 28 | context: __dirname, 29 | entry: { 30 | main: path.resolve(__dirname, 'src/index.ts'), 31 | }, 32 | output: { 33 | path: path.resolve(__dirname, 'dist'), 34 | filename: '[name].js', 35 | libraryTarget: 'commonjs', 36 | clean: true, 37 | }, 38 | target: 'node', 39 | mode: isProduction ? 'production' : 'development', 40 | devtool: isProduction ? 'source-map' : 'inline-source-map', 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.tsx?$/, 45 | loader: 'ts-loader', 46 | options: { 47 | transpileOnly: true, 48 | }, 49 | }, 50 | { 51 | test: /textlint-worker\.js$/i, 52 | loader: 'worker-loader', 53 | options: { 54 | esModule: false, 55 | inline: 'no-fallback', 56 | }, 57 | }, 58 | ], 59 | }, 60 | optimization: { 61 | minimize: isProduction, 62 | minimizer: [ 63 | new TerserPlugin({ 64 | extractComments: false, 65 | minify: TerserPlugin.uglifyJsMinify, 66 | terserOptions: {}, 67 | }), 68 | ], 69 | }, 70 | plugins: [ 71 | new CopyPlugin({ 72 | patterns: [ 73 | { 74 | from: './manifest.json', 75 | to: '.', 76 | }, 77 | ], 78 | }), 79 | new DefinePlugin({ 80 | PACKAGE_NAME: JSON.stringify(pack.name), 81 | VERSION: JSON.stringify(releaseVersion), 82 | PRODUCTION: JSON.stringify(isProduction), 83 | }), 84 | new ExternalsPlugin('commonjs', dependencies), 85 | ...(isProduction ? [sentryPlugin] : []), 86 | ], 87 | resolve: { 88 | alias: { 89 | '@': path.resolve(__dirname, 'src'), 90 | }, 91 | extensions: ['.ts', '.tsx', '.js', '.json'], 92 | mainFields: ['browser', 'module', 'main'], 93 | }, 94 | }; 95 | 96 | export default config; 97 | --------------------------------------------------------------------------------