├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .travis.yml ├── LICENSE ├── README.md ├── app └── index.html ├── appveyor.yml ├── build ├── icon.icns ├── icon.ico └── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 512x512.png │ └── 64x64.png ├── docs └── README.md ├── index.js ├── lerna.json ├── locales └── ja │ └── translation.json ├── package.json ├── packages ├── textlint-app-locator │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── lib │ │ └── textlint-app-locator.js │ └── package.json ├── textlint-message-to-codemirror │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── lib │ │ └── textlint-message-to-codemirror.js │ ├── package.json │ └── test │ │ └── textlint-message-to-codemirror-test.js └── textlint-server-package-manager │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ └── textlint-server-package-manager.js │ ├── test │ ├── mocha.opts │ └── textlint-server-package-manager-test.js │ └── yarn.lock ├── postcss.config.js ├── src ├── node │ ├── Application.js │ ├── electron │ │ └── AppUpdater.js │ ├── index.js │ ├── infra │ │ └── textlint │ │ │ └── TextlintAPIServer.js │ └── ipc │ │ ├── textlint-ipc-key.js │ │ └── textlint-ipc.js └── renderer │ ├── components │ ├── App.js │ ├── container │ │ ├── NavigationContainer │ │ │ ├── NavigationContainer.css │ │ │ └── NavigationContainer.js │ │ ├── TextlintEditorContainer │ │ │ ├── TextlintEditorContainer.css │ │ │ └── TextlintEditorContainer.js │ │ └── TextlintrcEditorContainer │ │ │ ├── TextlintrcEditorContainer.css │ │ │ └── TextlintrcEditorContainer.js │ ├── pages │ │ ├── NavWrapper.css │ │ ├── NavWrapper.js │ │ ├── TextlintEditorPage.js │ │ └── TextlintrcEditorPage.js │ └── project │ │ ├── DirectoryInput │ │ ├── DirectoryInput.css │ │ └── DirectoryInput.js │ │ ├── ElectronWindowTitle │ │ └── ElectronWindowTitle.js │ │ ├── FileToolbar │ │ └── FileToolbar.js │ │ ├── InstallButton │ │ └── InstallButton.js │ │ ├── LintResultList │ │ ├── LintResultList.js │ │ ├── LintResultListItem.css │ │ ├── LintResultListItem.is-error.css │ │ ├── LintResultListItem.is-fixable.css │ │ └── LintResultListItem.js │ │ ├── MessageNotification │ │ └── MessageNotification.js │ │ ├── OpenFileButton │ │ └── OpenFileButton.js │ │ ├── SaveButton │ │ └── SaveButton.js │ │ ├── TextlintEditor │ │ ├── TextlintEditor.css │ │ └── TextlintEditor.js │ │ ├── TextlintLogo │ │ ├── TextlintLogo.js │ │ └── textlint-logo.png │ │ └── TextlintrcEditor │ │ └── TextlintrcEditor.js │ ├── css │ ├── base.css │ └── config.css │ ├── domain │ ├── TextlintApp.js │ ├── TextlintAppFactory.js │ ├── TextlintAppId.js │ ├── __tests__ │ │ └── TextlintApp-test.js │ ├── textlint-editor │ │ ├── TextlintEditor.js │ │ ├── TextlintEditorContent.js │ │ └── TextlintEditorContentNoFile.js │ └── workspace │ │ ├── Workspace.js │ │ ├── WorkspaceFactory.js │ │ ├── Workspaces.js │ │ └── textlintrc │ │ ├── EmptyTextlintrc.js │ │ ├── Textlintrc.js │ │ └── TextlintrcValidator.js │ ├── index.css │ ├── index.js │ ├── infra │ ├── api │ │ └── PackageManger.js │ ├── repository │ │ ├── TextlintAppRepository.js │ │ └── adpter │ │ │ └── MemoryDB.js │ └── textlint │ │ └── TextlintAPI.js │ ├── store │ ├── AppStore.js │ ├── TextlintEditor │ │ ├── TextlintEditorStore.js │ │ └── __tests__ │ │ │ └── TextlintEditorStore-test.js │ └── TextlintrcEditor │ │ └── TextlintrcEditorStore.js │ └── use-case │ ├── InitializeUseCase.js │ ├── textlint-editor │ ├── FixTextUseCase.js │ ├── OpenNewFileUseCase.js │ ├── SaveAsNewFileUseCase.js │ └── UpdateTextUseCase.js │ ├── textlintrc │ ├── DismissInstallErrorUseCase.js │ ├── UpdateTextlintrcUseCase.js │ └── WriteToTextlintrcUseCase.js │ └── workspace │ ├── InstallTextlintPackageUseCase.js │ ├── ReloadCurrentWorkspaceUseCase.js │ └── UpdateWorkspaceDirectoryUseCase.js ├── test └── mocha.opts ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": "electron" 7 | } 8 | ], 9 | "stage-2", 10 | "react" 11 | ], 12 | "env": { 13 | "development": { 14 | "presets": [ 15 | "jsdoc-to-assert", 16 | "power-assert" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parser": "babel-eslint", 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "experimentalObjectRestSpread": true, 13 | "jsx": true 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "react", 19 | "no-allow-react-context" 20 | ], 21 | "extends": [ 22 | "eslint:recommended", 23 | "plugin:react/recommended" 24 | ], 25 | "rules": { 26 | "radix": 2, 27 | "arrow-parens": [ 28 | 2, 29 | "as-needed" 30 | ], 31 | "arrow-spacing": 2, 32 | "block-spacing": [ 33 | 2, 34 | "never" 35 | ], 36 | "camelcase": 1, 37 | "constructor-super": 2, 38 | "comma-dangle": [ 39 | 2, 40 | "never" 41 | ], 42 | "comma-style": [ 43 | 2, 44 | "last" 45 | ], 46 | "comma-spacing": [ 47 | 2, 48 | { 49 | "before": false, 50 | "after": true 51 | } 52 | ], 53 | "callback-return": [ 54 | 2, 55 | [ 56 | "callback", 57 | "cb", 58 | "next" 59 | ] 60 | ], 61 | "computed-property-spacing": [ 62 | 2, 63 | "never" 64 | ], 65 | "default-case": 2, 66 | "dot-notation": 0, 67 | "dot-location": [ 68 | 2, 69 | "property" 70 | ], 71 | "eol-last": 2, 72 | "eqeqeq": 2, 73 | "generator-star-spacing": 2, 74 | "indent": [ 75 | 2, 76 | 4, 77 | { 78 | "SwitchCase": 1 79 | } 80 | ], 81 | "require-jsdoc": 1, 82 | "key-spacing": [ 83 | 2, 84 | { 85 | "beforeColon": false, 86 | "afterColon": true 87 | } 88 | ], 89 | "linebreak-style": [ 90 | 2, 91 | "unix" 92 | ], 93 | "lines-around-comment": [ 94 | 2, 95 | { 96 | "beforeBlockComment": false, 97 | "beforeLineComment": false, 98 | "afterLineComment": false 99 | } 100 | ], 101 | "no-alert": 0, 102 | "no-class-assign": 2, 103 | "no-cond-assign": 2, 104 | "no-console": 1, 105 | "no-const-assign": 2, 106 | "no-constant-condition": 2, 107 | "no-duplicate-case": 2, 108 | "no-dupe-class-members": 2, 109 | "no-dupe-keys": 2, 110 | "no-empty": 2, 111 | "no-empty-pattern": 2, 112 | "no-invalid-this": 1, 113 | "no-mixed-spaces-and-tabs": 2, 114 | "no-multiple-empty-lines": [ 115 | 2, 116 | { 117 | "max": 2 118 | } 119 | ], 120 | "no-multi-spaces": 0, 121 | "no-this-before-super": 2, 122 | "no-trailing-spaces": 2, 123 | "no-lonely-if": 2, 124 | "no-unneeded-ternary": 2, 125 | "no-unused-vars": 0, 126 | // 未実装関数でエラー出るので 0 にして無効化・・・ 127 | "no-var": 2, 128 | "no-regex-spaces": 0, 129 | "object-curly-spacing": [ 130 | 0, 131 | "never", 132 | { 133 | "objectsInObjects": false, 134 | "arraysInObjects": false 135 | } 136 | ], 137 | "operator-assignment": [ 138 | 2, 139 | "always" 140 | ], 141 | "prefer-arrow-callback": 2, 142 | "prefer-const": 2, 143 | "prefer-reflect": 0, 144 | "prefer-spread": 2, 145 | "prefer-template": 2, 146 | "require-yield": 2, 147 | "quotes": [ 148 | 2, 149 | "double" 150 | ], 151 | "semi": [ 152 | 2, 153 | "always" 154 | ], 155 | "strict": 0, 156 | "space-before-blocks": 2, 157 | "space-before-function-paren": [ 158 | 2, 159 | "never" 160 | ], 161 | "space-infix-ops": 2, 162 | "space-unary-ops": 2, 163 | "spaced-comment": [ 164 | 2, 165 | "always" 166 | ], 167 | "valid-jsdoc": [ 168 | 1, 169 | { 170 | "requireReturn": false, 171 | "requireParamDescription": false, 172 | "requireReturnDescription": false 173 | } 174 | ], 175 | // React 176 | "react/no-danger": 0, 177 | "react/prop-types": 1, 178 | "react/forbid-prop-types": 2, 179 | "no-allow-react-context/no-allow-react-context": [ 180 | 2, { 181 | "except": [ 182 | "**/components/container/**/*.js", 183 | "**/components/pages/**/*.js" 184 | ] 185 | } 186 | ] 187 | } 188 | }; 189 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Debug log from npm 32 | npm-debug.log 33 | 34 | 35 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Global/JetBrains.gitignore 36 | 37 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 38 | 39 | *.iml 40 | 41 | ## Directory-based project format: 42 | .idea/ 43 | # if you remove the above rule, at least ignore the following: 44 | 45 | # User-specific stuff: 46 | # .idea/workspace.xml 47 | # .idea/tasks.xml 48 | # .idea/dictionaries 49 | 50 | # Sensitive or high-churn files: 51 | # .idea/dataSources.ids 52 | # .idea/dataSources.xml 53 | # .idea/sqlDataSources.xml 54 | # .idea/dynamic.xml 55 | # .idea/uiDesigner.xml 56 | 57 | # Gradle: 58 | # .idea/gradle.xml 59 | # .idea/libraries 60 | 61 | # Mongo Explorer plugin: 62 | # .idea/mongoSettings.xml 63 | 64 | ## File-based project format: 65 | *.ipr 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | 84 | 85 | /app/build/ 86 | /dist 87 | /out/ 88 | /.eslintcache 89 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix=v 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode7.3 2 | 3 | sudo: required 4 | dist: trusty 5 | 6 | language: c 7 | 8 | matrix: 9 | fast_finish: true 10 | include: 11 | - os: osx 12 | - os: linux 13 | env: CC=clang CXX=clang++ npm_config_clang=1 14 | compiler: clang 15 | 16 | addons: 17 | apt: 18 | packages: 19 | - libgnome-keyring-dev 20 | - icnsutils 21 | 22 | install: 23 | - nvm install 6 24 | - npm install -g yarn 25 | - npm install -g lerna-run 26 | - lerna-run yarn install 27 | - yarn install 28 | - ./node_modules/.bin/install-app-deps 29 | script: 30 | - npm test 31 | - $(npm bin)/lerna run test 32 | after_success: 33 | - DEBUG=electron-builder npm run release 34 | 35 | branches: 36 | except: 37 | - "/^v\\d+\\.\\d+\\.\\d+$/" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # textlint-app [![Build Status](https://travis-ci.org/textlint/textlint-app.svg?branch=master)](https://travis-ci.org/textlint/textlint-app) [![Build status](https://ci.appveyor.com/api/projects/status/mnburjdwu7vsva7t?svg=true)](https://ci.appveyor.com/project/azu/textlint-app) 2 | 3 | [textlint](https://github.com/textlint/textlint "textlint") app for cross platform. 4 | 5 | ![gif](https://media.giphy.com/media/3o7buj7KnuEurvGVm8/giphy.gif) 6 | 7 | ## Why? 8 | 9 | - Not require Node.js/npm on your environment. 10 | - Just download binary and launch app. 11 | 12 | ## Install 13 | 14 | Download Binary from 15 | 16 | ## Features 17 | 18 | - No dependencies 19 | - Just install and run it 20 | - Cross platform 21 | - Windows/Linux/Mac - [Download it](https://github.com/textlint/textlint-app/releases/latest) 22 | - Support `.textlintrc` 23 | - You can use existing `.textlintrc` config 24 | - Support `--fix` 25 | - Automatically fix lint error 26 | - TODO: 27 | - [ ] Support only markdown 28 | - [ ] Improve performance 29 | 30 | ## Usage 31 | 32 | 1. Open textlint app 33 | 2. Go to "Setting" tab 34 | 3. Setup `.textlintrc` 35 | - Please see [Configuring textlint](https://github.com/textlint/textlint/blob/master/docs/configuring.md "Configuring textlint") 36 | 4. Press "Install"! 37 | 5. Edit with textlint 38 | 39 | ### Example 40 | 41 | `.textlintrc`: 42 | ```json 43 | { 44 | "rules": { 45 | "no-todo": true 46 | } 47 | } 48 | ``` 49 | 50 | Result: 51 | 52 | ![image](https://monosnap.com/file/YdUoiwRYVDEghLw6k86t7sQzNHb2G3.png) 53 | 54 | 55 | ## Development 56 | 57 | You can install textlint-app in local environment. 58 | 59 | yarn install 60 | yarn run bootstrap 61 | yarn run watch 62 | yarn run electron 63 | 64 | ### Update packages 65 | 66 | If you update packages/ modules 67 | 68 | yarn run publish 69 | # It bump version, not publish to npm 70 | 71 | Update packages/ dependencies 72 | 73 | yarn upgrade 74 | 75 | TODO: `file:` is not linked by lerna... We will improve this. 76 | 77 | ## Changelog 78 | 79 | See [Releases page](https://github.com/textlint/textlint-app/releases). 80 | 81 | ## Running tests 82 | 83 | Install devDependencies and Run `yarn test`: 84 | 85 | yarn test 86 | 87 | ## Contributing 88 | 89 | Pull requests and stars are always welcome. 90 | 91 | For bugs and feature requests, [please create an issue](https://github.com/textlint/textlint-app/issues). 92 | 93 | 1. Fork it! 94 | 2. Create your feature branch: `git checkout -b my-new-feature` 95 | 3. Commit your changes: `git commit -am 'Add some feature'` 96 | 4. Push to the branch: `git push origin my-new-feature` 97 | 5. Submit a pull request :D 98 | 99 | ## Author 100 | 101 | - [github/azu](https://github.com/azu) 102 | - [twitter/azu_re](https://twitter.com/azu_re) 103 | 104 | ## License 105 | 106 | MIT © azu 107 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | textlint app 6 | 7 | 8 | 9 |
10 | LOADING.... 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.4.{build} 2 | 3 | platform: 4 | - x64 5 | 6 | init: 7 | - git config --global core.autocrlf input 8 | 9 | install: 10 | - ps: Install-Product node 6 x64 11 | - git reset --hard HEAD 12 | - npm install -g yarn 13 | - npm install -g cross-env 14 | - npm install -g lerna-run 15 | - lerna-run yarn install 16 | - yarn install 17 | - ./node_modules/.bin/install-app-deps 18 | 19 | build_script: 20 | - node --version 21 | - npm --version 22 | - cross-env DEBUG=electron-builder npm run release 23 | 24 | test: off -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icon.ico -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/build/icons/64x64.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Domain 2 | 3 | - TextlintApp (Aggregation) 4 | - Workspace 5 | - Textlintrc 6 | 7 | ### an example sentence 8 | 9 | Textlint load Configuration from Textlintrc. 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | require("./app/build/node"); -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.4", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "1.0.1", 7 | "npmClient": "yarn" 8 | } 9 | -------------------------------------------------------------------------------- /locales/ja/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Set working directory if needed.(Default: use textlint-app working dir)": "ワーキングディレクトリを設定(デフォルトではtextlint-appのものが使われます)", 3 | "Write .textlintrc configuration": ".textlintrcを設定", 4 | "Install textlint rules from the .textlintrc configuration.(Press \"Install\" button)": ".textlintrcの設定を元にtextlintのルールをインストール(インストールボタンをクリック)", 5 | "You can set .textlintrc working directory.": ".textlintrcファイルを読み書きするディレクトリを設定できます。", 6 | "Failed to install. Please check .textlintrc and Press Install again.": "インストールに失敗しました。.textlintrcを確認しもう一度インストールしてください。" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "azu ", 4 | "email": "azuciao@gmail.com", 5 | "license": "MIT", 6 | "files": [ 7 | "bin/", 8 | "lib/", 9 | "src/" 10 | ], 11 | "name": "textlint-app", 12 | "version": "1.4.1", 13 | "description": "textlint app for crossplatform.", 14 | "main": "index.js", 15 | "scripts": { 16 | "publish": "lerna publish --conventional-commits", 17 | "lint": "eslint src/ test/ --cache", 18 | "lint:fix": "eslint --fix src/ test/ --cache", 19 | "test": "mocha 'src/**/__tests__/*'", 20 | "bootstrap": "lerna bootstrap && install-app-deps", 21 | "start": "npm-run-all clean --parallel watch:*", 22 | "electron": "electron .", 23 | "build:js": "cross-env NODE_ENV=production webpack --progress -p", 24 | "build:css": "postcss --config postcss.config.js src/renderer/index.css -o app/build/bundle.css", 25 | "build": "npm-run-all clean --parallel build:*", 26 | "watch:js": "cross-env NODE_ENV=development webpack -d --watch", 27 | "watch:css": "chokidar --initial 'src/**/*.css' -c 'npm run build:css'", 28 | "watch": "npm-run-all clean --parallel watch:*", 29 | "clean": "rimraf app/build dist/ out/ && mkdirp app/build", 30 | "_i18n:extract": "i18next src/renderer/ -o locales -l ja -r", 31 | "pack": "npm run build && build --dir", 32 | "dist": "npm run build && build --x64 --ia32", 33 | "release": "npm run build && build --x64 --ia32 --publish onTagOrDraft" 34 | }, 35 | "build": { 36 | "productName": "textlint", 37 | "appId": "info.efcl.textlint", 38 | "asar": true, 39 | "npmRebuild": false, 40 | "files": [ 41 | "index.js", 42 | "app/**/*", 43 | "node_modules/**/*" 44 | ], 45 | "win": { 46 | "target": [ 47 | "nsis" 48 | ] 49 | }, 50 | "mac": { 51 | "target": [ 52 | "zip" 53 | ], 54 | "icon": "build/icon.icns", 55 | "category": "public.app-category.productivity" 56 | }, 57 | "linux": { 58 | "target": [ 59 | "deb" 60 | ] 61 | }, 62 | "publish": [ 63 | { 64 | "provider": "github" 65 | } 66 | ] 67 | }, 68 | "keywords": [ 69 | "textlint", 70 | "electron", 71 | "app", 72 | "native" 73 | ], 74 | "repository": { 75 | "type": "git", 76 | "url": "https://github.com/textlint/textlint-app.git" 77 | }, 78 | "bugs": { 79 | "url": "https://github.com/textlint/textlint-app/issues" 80 | }, 81 | "homepage": "https://github.com/textlint/textlint-app", 82 | "devDependencies": { 83 | "autoprefixer": "^7.0.1", 84 | "babel-cli": "^6.22.2", 85 | "babel-eslint": "^7.1.1", 86 | "babel-loader": "^7.0.0", 87 | "babel-preset-env": "^1.1.8", 88 | "babel-preset-jsdoc-to-assert": "^4.0.0", 89 | "babel-preset-power-assert": "^1.0.0", 90 | "babel-preset-react": "^6.22.0", 91 | "babel-preset-stage-2": "^6.22.0", 92 | "babel-register": "^6.22.0", 93 | "chokidar-cli": "^1.2.0", 94 | "cross-env": "^5.0.0", 95 | "css-loader": "^0.28.1", 96 | "electron": "^1.4.15", 97 | "electron-builder": "^17.3.1", 98 | "electron-devtools-installer": "^2.1.0", 99 | "electron-packager": "^8.5.1", 100 | "electron-webpack-plugin": "^2.0.0", 101 | "eslint": "^3.14.1", 102 | "eslint-plugin-no-allow-react-context": "^1.0.1", 103 | "eslint-plugin-react": "^7.0.0", 104 | "file-loader": "^0.11.1", 105 | "i18next-parser": "^0.11.1", 106 | "json-loader": "^0.5.4", 107 | "lerna": "^2.0.0-rc.4", 108 | "mkdirp": "^0.5.1", 109 | "mocha": "^3.2.0", 110 | "npm-run-all": "^4.0.1", 111 | "postcss-calc": "^6.0.0", 112 | "postcss-cli": "^3.2.0", 113 | "postcss-custom-media": "^5.0.1", 114 | "postcss-custom-properties": "^5.0.1", 115 | "postcss-easy-import": "^2.0.0", 116 | "postcss-reporter": "^3.0.0", 117 | "power-assert": "^1.4.2", 118 | "rimraf": "^2.5.4", 119 | "style-loader": "^0.17.0", 120 | "url-loader": "^0.5.7", 121 | "webpack": "^2.5.1" 122 | }, 123 | "dependencies": { 124 | "almin": "^0.14.0", 125 | "almin-logger": "^6.1.0", 126 | "classnames": "^2.2.5", 127 | "codemirror": "^5.23.0", 128 | "codemirror-textlint": "^1.2.0", 129 | "debug": "^2.6.0", 130 | "electron-default-menu": "^1.0.0", 131 | "electron-is-dev": "^0.1.2", 132 | "electron-updater": "^1.15.0", 133 | "electron-window-state": "^4.0.1", 134 | "i18next": "^4.2.0", 135 | "i18next-electron-language-detector": "^0.0.4", 136 | "ipc-promise": "^0.1.3", 137 | "lodash.debounce": "^4.0.8", 138 | "map-like": "^1.0.3", 139 | "markdown-extensions": "^1.1.0", 140 | "normalize.css": "^6.0.0", 141 | "office-ui-fabric-react": "^2.27.0", 142 | "react": "^15.4.1", 143 | "react-codemirror": "^0.3.0", 144 | "react-dom": "^15.4.1", 145 | "react-router": "^3.0.0", 146 | "strip-json-comments": "^2.0.1", 147 | "suitcss-classnames": "^1.1.0", 148 | "textlint": "^8.0.1", 149 | "textlint-app-locator": "file:packages/textlint-app-locator", 150 | "textlint-message-to-codemirror": "^1.0.0", 151 | "textlint-server-package-manager": "file:packages/textlint-server-package-manager" 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/textlint-app-locator/.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Debug log from npm 32 | npm-debug.log 33 | 34 | 35 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Global/JetBrains.gitignore 36 | 37 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 38 | 39 | *.iml 40 | 41 | ## Directory-based project format: 42 | .idea/ 43 | # if you remove the above rule, at least ignore the following: 44 | 45 | # User-specific stuff: 46 | # .idea/workspace.xml 47 | # .idea/tasks.xml 48 | # .idea/dictionaries 49 | 50 | # Sensitive or high-churn files: 51 | # .idea/dataSources.ids 52 | # .idea/dataSources.xml 53 | # .idea/sqlDataSources.xml 54 | # .idea/dynamic.xml 55 | # .idea/uiDesigner.xml 56 | 57 | # Gradle: 58 | # .idea/gradle.xml 59 | # .idea/libraries 60 | 61 | # Mongo Explorer plugin: 62 | # .idea/mongoSettings.xml 63 | 64 | ## File-based project format: 65 | *.ipr 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | -------------------------------------------------------------------------------- /packages/textlint-app-locator/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /packages/textlint-app-locator/README.md: -------------------------------------------------------------------------------- 1 | # textlint-app-locator 2 | 3 | Service Locator for textlint-app 4 | 5 | ## Install 6 | 7 | Install with [npm](https://www.npmjs.com/): 8 | 9 | npm install textlint-app-locator 10 | 11 | ## Usage 12 | 13 | ```js 14 | const locator = require("textlint-app-locator"); 15 | // set 16 | locator.context = context; 17 | // get 18 | locator.context.useCase(useCase); 19 | 20 | ``` 21 | 22 | ## Changelog 23 | 24 | See [Releases page](https://github.com/packages/textlint-app-locator/releases). 25 | 26 | ## Running tests 27 | 28 | Install devDependencies and Run `npm test`: 29 | 30 | npm i -d && npm test 31 | 32 | ## Contributing 33 | 34 | Pull requests and stars are always welcome. 35 | 36 | For bugs and feature requests, [please create an issue](https://github.com/packages/textlint-app-locator/issues). 37 | 38 | 1. Fork it! 39 | 2. Create your feature branch: `git checkout -b my-new-feature` 40 | 3. Commit your changes: `git commit -am 'Add some feature'` 41 | 4. Push to the branch: `git push origin my-new-feature` 42 | 5. Submit a pull request :D 43 | 44 | ## Author 45 | 46 | - [github/azu](https://github.com/azu) 47 | - [twitter/azu_re](https://twitter.com/azu_re) 48 | 49 | ## License 50 | 51 | MIT © azu 52 | -------------------------------------------------------------------------------- /packages/textlint-app-locator/lib/textlint-app-locator.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const TextlintAppLocator = { 4 | /** 5 | * @type {Context} 6 | */ 7 | context: null 8 | }; 9 | module.exports = TextlintAppLocator; -------------------------------------------------------------------------------- /packages/textlint-app-locator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": { 3 | "test": "test" 4 | }, 5 | "author": "azu", 6 | "license": "MIT", 7 | "private": true, 8 | "files": [ 9 | "lib/" 10 | ], 11 | "name": "textlint-app-locator", 12 | "version": "1.1.0", 13 | "description": "Service Locator for textlint-app", 14 | "main": "lib/textlint-app-locator.js", 15 | "scripts": {}, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/packages/textlint-app-locator.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/packages/textlint-app-locator/issues" 22 | }, 23 | "homepage": "https://github.com/packages/textlint-app-locator" 24 | } 25 | -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Node.gitignore 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Debug log from npm 32 | npm-debug.log 33 | 34 | 35 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Global/JetBrains.gitignore 36 | 37 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 38 | 39 | *.iml 40 | 41 | ## Directory-based project format: 42 | .idea/ 43 | # if you remove the above rule, at least ignore the following: 44 | 45 | # User-specific stuff: 46 | # .idea/workspace.xml 47 | # .idea/tasks.xml 48 | # .idea/dictionaries 49 | 50 | # Sensitive or high-churn files: 51 | # .idea/dataSources.ids 52 | # .idea/dataSources.xml 53 | # .idea/sqlDataSources.xml 54 | # .idea/dynamic.xml 55 | # .idea/uiDesigner.xml 56 | 57 | # Gradle: 58 | # .idea/gradle.xml 59 | # .idea/libraries 60 | 61 | # Mongo Explorer plugin: 62 | # .idea/mongoSettings.xml 63 | 64 | ## File-based project format: 65 | *.ipr 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/README.md: -------------------------------------------------------------------------------- 1 | # textlint-message-to-codemirror 2 | 3 | Convert textlint'Ss messages to CodeMirror lint object 4 | 5 | ## Install 6 | 7 | Install with [npm](https://www.npmjs.com/): 8 | 9 | npm install textlint-message-to-codemirror 10 | 11 | ## Usage 12 | 13 | ```js 14 | const assert = require("assert"); 15 | const textlintToCodeMirror = require("textlint-message-to-codemirror"); 16 | describe("textlintToCodeMirror", () => { 17 | it("message to codemirror", () => { 18 | assert.deepEqual(textlintToCodeMirror({ 19 | message: "message", 20 | severity: 1, 21 | line: 1, 22 | column: 1 23 | }), { 24 | from: { 25 | ch: 0, 26 | line: 0 27 | }, 28 | to: { 29 | ch: 1, 30 | line: 0 31 | }, 32 | message: "message", 33 | severity: "warning" 34 | }); 35 | 36 | assert.deepEqual(textlintToCodeMirror({ 37 | message: "message", 38 | severity: 2, 39 | line: 10, 40 | column: 10 41 | }), { 42 | from: { 43 | ch: 9, 44 | line: 9 45 | }, 46 | to: { 47 | ch: 10, 48 | line: 9 49 | }, 50 | message: "message", 51 | severity: "error" 52 | }); 53 | }); 54 | }); 55 | ``` 56 | 57 | ## Running tests 58 | 59 | Install devDependencies and Run `npm test`: 60 | 61 | npm i -d && npm test 62 | 63 | ## Contributing 64 | 65 | 66 | Pull requests and stars are always welcome. 67 | 1. Fork it! 68 | 2. Create your feature branch: `git checkout -b my-new-feature` 69 | 3. Commit your changes: `git commit -am 'Add some feature'` 70 | 4. Push to the branch: `git push origin my-new-feature` 71 | 5. Submit a pull request :D 72 | 73 | ## Author 74 | 75 | - [github/azu](https://github.com/azu) 76 | - [twitter/azu_re](https://twitter.com/azu_re) 77 | 78 | ## License 79 | 80 | MIT © azu 81 | -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/lib/textlint-message-to-codemirror.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | var assert = require("assert"); 4 | function convertSeverity(severity) { 5 | switch (severity) { 6 | case 1: 7 | return "warning"; 8 | case 2: 9 | return "error"; 10 | default: 11 | return "error"; 12 | } 13 | } 14 | /** 15 | * Convert TextlintMessage to CodeMirror lint result object 16 | * @param {TextLintMessage} message 17 | * @returns {{from: {line: number, ch: number}, to: {line: number, ch: *}, message: *, severity: *}} 18 | */ 19 | module.exports = function convertTextlintMessageToCodeMirror(message) { 20 | assert(typeof message === "object", "messsage should be textlint message object"); 21 | // https://codemirror.net/doc/manual.html 22 | // the API uses objects with line and ch properties. Both are zero-based. 23 | const posFrom = {line: message.line - 1, ch: message.column - 1}; 24 | const posTo = {line: message.line - 1, ch: message.column}; 25 | return { 26 | from: posFrom, 27 | to: posTo, 28 | message: message.message, 29 | severity: convertSeverity(message.severity) 30 | } 31 | }; -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": { 3 | "test": "test" 4 | }, 5 | "author": "azu", 6 | "license": "MIT", 7 | "files": [ 8 | "bin/", 9 | "lib/", 10 | "src/" 11 | ], 12 | "name": "textlint-message-to-codemirror", 13 | "version": "1.0.0", 14 | "description": "Convert textlint's messages to CodeMirror lint object", 15 | "main": "lib/textlint-message-to-codemirror.js", 16 | "scripts": { 17 | "test": "mocha test" 18 | }, 19 | "keywords": [ 20 | "textlint", 21 | "codemirror" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/textlint/textlint-app.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/textlint/textlint-app/issues" 29 | }, 30 | "homepage": "https://github.com/textlint/textlint-app", 31 | "devDependencies": { 32 | "mocha": "^3.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/textlint-message-to-codemirror/test/textlint-message-to-codemirror-test.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const assert = require("assert"); 4 | const textlintToCodeMirror = require("../lib/textlint-message-to-codemirror"); 5 | describe("textlintToCodeMirror", () => { 6 | /* 7 | export class TextLintMessage { 8 | // See src/shared/type/MessageType.js 9 | // Message Type 10 | type: string; 11 | // Rule Id 12 | ruleId: string; 13 | message: string; 14 | // optional data 15 | data?: any; 16 | // FixCommand 17 | fix?: TextLintFixCommand; 18 | // location info 19 | // Text -> AST TxtNode(0-based columns) -> textlint -> TextLintMessage(**1-based columns**) 20 | line: number; // start with 1 21 | column: number;// start with 1 22 | // indexed-location 23 | index: number;// start with 0 24 | // Severity Level 25 | // See src/shared/type/SeverityLevel.js 26 | severity?: number; 27 | } 28 | */ 29 | it("message to codemirror", () => { 30 | assert.deepEqual(textlintToCodeMirror({ 31 | message: "message", 32 | severity: 1, 33 | line: 1, 34 | column: 1 35 | }), { 36 | from: { 37 | ch: 0, 38 | line: 0 39 | }, 40 | to: { 41 | ch: 1, 42 | line: 0 43 | }, 44 | message: "message", 45 | severity: "warning" 46 | }); 47 | 48 | assert.deepEqual(textlintToCodeMirror({ 49 | message: "message", 50 | severity: 2, 51 | line: 10, 52 | column: 10 53 | }), { 54 | from: { 55 | ch: 9, 56 | line: 9 57 | }, 58 | to: { 59 | ch: 10, 60 | line: 9 61 | }, 62 | message: "message", 63 | severity: "error" 64 | }); 65 | }); 66 | it("null and throw error", () => { 67 | assert.throws(() => { 68 | textlintToCodeMirror(null) 69 | }, Error); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "env": { 6 | "development": { 7 | "presets": [ 8 | "jsdoc-to-assert", 9 | "power-assert" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | 3 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Node.gitignore 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 31 | node_modules 32 | 33 | # Debug log from npm 34 | npm-debug.log 35 | 36 | 37 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Global/JetBrains.gitignore 38 | 39 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 40 | 41 | *.iml 42 | 43 | ## Directory-based project format: 44 | .idea/ 45 | # if you remove the above rule, at least ignore the following: 46 | 47 | # User-specific stuff: 48 | # .idea/workspace.xml 49 | # .idea/tasks.xml 50 | # .idea/dictionaries 51 | 52 | # Sensitive or high-churn files: 53 | # .idea/dataSources.ids 54 | # .idea/dataSources.xml 55 | # .idea/sqlDataSources.xml 56 | # .idea/dynamic.xml 57 | # .idea/uiDesigner.xml 58 | 59 | # Gradle: 60 | # .idea/gradle.xml 61 | # .idea/libraries 62 | 63 | # Mongo Explorer plugin: 64 | # .idea/mongoSettings.xml 65 | 66 | ## File-based project format: 67 | *.ipr 68 | *.iws 69 | 70 | ## Plugin-specific files: 71 | 72 | # IntelliJ 73 | out/ 74 | 75 | # mpeltonen/sbt-idea plugin 76 | .idea_modules/ 77 | 78 | # JIRA plugin 79 | atlassian-ide-plugin.xml 80 | 81 | # Crashlytics plugin (for Android Studio and IntelliJ) 82 | com_crashlytics_export_strings.xml 83 | crashlytics.properties 84 | crashlytics-build.properties 85 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/README.md: -------------------------------------------------------------------------------- 1 | # textlint-server-package-manager 2 | 3 | textlint app server for npm 4 | 5 | ## Install 6 | 7 | Install with [npm](https://www.npmjs.com/): 8 | 9 | npm install textlint-server-package-manager 10 | 11 | ## Usage 12 | 13 | ### constructor({filePath}) 14 | 15 | ### install(textlintrc) 16 | 17 | ### getTextlintrc(): string 18 | ### writeTextlintrc(textlintrc): void 19 | 20 | ### checkIntegrity() : boolean 21 | 22 | ## Changelog 23 | 24 | See [Releases page](https://github.com/packages/textlint-server-package-manager/releases). 25 | 26 | ## Running tests 27 | 28 | Install devDependencies and Run `npm test`: 29 | 30 | npm i -d && npm test 31 | 32 | ## Contributing 33 | 34 | Pull requests and stars are always welcome. 35 | 36 | For bugs and feature requests, [please create an issue](https://github.com/packages/textlint-server-package-manager/issues). 37 | 38 | 1. Fork it! 39 | 2. Create your feature branch: `git checkout -b my-new-feature` 40 | 3. Commit your changes: `git commit -am 'Add some feature'` 41 | 4. Push to the branch: `git push origin my-new-feature` 42 | 5. Submit a pull request :D 43 | 44 | ## Author 45 | 46 | - [github/azu](https://github.com/azu) 47 | - [twitter/azu_re](https://twitter.com/azu_re) 48 | 49 | ## License 50 | 51 | MIT © azu 52 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "directories": { 4 | "test": "test" 5 | }, 6 | "author": "azu", 7 | "license": "MIT", 8 | "files": [ 9 | "bin/", 10 | "lib/", 11 | "src/" 12 | ], 13 | "name": "textlint-server-package-manager", 14 | "version": "1.0.1", 15 | "description": "textlint app server for npm", 16 | "main": "lib/textlint-server-package-manager.js", 17 | "scripts": { 18 | "test": "mocha test/", 19 | "build": "cross-env NODE_ENV=production babel src --out-dir lib --source-maps", 20 | "watch": "babel src --out-dir lib --watch --source-maps", 21 | "prepublish": "npm run --if-present build" 22 | }, 23 | "keywords": [ 24 | "textlint-app" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/packages/textlint-server-package-manager.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/packages/textlint-server-package-manager/issues" 32 | }, 33 | "homepage": "https://github.com/packages/textlint-server-package-manager", 34 | "devDependencies": { 35 | "babel-cli": "^6.24.1", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-jsdoc-to-assert": "^4.0.0", 38 | "babel-preset-power-assert": "^1.0.0", 39 | "babel-register": "^6.24.1", 40 | "cross-env": "^4.0.0", 41 | "mocha": "^3.3.0", 42 | "power-assert": "^1.4.2" 43 | }, 44 | "dependencies": { 45 | "@azu/npm-programmatic": "^0.2.0", 46 | "del": "^2.2.2", 47 | "mkdirp": "^0.5.1", 48 | "textlintrc-to-pacakge-list": "^1.2.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/src/textlint-server-package-manager.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const del = require("del"); 6 | const mkdirp = require("mkdirp"); 7 | const npm = require("@azu/npm-programmatic"); 8 | const toPackageList = require("textlintrc-to-pacakge-list"); 9 | const stripJsonComments = require("strip-json-comments"); 10 | module.exports = class TextlintPackageManger { 11 | constructor(packageDirectory) { 12 | this.packageDirectory = packageDirectory; 13 | const textlintrcFileName = ".textlintrc"; 14 | this.textlintrcFilePath = path.join(this.packageDirectory, textlintrcFileName); 15 | } 16 | 17 | /** 18 | * Install textlint rule package written in `textlintrc` 19 | * @param {string} textlintrc 20 | * @param {boolean} [force] 21 | * @return {Promise} 22 | */ 23 | install(textlintrc, { force = false } = {}) { 24 | if (force) { 25 | this.clean(); 26 | } 27 | return Promise.resolve().then(() => { 28 | const json = JSON.parse(stripJsonComments(textlintrc)); 29 | return toPackageList(json); 30 | }).then(packageNames => { 31 | mkdirp.sync(this.packageDirectory); 32 | this.createPackageJSON(); 33 | return npm.install(packageNames, { 34 | cwd: this.packageDirectory, 35 | save: true 36 | }); 37 | }).then(() => { 38 | return this.writeTextlintrc(textlintrc); 39 | }); 40 | } 41 | 42 | /** 43 | * @returns {Promise} 44 | */ 45 | getTextlintrc() { 46 | return new Promise((resolve, reject) => { 47 | fs.readFile(this.textlintrcFilePath, "utf-8", (error, content) => { 48 | if (error) { 49 | reject(error); 50 | } else { 51 | resolve(content); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | /** 58 | * check integrity textlintrc and installed packages. 59 | * @returns {Promise} 60 | */ 61 | checkIntegrity() { 62 | return npm.list(this.packageDirectory).then(packageNames => { 63 | return this.getTextlintrc().then(() => { 64 | const packageNames = toPackageList(this.textlintrcFilePath); 65 | return packageNames.every(packageName => { 66 | return packageNames.indexOf(packageName) !== -1; 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | 73 | /** 74 | * @param {string} textlintrc 75 | * @returns {Promise} 76 | */ 77 | writeTextlintrc(textlintrc) { 78 | return new Promise((resolve, reject) => { 79 | fs.writeFile(this.textlintrcFilePath, textlintrc, "utf-8", error => { 80 | if (error) { 81 | reject(error); 82 | } else { 83 | resolve(); 84 | } 85 | }); 86 | }); 87 | } 88 | 89 | 90 | createPackageJSON() { 91 | const packageFilePath = path.join(this.packageDirectory, "package.json"); 92 | if (fs.existsSync(packageFilePath)) { 93 | return; 94 | } 95 | fs.writeFileSync(packageFilePath, JSON.stringify(this._createPackageContent(), null, 4), "utf-8"); 96 | } 97 | 98 | clean() { 99 | del.sync( 100 | [ 101 | path.join(this.packageDirectory, "package.json"), 102 | path.join(this.packageDirectory, "node_modules") 103 | ], { 104 | force: true 105 | } 106 | ); 107 | } 108 | 109 | /** 110 | * create minimal package.json 111 | * @returns {Object} 112 | * @private 113 | */ 114 | _createPackageContent() { 115 | return { 116 | "private": true 117 | }; 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register -------------------------------------------------------------------------------- /packages/textlint-server-package-manager/test/textlint-server-package-manager-test.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const assert = require("assert"); 4 | const path = require("path"); 5 | const PackageManager = require("../src/textlint-server-package-manager"); 6 | describe("PackageManager", () => { 7 | it("has ", () => { 8 | const pkg = new PackageManager(__dirname); 9 | assert(pkg.packageDirectory === __dirname); 10 | assert(pkg.textlintrcFilePath === path.join(__dirname, ".textlintrc")); 11 | }) 12 | }); -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "plugins": { 3 | "postcss-easy-import": {}, 4 | "postcss-custom-properties": {}, 5 | "postcss-calc": {}, 6 | "postcss-custom-media": {}, 7 | "autoprefixer": { 8 | "browsers": "> 5%" 9 | }, 10 | "postcss-reporter": {} 11 | } 12 | }; -------------------------------------------------------------------------------- /src/node/Application.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import { app } from "electron"; 4 | import { BrowserWindow } from "electron"; 5 | const url = require("url"); 6 | import path from "path"; 7 | import windowStateKeeper from "electron-window-state"; 8 | import AppUpdater from "./electron/AppUpdater"; 9 | export default class Application { 10 | get isDeactived() { 11 | return this.mainWindow === null; 12 | } 13 | 14 | constructor() { 15 | this.mainWindow = null; 16 | } 17 | 18 | launch() { 19 | // command line 20 | const mainWindowState = windowStateKeeper({ 21 | defaultWidth: 800, 22 | defaultHeight: 600 23 | }); 24 | const title = require("../../package.json").name; 25 | process.title = title; 26 | this.mainWindow = new BrowserWindow({ 27 | title: title, 28 | x: mainWindowState.x, 29 | y: mainWindowState.y, 30 | width: mainWindowState.width, 31 | height: mainWindowState.height 32 | }); 33 | const index = { 34 | html: "/app/index.html" 35 | }; 36 | const format = url.format({ 37 | pathname: path.join(__dirname, "..", "..", "app", "index.html"), 38 | protocol: "file:", 39 | slashes: true 40 | }); 41 | this.mainWindow.loadURL(format); 42 | this.mainWindow.webContents.on("did-finish-load", () => { 43 | // loaded 44 | }); 45 | // automatically (the listeners will be removed when the window is closed) 46 | // and restore the maximized or full screen state 47 | mainWindowState.manage(this.mainWindow); 48 | 49 | if (process.env.NODE_ENV !== "production") { 50 | this.mainWindow.openDevTools(); 51 | } 52 | 53 | new AppUpdater(); 54 | } 55 | 56 | show() { 57 | this.mainWindow.show(); 58 | } 59 | 60 | hide() { 61 | this.mainWindow.hide(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/node/electron/AppUpdater.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import {BrowserWindow} from "electron"; 4 | import {dialog} from "electron"; 5 | import {autoUpdater} from "electron-updater"; 6 | const os = require("os"); 7 | const isDev = require("electron-is-dev"); 8 | 9 | /** 10 | * @param {string} title 11 | * @param {string} message 12 | */ 13 | function notify(title, message) { 14 | const windows = BrowserWindow.getAllWindows(); 15 | if (windows.length === 0) { 16 | return; 17 | } 18 | const options = { 19 | type: "info", 20 | buttons: ["OK"], 21 | title: title, 22 | message: message 23 | }; 24 | 25 | dialog.showMessageBox(windows[0], options); 26 | } 27 | 28 | export default class AppUpdater { 29 | constructor() { 30 | if (isDev) { 31 | return; 32 | } 33 | 34 | const platform = os.platform(); 35 | if (platform === "linux") { 36 | return; 37 | } 38 | 39 | autoUpdater.logger = console; 40 | autoUpdater.signals.updateDownloaded(it => { 41 | notify("A new update is ready to install", `Version ${it.version} is downloaded and will be automatically installed on Quit`); 42 | }); 43 | autoUpdater.checkForUpdates(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/node/index.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | 4 | const defaultMenu = require("electron-default-menu"); 5 | import {Menu, app, shell} from "electron"; 6 | import Application from "./Application"; 7 | import registerIPC from "./ipc/textlint-ipc"; 8 | let application = null; 9 | function startRenderApp() { 10 | application = new Application(); 11 | application.launch(); 12 | } 13 | function installExtension() { 14 | return new Promise((resolve, reject) => { 15 | if (process.env.NODE_ENV === "development") { 16 | const installer = require("electron-devtools-installer"); // eslint-disable-line global-require 17 | 18 | const extension = "REACT_DEVELOPER_TOOLS"; 19 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 20 | return installer.default(installer[extension], forceDownload).then(resolve, reject); 21 | } 22 | resolve(); 23 | }); 24 | 25 | } 26 | // Quit when all windows are closed. 27 | app.on("window-all-closed", () => { 28 | app.quit(); 29 | }); 30 | 31 | app.on("activate", () => { 32 | if (!application) { 33 | return; 34 | } 35 | // On OS X it's common to re-create a window in the app when the 36 | // dock icon is clicked and there are no other windows open. 37 | if (application.isDeactived) { 38 | application.launch(); 39 | } else { 40 | application.show(); 41 | } 42 | }); 43 | 44 | app.on("ready", () => { 45 | // Get template for default menu 46 | const menu = defaultMenu(app, shell); 47 | // Set top-level application menu, using modified template 48 | Menu.setApplicationMenu(Menu.buildFromTemplate(menu)); 49 | if (process.env.NODE_ENV === "development") { 50 | installExtension().then(() => { 51 | startRenderApp(); 52 | }); 53 | } else { 54 | startRenderApp(); 55 | } 56 | registerIPC(); 57 | }); 58 | -------------------------------------------------------------------------------- /src/node/infra/textlint/TextlintAPIServer.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const textlint = require("textlint"); 4 | export default class TextlintAPI { 5 | constructor() { 6 | this.configFile = null; 7 | this.rulesBaseDirectory = null; 8 | /** 9 | * @type {TextLintEngine} 10 | * @private 11 | */ 12 | this._textLintEngine = null; 13 | /** 14 | * @type {TextFixEngine} 15 | * @private 16 | */ 17 | this._textFixEngine = null; 18 | } 19 | 20 | 21 | get lintEngine() { 22 | if (this._textLintEngine) { 23 | return this._textLintEngine; 24 | } 25 | const textLineEngine = new textlint.TextLintEngine({ 26 | configFile: this.configFile, 27 | rulesBaseDirectory: this.rulesBaseDirectory 28 | }); 29 | this._textLintEngine = textLineEngine; 30 | return textLineEngine; 31 | } 32 | 33 | 34 | get fixEngine() { 35 | if (this._textFixEngine) { 36 | return this._textFixEngine; 37 | } 38 | const textFixEngine = new textlint.TextFixEngine({ 39 | configFile: this.configFile, 40 | rulesBaseDirectory: this.rulesBaseDirectory 41 | }); 42 | this._textFixEngine = textFixEngine; 43 | return textFixEngine; 44 | } 45 | 46 | /** 47 | * @param {string} configFile path to config file 48 | * @param {string} rulesBaseDirectory path to directory of node_modules 49 | */ 50 | setup({ 51 | configFile, 52 | rulesBaseDirectory 53 | }) { 54 | this.configFile = configFile; 55 | this.rulesBaseDirectory = rulesBaseDirectory; 56 | this.reset(); 57 | } 58 | 59 | /** 60 | * @param {string} text 61 | * @param {string} [ext] 62 | * @returns {Promise.} 63 | */ 64 | lintText(text, ext = ".md") { 65 | return this.lintEngine.executeOnText(text, ext).then(results => { 66 | return this._flattenResultToMessages(results); 67 | }); 68 | } 69 | 70 | /** 71 | * @param {string} text 72 | * @param {string} [ext] 73 | * @returns {Promise.} 74 | */ 75 | fixText(text, ext = ".md") { 76 | return this.fixEngine.executeOnText(text, ext).then(results => { 77 | return results[0]; 78 | }); 79 | } 80 | 81 | /** 82 | * reset engine 83 | */ 84 | reset() { 85 | this._textFixEngine = null; 86 | this._textLintEngine = null; 87 | } 88 | 89 | _flattenResultToMessages(results) { 90 | const lintMessages = []; 91 | results.forEach(result => { 92 | result.messages.forEach(message => { 93 | lintMessages.push(message); 94 | }); 95 | }); 96 | return lintMessages; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/node/ipc/textlint-ipc-key.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | export default { 4 | setup: "textlint-api-setup", 5 | lintText: "textlint-api-lintText", 6 | fixText: "textlint-api-fixText" 7 | }; 8 | -------------------------------------------------------------------------------- /src/node/ipc/textlint-ipc.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const ipcPromise = require("ipc-promise"); 4 | import TextLintAPIServer from "../infra/textlint/TextlintAPIServer"; 5 | import Key from "./textlint-ipc-key"; 6 | /** 7 | * register ipc 8 | */ 9 | export default function registerIPC() { 10 | const apiServer = new TextLintAPIServer(); 11 | ipcPromise.on(Key.setup, ({ 12 | configFile, 13 | rulesBaseDirectory 14 | }) => { 15 | apiServer.setup({ 16 | configFile, 17 | rulesBaseDirectory 18 | }); 19 | return Promise.resolve(); 20 | }); 21 | ipcPromise.on(Key.lintText, ({text, ext}) => { 22 | return apiServer.lintText(text, ext); 23 | }); 24 | ipcPromise.on(Key.fixText, ({text, ext}) => { 25 | return apiServer.fixText(text, ext); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/components/App.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | // 5 | import {Router, hashHistory, Route} from "react-router"; 6 | // page 7 | import NavWrapper from "./pages/NavWrapper"; 8 | export default class App extends React.Component { 9 | 10 | render() { 11 | const routes = NavWrapper.routes.map(routeObject => { 12 | return ; 13 | }); 14 | return 15 | {routes} 16 | ; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/components/container/NavigationContainer/NavigationContainer.css: -------------------------------------------------------------------------------- 1 | .NavigationContainer-logo { 2 | width: 100%; 3 | height: auto; 4 | background-color: white; 5 | } -------------------------------------------------------------------------------- /src/renderer/components/container/NavigationContainer/NavigationContainer.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const classnames = require("classnames"); 5 | import {Nav} from "office-ui-fabric-react"; 6 | import TextlintLogo from "../../project/TextlintLogo/TextlintLogo"; 7 | export default class NavigationContainer extends React.Component { 8 | static contextTypes = { 9 | router: React.PropTypes.object 10 | }; 11 | 12 | static propTypes = { 13 | className: React.PropTypes.string, 14 | currentPath: React.PropTypes.string, 15 | routes: React.PropTypes.arrayOf(React.PropTypes.object) 16 | }; 17 | 18 | render() { 19 | const className = classnames("NavigationContainer", this.props.className); 20 | const routes = this.props.routes; 21 | const currentPath = this.props.currentPath; 22 | const groups = [ 23 | { 24 | links: routes.map(route => { 25 | return { 26 | name: route.name, 27 | title: " ", 28 | url: route.path, 29 | key: route.path, 30 | onClick: event => { 31 | event.preventDefault(); 32 | if (route.path !== currentPath) { 33 | this.context.router.push(route.path); 34 | } 35 | } 36 | }; 37 | }) 38 | } 39 | ]; 40 | return ( 41 |
42 | 43 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/components/container/TextlintEditorContainer/TextlintEditorContainer.css: -------------------------------------------------------------------------------- 1 | .TextlintEditorContainer { 2 | height: 100%; 3 | } 4 | 5 | .TextlintEditorContainer-wrapper { 6 | display: flex; 7 | flex-flow: column nowrap; 8 | align-items: stretch; 9 | height: 100%; 10 | max-height: 100%; 11 | } 12 | 13 | .TextlintEditorContainer-wrapper > * { 14 | flex: 1 1 auto; 15 | } 16 | 17 | /* | <Toolbar> */ 18 | .TextlintEditorContainer-header { 19 | display: flex; 20 | flex-flow: row; 21 | } 22 | 23 | .TextlintEditorContainer-title { 24 | margin: 0 auto; 25 | padding: 0 1em; 26 | } 27 | 28 | .TextlintEditorContainer-toolbar { 29 | flex: 1; 30 | text-align: right; 31 | justify-content: flex-end; 32 | } 33 | 34 | .TextlintEditorContainer-wrapper .TextlintEditor { 35 | height: 100%; 36 | } 37 | 38 | /* Editor | Result */ 39 | .TextlintEditorContainer-main { 40 | display: flex; 41 | flex-flow: row; 42 | height: 100%; 43 | } 44 | 45 | .TextlintEditor { 46 | flex: 4 1 auto; 47 | max-width: calc(100% - 20%); 48 | } 49 | 50 | .TextlintEditorContainer-mainResult { 51 | flex: 1 1 auto; 52 | max-width: 20%; 53 | min-width: 10%; 54 | height: 100%; 55 | color: white; 56 | background-color: #333; 57 | } 58 | 59 | /* height auto */ 60 | .TextlintEditorContainer .ReactCodeMirror { 61 | height: 100%; 62 | } 63 | 64 | /* list */ 65 | .TextlintEditorContainer-mainResult { 66 | padding-left: 0.5rem; 67 | } 68 | 69 | .TextlintEditorContainer-mainResultList { 70 | padding-left: 0; 71 | font-size: 12px; 72 | } -------------------------------------------------------------------------------- /src/renderer/components/container/TextlintEditorContainer/TextlintEditorContainer.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const { dialog } = require("electron").remote; 5 | const debounce = require("lodash.debounce"); 6 | const markdownExtensions = require("markdown-extensions"); 7 | const locator = require("textlint-app-locator"); 8 | import TextlintEditor from "../../project/TextlintEditor/TextlintEditor"; 9 | import ElectronWindowTitle from "../../project/ElectronWindowTitle/ElectronWindowTitle"; 10 | import SaveAsNewFileUseCase from "../../../use-case/textlint-editor/SaveAsNewFileUseCase"; 11 | import FileToolbar from "../../project/FileToolbar/FileToolbar"; 12 | import LintResultList from "../../project/LintResultList/LintResultList"; 13 | // state 14 | import { TextlintEditorState } from "../../../store/TextlintEditor/TextlintEditorStore"; 15 | import { TextlintrcEditorState } from "../../../store/TextlintrcEditor/TextlintrcEditorStore"; 16 | // use-case 17 | import OpenNewFileUseCase from "../../../use-case/textlint-editor/OpenNewFileUseCase.js"; 18 | import UpdateTextUseCase from "../../../use-case/textlint-editor/UpdateTextUseCase"; 19 | import FixTextUseCase from "../../../use-case/textlint-editor/FixTextUseCase"; 20 | import { DefaultButton } from "office-ui-fabric-react"; 21 | export default class TextlintEditorContainer extends React.Component { 22 | static propTypes = { 23 | textlintEditor: React.PropTypes.instanceOf(TextlintEditorState).isRequired, 24 | textlintrcEditor: React.PropTypes.instanceOf(TextlintrcEditorState).isRequired 25 | }; 26 | 27 | constructor() { 28 | super(); 29 | /** 30 | * @type {null|TextlintEditor} 31 | */ 32 | this.TextlintEditor = null; 33 | this.state = { 34 | // codemirror error format 35 | lintErrors: [], 36 | // textlint message format 37 | lintMessages: [] 38 | }; 39 | this.onChangeTextlintEditor = value => { 40 | locator.context.useCase(UpdateTextUseCase.create()).execute(value); 41 | }; 42 | this.onLintError = ({ lintErrors, lintMessages }) => { 43 | this.setState({ 44 | lintErrors, 45 | lintMessages 46 | }); 47 | }; 48 | this.onClickOpenFile = event => { 49 | const options = { 50 | title: "Open File for linting", 51 | filters: [ 52 | { name: "Markdown", extensions: markdownExtensions } 53 | ], 54 | properties: ["openFile"] 55 | }; 56 | dialog.showOpenDialog(options, filenames => { 57 | if (!filenames) { 58 | return; 59 | } 60 | if (filenames.length <= 0) { 61 | return; 62 | } 63 | const filename = filenames[0]; 64 | locator.context.useCase(OpenNewFileUseCase.create()).execute(filename); 65 | }); 66 | }; 67 | this.onClickSaveAsNewFile = event => { 68 | const options = { 69 | title: "Save as File", 70 | filters: [ 71 | { name: "Markdown", extensions: ["md"] } 72 | ] 73 | }; 74 | dialog.showSaveDialog(options, filename => { 75 | if (!filename) { 76 | return; 77 | } 78 | locator.context.useCase(SaveAsNewFileUseCase.create()).execute(filename); 79 | }); 80 | }; 81 | this.onClickLintItem = lintError => { 82 | if (this.TextlintEditor) { 83 | this.TextlintEditor.jumpToPos({ 84 | line: lintError.from.line, 85 | ch: lintError.from.ch 86 | }); 87 | } 88 | }; 89 | this.onClickFixErrors = () => { 90 | locator.context.useCase(FixTextUseCase.create()).execute(); 91 | }; 92 | } 93 | 94 | /** 95 | * @param {Function} onOpenFile 96 | * @returns {[*]} 97 | */ 98 | createMenuItems({ onOpenFile, onSaveAsNewFile }) { 99 | return [ 100 | { 101 | name: "Open File", 102 | key: "OpenFile", 103 | icon: "OpenFile", 104 | onClick: onOpenFile 105 | }, 106 | { 107 | name: "Save as...", 108 | key: "SaveFile", 109 | icon: "Save", 110 | onClick: onSaveAsNewFile 111 | } 112 | ]; 113 | } 114 | 115 | render() { 116 | /** 117 | * @type {TextlintrcEditorState} 118 | */ 119 | const textlintrcEditor = this.props.textlintrcEditor; 120 | /** 121 | * @type {TextlintEditorState} 122 | */ 123 | const textlintEditor = this.props.textlintEditor; 124 | const items = this.createMenuItems({ 125 | onOpenFile: this.onClickOpenFile, 126 | onSaveAsNewFile: this.onClickSaveAsNewFile 127 | }); 128 | /** 129 | * @type {LintResultListItemProps[]} 130 | */ 131 | const messages = this.state.lintErrors.map((lintError, index) => { 132 | /** 133 | * @type {TextLintMessage} 134 | */ 135 | const lintMessage = this.state.lintMessages[index]; 136 | return { 137 | message: lintError.message, 138 | startLine: lintError.from.line, 139 | startCh: lintError.from.ch, 140 | isFixable: lintMessage.fix !== undefined, 141 | onClick: this.onClickLintItem.bind(this, lintError) 142 | }; 143 | }); 144 | const includesFixableMessage = messages.some(message => message.isFixable); 145 | const fixButton = includesFixableMessage 146 | ? <DefaultButton onClick={this.onClickFixErrors}>Fix all errors</DefaultButton> 147 | : null; 148 | return <div className="TextlintEditorContainer"> 149 | <div className="TextlintEditorContainer-wrapper"> 150 | <div className="TextlintEditorContainer-header"> 151 | <h1 className="TextlintEditorContainer-title ms-font-xxl ms-fontColor-themePrimary">Edit with 152 | textlint</h1> 153 | <ElectronWindowTitle>{textlintEditor.editingFileName}</ElectronWindowTitle> 154 | <div className="TextlintEditorContainer-toolbar"> 155 | <FileToolbar farItems={items}/> 156 | </div> 157 | </div> 158 | <div className="TextlintEditorContainer-main"> 159 | <TextlintEditor 160 | ref={c => this.TextlintEditor = c } 161 | className="TextlintEditorContainer-mainEditor" 162 | value={textlintEditor.textContent} 163 | defaultValue={textlintEditor.textContent} 164 | onChange={this.onChangeTextlintEditor} 165 | onLintError={this.onLintError} 166 | textlintrcFilePath={textlintrcEditor.filePath} 167 | modulesDirectory={textlintrcEditor.modulesDirectory} 168 | /> 169 | <div className="TextlintEditorContainer-mainResult"> 170 | <h2 className="TextlintEditorContainer-mainResultTitle ms-font-l">Lint Errors</h2> 171 | {fixButton} 172 | <LintResultList items={messages}/> 173 | </div> 174 | </div> 175 | </div> 176 | </div> 177 | ; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/renderer/components/container/TextlintrcEditorContainer/TextlintrcEditorContainer.css: -------------------------------------------------------------------------------- 1 | .TextlintrcEditorContainer { 2 | padding: 0 1rem; 3 | } 4 | 5 | .TextlintrcEditorContainer-editor { 6 | padding: 1rem 0; 7 | } 8 | 9 | .TextlintrcEditorContainer-button { 10 | margin-right: 0.5rem; 11 | } -------------------------------------------------------------------------------- /src/renderer/components/container/TextlintrcEditorContainer/TextlintrcEditorContainer.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import i18next from "i18next"; 5 | const locator = require("textlint-app-locator"); 6 | import TextlintrcEditor from "../../project/TextlintrcEditor/TextlintrcEditor"; 7 | import InstallButton from "../../project/InstallButton/InstallButton"; 8 | import SaveButton from "../../project/SaveButton/SaveButton"; 9 | import DirectoryInput from "../../project/DirectoryInput/DirectoryInput"; 10 | import MessageNotification from "../../project/MessageNotification/MessageNotification"; 11 | import { MessageBar, MessageBarType, Spinner, SpinnerSize } from "office-ui-fabric-react"; 12 | // use-case 13 | import InstallTextlintPackageUseCase from "../../../use-case/workspace/InstallTextlintPackageUseCase"; 14 | import UpdateTextlintrcUseCase from "../../../use-case/textlintrc/UpdateTextlintrcUseCase"; 15 | import UpdateWorkspaceDirectoryUseCase from "../../../use-case/workspace/UpdateWorkspaceDirectoryUseCase"; 16 | import WriteToTextlintrcUseCase from "../../../use-case/textlintrc/WriteToTextlintrcUseCase"; 17 | import { DismissInstallErrorUseCase } from "../../../use-case/textlintrc/DismissInstallErrorUseCase"; 18 | // state 19 | import { TextlintrcEditorState } from "../../../store/TextlintrcEditor/TextlintrcEditorStore"; 20 | export default class TextlintrcEditorContainer extends React.Component { 21 | 22 | static propTypes = { 23 | textlintrcEditor: React.PropTypes.instanceOf(TextlintrcEditorState).isRequired 24 | }; 25 | 26 | constructor() { 27 | super(); 28 | 29 | this.onClickInstall = event => { 30 | return locator.context.useCase(InstallTextlintPackageUseCase.create()).execute(); 31 | }; 32 | this.onClickSave = event => { 33 | return locator.context.useCase(WriteToTextlintrcUseCase.create()).execute(); 34 | }; 35 | this.onChangeValue = value => { 36 | locator.context.useCase(UpdateTextlintrcUseCase.create()).execute(value); 37 | }; 38 | this.onSubmitDirectory = workingDirectory => { 39 | if (workingDirectory) { 40 | locator.context.useCase(UpdateWorkspaceDirectoryUseCase.create()).execute(workingDirectory); 41 | } 42 | }; 43 | } 44 | 45 | render() { 46 | /** 47 | * @type {TextlintrcEditorState} 48 | */ 49 | const textlintrcEditor = this.props.textlintrcEditor; 50 | const workingDirectory = textlintrcEditor.workingDirectory; 51 | const loadingMessage = textlintrcEditor.isLoading 52 | ? <Spinner size={ SpinnerSize.large } 53 | label='Installing textlint rules...'/> 54 | : null; 55 | const errorMessage = textlintrcEditor.installFailureError 56 | ? <MessageBar messageBarType={ MessageBarType.error } onDismiss={() => { 57 | locator.context.useCase(DismissInstallErrorUseCase).execute(); 58 | }}>{textlintrcEditor.installFailureError.message}</MessageBar> 59 | : null; 60 | return <div className="TextlintrcEditorContainer"> 61 | <h1 className="TextlintrcEditorContainer-title ms-font-xxl ms-fontColor-themePrimary">.textlintrc</h1> 62 | <ol className="TextlintrcEditorContainer-usage"> 63 | <li>{i18next.t("Set working directory if needed.(Default: use textlint-app working dir)")}</li> 64 | <li>{i18next.t("Write .textlintrc configuration")}</li> 65 | <li>{i18next.t("Install textlint rules from the .textlintrc configuration.(Press \"Install\" button)")}</li> 66 | </ol> 67 | <MessageNotification>{loadingMessage}</MessageNotification> 68 | {errorMessage} 69 | <DirectoryInput defaultDir={workingDirectory} onSubmit={this.onSubmitDirectory}/> 70 | <TextlintrcEditor 71 | className="TextlintrcEditorContainer-editor" 72 | value={textlintrcEditor.textValue} 73 | onChange={this.onChangeValue} 74 | /> 75 | <SaveButton 76 | className="TextlintrcEditorContainer-button" 77 | disabled={textlintrcEditor.isLoading} 78 | onClick={this.onClickSave} 79 | /> 80 | <InstallButton 81 | className="TextlintrcEditorContainer-button" 82 | disabled={textlintrcEditor.isLoading} 83 | onClick={this.onClickInstall} 84 | /> 85 | </div>; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/renderer/components/pages/NavWrapper.css: -------------------------------------------------------------------------------- 1 | .App { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | display: flex; 7 | } 8 | 9 | .App-nav { 10 | flex: 2 1 auto; 11 | max-width: 12em; 12 | background-color: #333; 13 | padding-left: var(--App-navi--padding-left); 14 | } 15 | 16 | .App-main { 17 | flex: 8 1 auto; 18 | max-width: 90%; 19 | height: 100%; 20 | } -------------------------------------------------------------------------------- /src/renderer/components/pages/NavWrapper.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import NavigationContainer from "../container/NavigationContainer/NavigationContainer"; 5 | 6 | import TextlintEditorPage from "./TextlintEditorPage"; 7 | import TextlintrcEditorPage from "./TextlintrcEditorPage"; 8 | export default class NavWrapper extends React.Component { 9 | static contextTypes = { 10 | router: React.PropTypes.object 11 | }; 12 | static propTypes = { 13 | children: React.PropTypes.element 14 | }; 15 | 16 | static routes = [ 17 | { 18 | name: "Edit with textlint", 19 | path: "/", 20 | icon: "Edit", 21 | Component: TextlintEditorPage 22 | }, 23 | { 24 | name: "Settings", 25 | path: "/TextlintrcEditorPage", 26 | icon: "Settings", 27 | Component: TextlintrcEditorPage 28 | } 29 | ]; 30 | 31 | render() { 32 | const router = this.context.router; 33 | const currentPath = router.routes[0].path; 34 | return <div className="App"> 35 | <NavigationContainer 36 | className="App-nav" 37 | currentPath={currentPath} 38 | routes={NavWrapper.routes}/> 39 | <div className="App-main"> 40 | {this.props.children} 41 | </div> 42 | </div>; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/renderer/components/pages/TextlintEditorPage.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const locator = require("textlint-app-locator"); 5 | import NavWrapper from "./NavWrapper.js"; 6 | import TextlintEditorContainer from "../container/TextlintEditorContainer/TextlintEditorContainer"; 7 | 8 | export default class TextlintEditorPage extends React.Component { 9 | static contextTypes = { 10 | router: React.PropTypes.object 11 | }; 12 | 13 | constructor() { 14 | super(); 15 | this.state = locator.context.getState(); 16 | } 17 | 18 | componentWillMount() { 19 | this.releaseOnChage = locator.context.onChange(() => { 20 | this.setState(locator.context.getState()); 21 | }); 22 | } 23 | 24 | componentWillUnmount() { 25 | if (this.releaseOnChage) { 26 | this.releaseOnChage(); 27 | } 28 | } 29 | 30 | render() { 31 | return <NavWrapper> 32 | <TextlintEditorContainer {...this.state} /> 33 | </NavWrapper>; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/components/pages/TextlintrcEditorPage.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | 5 | const locator = require("textlint-app-locator"); 6 | import NavWrapper from "./NavWrapper.js"; 7 | import TextlintrcEditorContainer from "../container/TextlintrcEditorContainer/TextlintrcEditorContainer"; 8 | 9 | export default class TextlintrcEditorPage extends React.Component { 10 | static contextTypes = { 11 | router: React.PropTypes.object 12 | }; 13 | 14 | constructor() { 15 | super(); 16 | this.state = locator.context.getState(); 17 | } 18 | 19 | componentWillMount() { 20 | this.releaseOnChage = locator.context.onChange(() => { 21 | this.setState(locator.context.getState()); 22 | }); 23 | } 24 | 25 | componentWillUnmount() { 26 | if (this.releaseOnChage) { 27 | this.releaseOnChage(); 28 | } 29 | } 30 | 31 | render() { 32 | return <NavWrapper> 33 | <TextlintrcEditorContainer {...this.state} /> 34 | </NavWrapper>; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/renderer/components/project/DirectoryInput/DirectoryInput.css: -------------------------------------------------------------------------------- 1 | .DirectoryInput-main { 2 | display: flex; 3 | } 4 | 5 | /* align with bottom */ 6 | .DirectoryInput-textField { 7 | width: 100%; 8 | margin: 0 !important; 9 | } 10 | 11 | .DirectoryInput-submitButton { 12 | align-self: flex-end; 13 | width: 120px; 14 | } -------------------------------------------------------------------------------- /src/renderer/components/project/DirectoryInput/DirectoryInput.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import i18next from "i18next"; 5 | import { 6 | TextField, 7 | DefaultButton, 8 | Label 9 | } from "office-ui-fabric-react"; 10 | export default class DirectoryInput extends React.Component { 11 | static propTypes = { 12 | onSubmit: React.PropTypes.func.isRequired, 13 | defaultDir: React.PropTypes.string 14 | }; 15 | 16 | constructor() { 17 | super(); 18 | this.state = { 19 | value: undefined 20 | }; 21 | 22 | this._onChanged = value => { 23 | if (!value) { 24 | return; 25 | } 26 | return this.setState({ 27 | value 28 | }); 29 | }; 30 | 31 | } 32 | 33 | render() { 34 | const submit = () => { 35 | this.props.onSubmit(this.state.value); 36 | }; 37 | return <div className="DirectoryInput"> 38 | <Label>{i18next.t("You can set .textlintrc working directory.")}</Label> 39 | <div className="DirectoryInput-main"> 40 | <TextField 41 | className='DirectoryInput-textField' 42 | label="Working directory" 43 | defaultValue={this.props.defaultDir} 44 | onChanged={this._onChanged} 45 | /> 46 | <DefaultButton 47 | className="DirectoryInput-submitButton" 48 | onClick={submit}> 49 | {i18next.t("Load")} 50 | </DefaultButton> 51 | </div> 52 | </div>; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/renderer/components/project/ElectronWindowTitle/ElectronWindowTitle.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const remote = require("electron").remote; 5 | export default class ElectronWindowTitle extends React.Component { 6 | static propTypes = { 7 | children: React.PropTypes.string.isRequired 8 | }; 9 | 10 | shouldComponentUpdate(nextProps) { 11 | return this.props.children !== nextProps.children; 12 | } 13 | 14 | componentDidMount() { 15 | const currentWindow = remote.getCurrentWindow(); 16 | currentWindow.setTitle(this.getTitle()); 17 | } 18 | 19 | componentDidUpdate() { 20 | const currentWindow = remote.getCurrentWindow(); 21 | currentWindow.setTitle(this.getTitle()); 22 | } 23 | 24 | getTitle() { 25 | return `textlint app${ this.props.children ? ` [${ this.props.children }]` : ""}`; 26 | } 27 | 28 | render() { 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/components/project/FileToolbar/FileToolbar.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import {CommandBar} from "office-ui-fabric-react"; 5 | /** 6 | * @param {Array} [items] 7 | * @param {Array} [farItems] 8 | * @returns {XML} 9 | * @constructor 10 | */ 11 | export default function FileToolbar({items = [], farItems = []}) { 12 | return <CommandBar 13 | className="FileToolbar" 14 | isSearchBoxVisible={ false } 15 | items={items} 16 | farItems={farItems} 17 | />; 18 | } 19 | FileToolbar.PropTypes = { 20 | items: React.PropTypes.array, 21 | farItems: React.PropTypes.array 22 | }; 23 | -------------------------------------------------------------------------------- /src/renderer/components/project/InstallButton/InstallButton.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import i18next from "i18next"; 4 | const React = require("react"); 5 | const classnames = require("classnames"); 6 | import { PrimaryButton } from "office-ui-fabric-react"; 7 | export default class InstallButton extends React.Component { 8 | static propTypes = { 9 | className: React.PropTypes.string, 10 | disabled: React.PropTypes.bool, 11 | onClick: React.PropTypes.func.isRequired 12 | }; 13 | 14 | render() { 15 | return <PrimaryButton 16 | disabled={this.props.disabled} 17 | className={classnames("InstallButton", this.props.className)} 18 | iconProps={{ iconName: "Download" }} 19 | onClick={this.props.onClick}> 20 | {i18next.t("Install")} 21 | </PrimaryButton>; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/components/project/LintResultList/LintResultList.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import LintResultListItem from "./LintResultListItem"; 5 | import {List} from "office-ui-fabric-react"; 6 | export default class LintResultList extends React.Component { 7 | render() { 8 | return <div className="LintResultList " data-is-scrollable={ true }> 9 | <List 10 | items={ this.props.items } 11 | onRenderCell={ (item, index) => ( 12 | <LintResultListItem item={item}/> 13 | )} 14 | /> 15 | </div>; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/components/project/LintResultList/LintResultListItem.css: -------------------------------------------------------------------------------- 1 | .LintResultListItem { 2 | font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | font-size: 14px; 5 | font-weight: 400; 6 | box-sizing: border-box; 7 | margin: 0; 8 | box-shadow: none; 9 | padding: 9px 28px; 10 | position: relative; 11 | display: block; 12 | /* bottom line */ 13 | border-bottom: 1px dotted #eaeaea; 14 | } 15 | 16 | .LintResultListItem:hover { 17 | background-color: #555; 18 | cursor: pointer; 19 | outline: 1px solid transparent; 20 | } 21 | 22 | .LitResultLiteItem-metaText { 23 | font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; 24 | display: inline-block; 25 | text-align: right; 26 | font-size: 11px; 27 | font-weight: 400; 28 | color: #eff6fc; 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/components/project/LintResultList/LintResultListItem.is-error.css: -------------------------------------------------------------------------------- 1 | .LintResultListItem.is-error { 2 | border-left: 3px solid #d70030; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/components/project/LintResultList/LintResultListItem.is-fixable.css: -------------------------------------------------------------------------------- 1 | .LintResultListItem.is-fixable::before { 2 | content: "✔"; 3 | color: green; 4 | position: absolute; 5 | left: 0.2rem; 6 | top: 0.5rem; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/renderer/components/project/LintResultList/LintResultListItem.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const suitcssClassnames = require("suitcss-classnames"); 5 | const className = require("classnames"); 6 | /** 7 | * @typedef {Object} LintResultListItemProps 8 | * @property {string} message 9 | * @property {number} startLine 10 | * @property {number} startCh 11 | * @property {boolean} isFixable 12 | */ 13 | /** 14 | * @param {LintResultListItemProps} item 15 | * @returns {XML} 16 | * @constructor 17 | */ 18 | export default function LintResultListItem({item}) { 19 | const className = suitcssClassnames({ 20 | component: "LintResultListItem", 21 | states: { 22 | "is-error": true, 23 | "is-fixable": item.isFixable 24 | } 25 | }); 26 | return ( 27 | <div className={className} onClick={item.onClick}> 28 | <span className='ms-ListItem-tertiaryText'>{item.message}</span> 29 | <span className='LitResultLiteItem-metaText'>@{`${item.startLine}:${item.startCh}`}</span> 30 | </div> 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/renderer/components/project/MessageNotification/MessageNotification.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | 4 | const React = require("react"); 5 | import { 6 | MessageBar, 7 | MessageBarType 8 | } from "office-ui-fabric-react"; 9 | export default class MessageNotification extends React.Component { 10 | static propTypes = { 11 | children: React.PropTypes.element 12 | }; 13 | 14 | render() { 15 | if (!this.props.children) { 16 | return null; 17 | } 18 | return <MessageBar>{this.props.children}</MessageBar>; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/components/project/OpenFileButton/OpenFileButton.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | import { CommandButton } from "office-ui-fabric-react"; 5 | export default function OpenFileButton({ onClick }) { 6 | return <CommandButton onClick={onClick} iconProps={{ iconName: "OpenFile" }}> 7 | Open File 8 | </CommandButton>; 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/components/project/SaveButton/SaveButton.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import i18next from "i18next"; 4 | const React = require("react"); 5 | const classnames = require("classnames"); 6 | import { PrimaryButton } from "office-ui-fabric-react"; 7 | export default class SaveButton extends React.Component { 8 | static propTypes = { 9 | className: React.PropTypes.string, 10 | disabled: React.PropTypes.bool, 11 | onClick: React.PropTypes.func.isRequired 12 | }; 13 | 14 | render() { 15 | return <PrimaryButton 16 | disabled={this.props.disabled} 17 | className={classnames("SaveButton", this.props.className)} 18 | iconProps={{ iconName: "Save" }} 19 | onClick={this.props.onClick}> 20 | {i18next.t("Save")} 21 | </PrimaryButton>; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/components/project/TextlintEditor/TextlintEditor.css: -------------------------------------------------------------------------------- 1 | .TextlintEditor .CodeMirror { 2 | font-size: 16px; 3 | height: 100%; 4 | } -------------------------------------------------------------------------------- /src/renderer/components/project/TextlintEditor/TextlintEditor.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import i18next from "i18next"; 4 | // node 5 | const remote = require("electron").remote; 6 | const textlintToCodeMirror = require("textlint-message-to-codemirror"); 7 | const debug = require("debug")("textlint-app:TextlintEditor"); 8 | const debounce = require("lodash.debounce"); 9 | // infra 10 | import TextlintAPI from "../../../infra/textlint/TextlintAPI"; 11 | // main 12 | const React = require("react"); 13 | const CodeMirror = require("react-codemirror"); 14 | require("codemirror/mode/markdown/markdown.js"); 15 | require("codemirror/addon/lint/lint.js"); 16 | require("codemirror/addon/lint/lint.css"); 17 | // search 18 | require("codemirror/addon/dialog/dialog.css"); 19 | require("codemirror/addon/search/matchesonscrollbar.css"); 20 | require("codemirror/addon/dialog/dialog.js"); 21 | require("codemirror/addon/search/searchcursor.js"); 22 | require("codemirror/addon/search/search.js"); 23 | require("codemirror/addon/scroll/annotatescrollbar.js"); 24 | require("codemirror/addon/search/matchesonscrollbar.js"); 25 | require("codemirror/addon/search/jump-to-line.js"); 26 | export default class TextlintEditor extends React.Component { 27 | static propTypes = { 28 | modulesDirectory: React.PropTypes.string, 29 | textlintrcFilePath: React.PropTypes.string, 30 | value: React.PropTypes.string, 31 | defaultValue: React.PropTypes.string, 32 | onChange: React.PropTypes.func, 33 | onLintError: React.PropTypes.func 34 | }; 35 | 36 | constructor() { 37 | super(); 38 | 39 | /** 40 | * @private 41 | */ 42 | this._CodeMirror = null; 43 | this.state = { 44 | textValue: i18next.t(`# Usage 45 | 46 | 1. Setting .textlintrc. (Go to \`Settings\` tab) 47 | 2. Install textlint rules via .textlintrc. (In \`Settings\` tab) 48 | 3. Write Texts and Lint! (Here!) 49 | `) 50 | }; 51 | this.updateValue = this._updateValue.bind(this); 52 | this.validator = this._createValidator(); 53 | } 54 | 55 | jumpToPos({line, ch}) { 56 | if (!this._CodeMirror) { 57 | return; 58 | } 59 | const codeMirror = this._CodeMirror.getCodeMirror(); 60 | codeMirror.focus(); 61 | codeMirror.setCursor({line, ch}); 62 | } 63 | 64 | shouldComponentUpdate(nextProps, nextState) { 65 | if (this.state.textValue !== nextState.textValue) { 66 | return true; 67 | } 68 | 69 | if (this.props.value !== nextProps.value) { 70 | return true; 71 | } 72 | return false; 73 | } 74 | 75 | componentWillReceiveProps(nextProps) { 76 | if (this.props.value !== nextProps.value) { 77 | this._updateValue(nextProps.value); 78 | } 79 | if (this.props.textlintrcFilePath !== nextProps.textlintrcFilePath || 80 | this.props.modulesDirectory !== nextProps.modulesDirectory 81 | ) { 82 | this.validator = debounce(this._createValidator({ 83 | textlintrcFilePath: nextProps.textlintrcFilePath, 84 | nodeModulesDirectory: nextProps.modulesDirectory 85 | }), 300); 86 | } 87 | } 88 | 89 | componentDidMount() { 90 | if (this._CodeMirror) { 91 | const codeMirror = this._CodeMirror.getCodeMirror(); 92 | codeMirror.getScrollerElement().style.minHeight = "30em"; 93 | // Workaround for IME position 94 | // https://github.com/codemirror/CodeMirror/issues/4089 95 | // https://github.com/BoostIO/Boostnote/commit/8f1c198406d68ef7818a84f4201c6df446e14592 96 | codeMirror.getInputField().style.marginBottom = "-2em"; 97 | codeMirror.refresh(); 98 | } 99 | this.validator = debounce(this._createValidator({ 100 | textlintrcFilePath: this.props.textlintrcFilePath, 101 | nodeModulesDirectory: this.props.modulesDirectory 102 | }), 300); 103 | } 104 | 105 | render() { 106 | const options = { 107 | lineNumbers: true, 108 | lineWrapping: true, 109 | mode: "markdown", 110 | inputStyle: "textarea", 111 | extraKeys: {"Alt-F": "findPersistent"}, 112 | gutters: ["CodeMirror-lint-markers"], 113 | lint: { 114 | "getAnnotations": this.validator, 115 | "async": true 116 | } 117 | }; 118 | return <div className="TextlintEditor"> 119 | <CodeMirror 120 | ref={c => this._CodeMirror = c } 121 | value={this.state.textValue} 122 | defaultValue={this.props.defaultValue} 123 | onChange={this.updateValue} 124 | options={options}/> 125 | </div>; 126 | } 127 | 128 | /** 129 | * @param {string}value 130 | * @private 131 | */ 132 | _updateValue(value) { 133 | if (this.state.textValue !== value) { 134 | this.setState({ 135 | textValue: value 136 | }); 137 | this.props.onChange(value); 138 | } 139 | } 140 | 141 | /** 142 | * 143 | * @param {string} [textlintrcFilePath] 144 | * @param {string} [nodeModulesDirectory] 145 | * @returns {function()} 146 | * @private 147 | */ 148 | _createValidator({ 149 | textlintrcFilePath, 150 | nodeModulesDirectory 151 | } = {}) { 152 | debug("textlintrcFilePath", textlintrcFilePath, "nodeModulesDirectory", nodeModulesDirectory); 153 | if (!textlintrcFilePath || !nodeModulesDirectory) { 154 | return (text, callback) => { 155 | callback([]); 156 | }; 157 | } 158 | const textlintAPI = new TextlintAPI({ 159 | configFile: textlintrcFilePath, 160 | rulesBaseDirectory: nodeModulesDirectory 161 | }); 162 | let isLinting = false; 163 | return (text, callback) => { 164 | if (!text) { 165 | callback([]); 166 | return; 167 | } 168 | if (isLinting) { 169 | return; 170 | } 171 | isLinting = true; 172 | textlintAPI.lintText(text, ".md").then(lintMessages => { 173 | isLinting = false; 174 | debug(`Found ${lintMessages.length} Errors`); 175 | const lintErrors = lintMessages.map(textlintToCodeMirror); 176 | this.props.onLintError({ 177 | lintMessages, 178 | lintErrors 179 | }); 180 | callback(lintErrors); 181 | }).catch(error => { 182 | debug(error); 183 | }); 184 | }; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/renderer/components/project/TextlintLogo/TextlintLogo.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const src = require("./textlint-logo.png"); 4 | const React = require("react"); 5 | export default class TextlintLogo extends React.Component { 6 | render() { 7 | return <img src={src} alt="textlint" {...this.props}/>; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/components/project/TextlintLogo/textlint-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/textlint-app/66d4dd78242198fa19a80a97e7ab446789027512/src/renderer/components/project/TextlintLogo/textlint-logo.png -------------------------------------------------------------------------------- /src/renderer/components/project/TextlintrcEditor/TextlintrcEditor.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const React = require("react"); 4 | const classnamaes = require("classnames"); 5 | const CodeMirror = require("react-codemirror"); 6 | require("codemirror/lib/codemirror.css"); 7 | require("codemirror/mode/javascript/javascript"); 8 | import { Label } from "office-ui-fabric-react"; 9 | export default class TextlintrcEditor extends React.Component { 10 | static propTypes = { 11 | className: React.PropTypes.string, 12 | value: React.PropTypes.string, 13 | onChange: React.PropTypes.func 14 | }; 15 | 16 | constructor() { 17 | super(); 18 | this.state = { 19 | textValue: "" 20 | }; 21 | } 22 | 23 | componentWillReceiveProps(props) { 24 | if (this.state.textValue !== props.value) { 25 | this.setState({ 26 | textValue: props.value 27 | }); 28 | } 29 | } 30 | 31 | componentWillMount() { 32 | if (this.state.textValue !== this.props.value) { 33 | this.setState({ 34 | textValue: this.props.value 35 | }); 36 | } 37 | } 38 | 39 | render() { 40 | const options = { 41 | lineNumbers: true, 42 | mode: "javascript" 43 | }; 44 | const className = classnamaes("TextlintrcEditor", this.props.className); 45 | return <div className={className}> 46 | <Label>.textlintrc configuration</Label> 47 | <CodeMirror 48 | value={this.state.textValue} 49 | onChange={this.props.onChange} 50 | options={options}/> 51 | </div>; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/css/base.css: -------------------------------------------------------------------------------- 1 | html, body, #js-app { 2 | width: 100%; 3 | height: 100%; 4 | min-height: 100%; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { 10 | font-family: var(--font-family--base); 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } -------------------------------------------------------------------------------- /src/renderer/css/config.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --App-navi--padding-left: 0.5rem; 3 | --App-pading: 1rem; 4 | 5 | /* font family */ 6 | /* Gothic */ 7 | --font-family--base: Avenir, "Open Sans", "Helvetica Neue", Helvetica, Arial, Verdana, Roboto, "ヒラギノ角ゴ Pro W6", "Hiragino Kaku Gothic Pro", "Meiryo UI", "メイリオ", Meiryo, "MS Pゴシック", "MS PGothic", sans-serif; 8 | /* serif */ 9 | --font-family---serif: sans-serif; 10 | } -------------------------------------------------------------------------------- /src/renderer/domain/TextlintApp.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import TextlintAppId from "./TextlintAppId"; 4 | import TextlintEditor from "./textlint-editor/TextlintEditor"; 5 | import Workspaces from "./workspace/Workspaces"; 6 | export default class TextlintApp { 7 | 8 | /** 9 | * @type {TextlintAppId} 10 | */ 11 | id; 12 | 13 | /** 14 | * @param {TextlintEditor} textlintEditor 15 | * @param {Workspaces} workspaces 16 | */ 17 | constructor({workspaces, textlintEditor}) { 18 | this.id = new TextlintAppId(); 19 | this.workspaces = workspaces; 20 | this.textlintEditor = textlintEditor; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/domain/TextlintAppFactory.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import TextlintApp from "./TextlintApp"; 4 | import TextlintEditor from "./textlint-editor/TextlintEditor"; 5 | import TextlintEditorContentNoFile from "./textlint-editor/TextlintEditorContentNoFile"; 6 | import Workspaces from "./workspace/Workspaces"; 7 | import WorkspaceFactory from "./workspace/WorkspaceFactory"; 8 | export default class TextlintAppFactor { 9 | /** 10 | * @param {string} directory - default workspace directory 11 | * @param {Textlintrc} [textlintrc] - default textlintrc 12 | * @returns {TextlintApp} 13 | */ 14 | static create({directory, textlintrc}) { 15 | const workspace = WorkspaceFactory.create({ 16 | directory, 17 | textlintrc 18 | }); 19 | const workspaces = new Workspaces(workspace); 20 | const usage = ""; 21 | const textlintEditor = new TextlintEditor(new TextlintEditorContentNoFile({ 22 | text: usage 23 | })); 24 | return new TextlintApp({workspaces, textlintEditor}); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/domain/TextlintAppId.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | let id = 0; 4 | export default class TextlintAppId { 5 | id; 6 | 7 | constructor() { 8 | this.id = id++; 9 | } 10 | 11 | /** 12 | * @param {TextlintAppId} id 13 | * @returns {boolean} 14 | */ 15 | equals(id) { 16 | return this.id === id.id; 17 | } 18 | 19 | toString() { 20 | return String(this.id); 21 | } 22 | 23 | valueOf() { 24 | return this.id; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/domain/__tests__/TextlintApp-test.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const assert = require("assert"); 4 | import TextlintApp from "../TextlintApp"; 5 | import TextlintAppFactory from "../TextlintAppFactory"; 6 | describe("TextlintApp", () => { 7 | it("should return TextlintApp instance", () => { 8 | const app = TextlintAppFactory.create({ 9 | directory: __dirname 10 | }); 11 | assert(app instanceof TextlintApp); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/renderer/domain/textlint-editor/TextlintEditor.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import TextlintEditorContent from "./TextlintEditorContent"; 4 | export default class TextlintEditor { 5 | /** 6 | * @param {TextlintEditorContent} content 7 | */ 8 | constructor(content) { 9 | this.content = content; 10 | } 11 | 12 | /** 13 | * @param {string} text 14 | * @param {string} filePath 15 | */ 16 | openNewFile({text, filePath}) { 17 | this.content = new TextlintEditorContent({text, filePath}); 18 | } 19 | 20 | /** 21 | * @param {string} text 22 | */ 23 | updateText(text) { 24 | this.content = this.content.updateText(text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/domain/textlint-editor/TextlintEditorContent.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const path = require("path"); 4 | export default class TextlintEditorContent { 5 | /** 6 | * 7 | * @param {string} text 8 | * @param {string} filePath 9 | */ 10 | constructor({ text, filePath }) { 11 | this.text = text; 12 | this.filePath = filePath; 13 | } 14 | 15 | get canAccessToFile() { 16 | return true; 17 | } 18 | 19 | get fileExtension() { 20 | return path.extname(this.filePath); 21 | } 22 | 23 | /** 24 | * @param {string} text 25 | * @returns {TextlintEditorContent} 26 | */ 27 | updateText(text) { 28 | return new this.constructor(Object.assign({}, this, { text })); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/domain/textlint-editor/TextlintEditorContentNoFile.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import TextlintEditorContent from "./TextlintEditorContent"; 4 | const os = require("os"); 5 | const path = require("path"); 6 | export default class TextlintEditorContentNoFile extends TextlintEditorContent { 7 | constructor({ text }) { 8 | const tmpFile = path.join(os.tmpdir(), "tmp.md"); 9 | super({ text, filePath: tmpFile }); 10 | } 11 | 12 | get canAccessToFile() { 13 | return false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/Workspace.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const path = require("path"); 4 | import Textlintrc from "./textlintrc/Textlintrc"; 5 | export default class Workspace { 6 | /** 7 | * @type {Textlintrc} 8 | */ 9 | textlintrc; 10 | 11 | /** 12 | * @type {string} 13 | */ 14 | directory; 15 | 16 | /** 17 | * @param {Textlintrc} textlintrc 18 | * @param {string} directory 19 | */ 20 | constructor({textlintrc, directory}) { 21 | this.update({textlintrc, directory}); 22 | } 23 | 24 | get isSetupTextlint() { 25 | return this.textlintrc.filePath !== undefined && 26 | this.directory !== undefined && 27 | this.modulesDirectory !== undefined; 28 | } 29 | 30 | /** 31 | * @param {string} filePath 32 | * @param {string} content 33 | */ 34 | openNewTextlintrc({filePath, content}) { 35 | this.textlintrc = new Textlintrc({filePath, content}); 36 | } 37 | 38 | /** 39 | * @param {Textlintrc} textlintrc 40 | * @param {string} directory 41 | */ 42 | update({textlintrc, directory}) { 43 | this.textlintrc = textlintrc; 44 | this.directory = directory; 45 | this.modulesDirectory = path.join(directory, "node_modules"); 46 | } 47 | 48 | /** 49 | * update current content 50 | * @param {string} content 51 | */ 52 | updateCurrentContent(content) { 53 | this.textlintrc = this.textlintrc.updateContent(content); 54 | } 55 | 56 | /** 57 | * @param {Workspace} workspace 58 | * @returns {Boolean} 59 | */ 60 | equals(workspace) { 61 | return workspace.textlintrc.equals(this.textlintrc); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/WorkspaceFactory.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import Workspace from "./Workspace"; 4 | import EmptyTextlintrc from "./textlintrc/EmptyTextlintrc"; 5 | export default class WorkspaceFactory { 6 | /** 7 | * @param {string} directory 8 | * @param {Textlintrc} [textlintrc] 9 | * @returns {Workspace} 10 | */ 11 | static create({directory, textlintrc}) { 12 | return new Workspace({ 13 | textlintrc: textlintrc ? textlintrc : new EmptyTextlintrc(), 14 | directory 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/Workspaces.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import Workspace from "./Workspace"; 4 | export default class Workspaces { 5 | /** 6 | * @type {Workspace[]} 7 | * @private 8 | */ 9 | _workspaces = []; 10 | 11 | /** 12 | * @type {Workspace} 13 | * @private 14 | */ 15 | _current; 16 | 17 | 18 | /** 19 | * @param {Workspace} workspace 20 | */ 21 | constructor(workspace) { 22 | // default workspace use empty textlintrc 23 | this._current = workspace; 24 | } 25 | 26 | /** 27 | * @returns {Workspace} 28 | */ 29 | get current() { 30 | return this._current; 31 | } 32 | 33 | /** 34 | * @param {Workspace} workspace 35 | */ 36 | set current(workspace) { 37 | this._current = workspace; 38 | } 39 | 40 | /** 41 | * @param {Workspace} aWorkspace 42 | * @returns {boolean} 43 | */ 44 | hasAlreadyOpened(aWorkspace) { 45 | return this._workspaces.some(workspace => workspace.equals(aWorkspace)); 46 | } 47 | 48 | /** 49 | * @param {string} filePath 50 | * @returns {Workspace} 51 | */ 52 | findWorkspaceByFilePath(filePath) { 53 | return this._workspaces.find(workspace => filePath === workspace.textlintrc.filePath); 54 | } 55 | 56 | /** 57 | * @param {Workspace} workspace 58 | */ 59 | addWorkspace(workspace) { 60 | this._workspaces.push(workspace); 61 | } 62 | 63 | /** 64 | * @param {Workspace} workspace 65 | */ 66 | useWorkspace(workspace) { 67 | if (!this.hasAlreadyOpened(workspace)) { 68 | this.addWorkspace(workspace); 69 | } 70 | this.current = workspace; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/textlintrc/EmptyTextlintrc.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import Textlintrc from "./Textlintrc"; 4 | export default class EmptyTextlintrc extends Textlintrc { 5 | constructor() { 6 | super({ 7 | content: "", 8 | filePath: null 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/textlintrc/Textlintrc.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import Validator from "./TextlintrcValidator"; 4 | const stripJsonComments = require("strip-json-comments"); 5 | const toPackageList = require("textlintrc-to-pacakge-list"); 6 | export default class Textlintrc { 7 | /** 8 | * @type {String|null} 9 | * @private 10 | */ 11 | _textValue; 12 | /** 13 | * @type {Object|null} 14 | * @private 15 | */ 16 | _jsonValue; 17 | 18 | /** 19 | * @type {String|null} 20 | */ 21 | filePath; 22 | 23 | constructor({content, filePath}) { 24 | this.filePath = filePath; 25 | this.content = content; 26 | if (Validator.validate(content)) { 27 | this._jsonValue = JSON.parse(stripJsonComments(content)); 28 | } else { 29 | this._jsonValue = {}; 30 | } 31 | } 32 | 33 | /** 34 | * @returns {String|null} 35 | */ 36 | get textValue() { 37 | return this.content; 38 | } 39 | 40 | /** 41 | * @returns {Object|null} 42 | */ 43 | get jsonValue() { 44 | return this._jsonValue; 45 | } 46 | 47 | /** 48 | * Can access to filePath? 49 | * @returns {boolean} 50 | */ 51 | get canAccessToFile() { 52 | return this.filePath !== null; 53 | } 54 | 55 | /** 56 | * is valid as configuration 57 | * @returns {boolean} 58 | */ 59 | get isValid() { 60 | return Validator.validate(this._textValue); 61 | } 62 | 63 | get packageNames() { 64 | if (!this.isValid) { 65 | return []; 66 | } 67 | return toPackageList(this._jsonValue); 68 | } 69 | 70 | /** 71 | * update content 72 | * @param {string} content 73 | * @returns {Textlintrc} 74 | */ 75 | updateContent(content) { 76 | return new Textlintrc(Object.assign({}, this, {content})); 77 | } 78 | 79 | /** 80 | * @param {Textlintrc} textlintrc 81 | */ 82 | equals(textlintrc) { 83 | return textlintrc.filePath === this.filePath; 84 | } 85 | 86 | toJSON() { 87 | return this._jsonValue; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/renderer/domain/workspace/textlintrc/TextlintrcValidator.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const stripJsonComments = require("strip-json-comments"); 4 | export default class TextlintrcValidator { 5 | /** 6 | * is valid as textlintrc content 7 | * @param {string|*} content 8 | * @returns {boolean} 9 | */ 10 | static validate(content) { 11 | if (typeof content !== "string") { 12 | return false; 13 | } 14 | const noCommentValue = stripJsonComments(content); 15 | try { 16 | JSON.parse(noCommentValue); 17 | return true; 18 | } catch (e) { 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import "css/**/*.css"; 3 | @import "components/**/*.css"; 4 | -------------------------------------------------------------------------------- /src/renderer/index.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | // deps 4 | require("office-ui-fabric-react/dist/css/fabric.css"); 5 | import i18next from "i18next"; 6 | 7 | const remote = require("electron").remote; 8 | const LanguageDetector = remote.require("i18next-electron-language-detector"); 9 | 10 | import React from "react"; 11 | import ReactDOM from "react-dom"; 12 | // use-case 13 | import InitializeUseCase from "./use-case/InitializeUseCase"; 14 | // Component 15 | import App from "./components/App"; 16 | // Store 17 | import AppStore from "./store/AppStore"; 18 | 19 | const locator = require("textlint-app-locator"); 20 | const { Dispatcher, Context } = require("almin"); 21 | locator.context = new Context({ 22 | store: AppStore.create(), 23 | dispatcher: new Dispatcher(), 24 | options: { 25 | performanceProfile: process.env.NODE_ENV !== "production" 26 | } 27 | }); 28 | 29 | if (process.env.NODE_ENV !== "production") { 30 | const AlminLogger = require("almin-logger").AlminLogger; 31 | const logger = new AlminLogger(); 32 | logger.startLogging(locator.context); 33 | } 34 | 35 | i18next.use(LanguageDetector).init({ 36 | // disable 37 | keySeparator: "", 38 | nsSeparator: "", 39 | contextSeparator: "", 40 | pluralSeparator: "", 41 | resources: { 42 | ja: { 43 | translation: require("../../locales/ja/translation.json") 44 | } 45 | } 46 | }, (err, t) => { 47 | if (err) { 48 | console.error(err); 49 | throw new err; 50 | } 51 | locator.context.useCase(InitializeUseCase.create()).execute().then(() => { 52 | // entry point 53 | ReactDOM.render(<App/>, document.getElementById("js-app")); 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /src/renderer/infra/api/PackageManger.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const debug = require("debug")("textlint-app:PackageManger"); 4 | const remote = require("electron").remote; 5 | const ServerPackageManager = remote.require("textlint-server-package-manager"); 6 | export default class PackageManger { 7 | /** 8 | * @param {Workspace} workspace 9 | */ 10 | static install(workspace) { 11 | /** 12 | * @type {TextlintPackageManger} 13 | */ 14 | const manager = new ServerPackageManager(workspace.directory); 15 | debug("Installing from ", manager.textlintrcFilePath); 16 | // TODO: always flush and install 17 | // We should implement package.update 18 | return manager.install(workspace.textlintrc.textValue, { 19 | force: true 20 | }); 21 | } 22 | 23 | /** 24 | * @param {string} directory 25 | * @returns {Promise.<{content:string, filePath:string}>} 26 | */ 27 | static getTextlinrc(directory) { 28 | const manager = new ServerPackageManager(directory); 29 | return manager.getTextlintrc().then(content => { 30 | return { 31 | content, 32 | filePath: manager.textlintrcFilePath 33 | }; 34 | }); 35 | } 36 | 37 | /** 38 | * @param {string} directory 39 | * @param {string} textlintrcContent 40 | * @returns {Promise} 41 | */ 42 | static writeTextlintrc(directory, textlintrcContent) { 43 | const manager = new ServerPackageManager(directory); 44 | return manager.writeTextlintrc(textlintrcContent); 45 | } 46 | 47 | 48 | /** 49 | * Check integrity between .textlintrc and installed module 50 | * @param {string} directory 51 | * @returns {Promise.<Boolean>} 52 | */ 53 | static checkIntegrity(directory) { 54 | /** 55 | * @type {TextlintPackageManger} 56 | */ 57 | const manager = new ServerPackageManager(directory); 58 | return manager.checkIntegrity(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/renderer/infra/repository/TextlintAppRepository.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const EventEmitter = require("events"); 4 | const REPOSITORY_CHANGE = "REPOSITORY_CHANGE"; 5 | import MemoryDB from "./adpter/MemoryDB"; 6 | // Collection repository 7 | export class TextlintAppRepository extends EventEmitter { 8 | constructor(database = new MemoryDB()) { 9 | super(); 10 | /** 11 | * @type {MemoryDB} 12 | */ 13 | this._database = database; 14 | } 15 | 16 | /** 17 | * @param {*} id 18 | * @private 19 | */ 20 | _get(id) { 21 | // Domain.<id> 22 | return this._database.get(`${id}`); 23 | } 24 | 25 | /** 26 | * @param {TextlintAppId} id 27 | * @returns {TextlintApp|undefined} 28 | */ 29 | findById(id) { 30 | return this._get(id.id); 31 | } 32 | 33 | /** 34 | * @returns {TextlintApp|undefined} 35 | */ 36 | lastUsed() { 37 | const app = this._database.get("lastUsed"); 38 | if (!app) { 39 | return; 40 | } 41 | return this._get(app.id); 42 | } 43 | 44 | /** 45 | * @param {TextlintApp} textlintApp 46 | */ 47 | save(textlintApp) { 48 | this._database.set("lastUsed", textlintApp); 49 | this._database.set(`${textlintApp.id}`, textlintApp); 50 | this.emit(REPOSITORY_CHANGE, textlintApp); 51 | } 52 | 53 | /** 54 | * @param {TextlintApp} app 55 | */ 56 | remove(app) { 57 | this._database.delete(`${app.id}`); 58 | this.emit(REPOSITORY_CHANGE); 59 | } 60 | 61 | onChange(handler) { 62 | this.on(REPOSITORY_CHANGE, handler); 63 | } 64 | } 65 | // singleton 66 | export default new TextlintAppRepository(); 67 | -------------------------------------------------------------------------------- /src/renderer/infra/repository/adpter/MemoryDB.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const MapLike = require("map-like"); 3 | export default class MemoryDB extends MapLike { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/renderer/infra/textlint/TextlintAPI.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const ipcPromise = require("ipc-promise"); 4 | import Key from "../../../node/ipc/textlint-ipc-key"; 5 | export default class TextlintAPI { 6 | /** 7 | * @param {string} configFile path to config file 8 | * @param {string} rulesBaseDirectory path to directory of node_modules 9 | */ 10 | constructor({ 11 | configFile, 12 | rulesBaseDirectory 13 | }) { 14 | ipcPromise.send(Key.setup, { 15 | configFile, 16 | rulesBaseDirectory 17 | }); 18 | } 19 | 20 | /** 21 | * @param {string} text 22 | * @param {string} [ext] 23 | * @returns {Promise.<TextLintMessage[]>}} 24 | */ 25 | lintText(text, ext = ".md") { 26 | return ipcPromise.send(Key.lintText, {text, ext}); 27 | } 28 | 29 | /** 30 | * @param {string} text 31 | * @param {string} [ext] 32 | * @returns {Promise.<TextLintFixResult>}} 33 | */ 34 | fixText(text, ext = ".md") { 35 | return ipcPromise.send(Key.fixText, {text, ext}); 36 | } 37 | 38 | _flattenResultToMessages(results) { 39 | const lintMessages = []; 40 | results.forEach(result => { 41 | result.messages.forEach(message => { 42 | lintMessages.push(message); 43 | }); 44 | }); 45 | return lintMessages; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/store/AppStore.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import { StoreGroup } from "almin"; 4 | import textlintAppRepository from "../infra/repository/TextlintAppRepository"; 5 | import TextlintrcEditorStore from "./TextlintrcEditor/TextlintrcEditorStore"; 6 | import TextlintEditorStore from "./TextlintEditor/TextlintEditorStore"; 7 | export default class AppStore { 8 | static create() { 9 | return new StoreGroup({ 10 | "textlintrcEditor": new TextlintrcEditorStore({ 11 | textlintAppRepository 12 | }), 13 | "textlintEditor": new TextlintEditorStore({ 14 | textlintAppRepository 15 | }) 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/store/TextlintEditor/TextlintEditorStore.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import * as path from "path"; 4 | import { Store } from "almin"; 5 | export const defaultStateArgs = { 6 | textContent: "", 7 | contentFilePath: null, 8 | contentFileExtension: null, 9 | canAccessToFile: false 10 | }; 11 | export class TextlintEditorState { 12 | /** 13 | * @param {string} textContent 14 | * @param {string|null} contentFilePath 15 | * @param {string|null} contentFileExtension 16 | * @param {boolean} canAccessToFile 17 | */ 18 | constructor({ 19 | textContent, 20 | contentFilePath, 21 | contentFileExtension, 22 | canAccessToFile 23 | } = defaultStateArgs) { 24 | this.textContent = textContent; 25 | this.contentFilePath = contentFilePath; 26 | this.contentFileExtension = contentFileExtension; 27 | this.canAccessToFile = canAccessToFile; 28 | } 29 | 30 | get editingFileName() { 31 | if (!this.canAccessToFile) { 32 | return ""; 33 | } 34 | if (this.contentFilePath.length <= 100) { 35 | return this.contentFilePath; 36 | } 37 | return path.basename(this.contentFilePath); 38 | } 39 | 40 | /** 41 | * 42 | * @param {TextlintApp} textlintApp 43 | * @returns {TextlintEditorState} 44 | */ 45 | update({ textlintApp }) { 46 | /** 47 | * @type {TextlintEditor} 48 | */ 49 | const textlintEditor = textlintApp.textlintEditor; 50 | return new TextlintEditorState(Object.assign({}, this, { 51 | textContent: textlintEditor.content.text, 52 | contentFilePath: textlintEditor.content.filePath, 53 | contentFileExtension: textlintEditor.content.fileExtension, 54 | canAccessToFile: textlintEditor.content.canAccessToFile 55 | })); 56 | } 57 | } 58 | 59 | export default class TextlintEditorStore extends Store { 60 | /** 61 | * @param {TextlintAppRepository} textlintAppRepository 62 | */ 63 | constructor({ textlintAppRepository }) { 64 | super(); 65 | /** 66 | * @type {TextlintEditorState} 67 | */ 68 | this.state = new TextlintEditorState(); 69 | textlintAppRepository.onChange(this._onChange.bind(this)); 70 | } 71 | 72 | getState() { 73 | return this.state; 74 | } 75 | 76 | _onChange(textlintApp) { 77 | this.setState(this.state.update({ textlintApp })); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/renderer/store/TextlintEditor/__tests__/TextlintEditorStore-test.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import * as assert from "assert"; 4 | import { TextlintEditorState } from "../TextlintEditorStore"; 5 | describe("TextlintEditorState", () => { 6 | describe("#editingFileName", () => { 7 | context("current file path is >=100 length", () => { 8 | it("should return filename", () => { 9 | const state = new TextlintEditorState(Object.assign({}, new TextlintEditorState(), { 10 | canAccessToFile: true, 11 | contentFilePath: `${"/a".repeat(50) }/file.md` 12 | })); 13 | assert.ok(state.editingFileName === "file.md"); 14 | }); 15 | }); 16 | context("current file path is <100 length", () => { 17 | it("should return file path", () => { 18 | const contentFilePath = "/path/to/file.md"; 19 | const state = new TextlintEditorState(Object.assign({}, new TextlintEditorState(), { 20 | canAccessToFile: true, 21 | contentFilePath: contentFilePath 22 | })); 23 | assert.ok(state.editingFileName === contentFilePath); 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/renderer/store/TextlintrcEditor/TextlintrcEditorStore.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import i18next from"i18next"; 4 | import { Store } from "almin"; 5 | // use-case 6 | import { DismissInstallErrorType } from "../../use-case/textlintrc/DismissInstallErrorUseCase"; 7 | import InstallTextlintPackageUseCase from "../../use-case/workspace/InstallTextlintPackageUseCase"; 8 | export class TextlintrcEditorState { 9 | 10 | /** 11 | * @param {string} [workingDirectory] 12 | * @param {string} [modulesDirectory] 13 | * @param {string} [isValid] 14 | * @param {string} [textValue] 15 | * @param {string} [jsonValue] 16 | * @param {string} [packageNames] 17 | * @param {string} [canAccessToFile] 18 | * @param {string} [filePath] 19 | * @param {boolean} [isLoading] 20 | * @param {Error} [installFailureError] 21 | */ 22 | constructor({ 23 | workingDirectory, 24 | modulesDirectory, 25 | isValid, 26 | textValue, 27 | jsonValue, 28 | packageNames, 29 | canAccessToFile, 30 | filePath, 31 | isLoading, 32 | installFailureError 33 | } = {}) { 34 | this.workingDirectory = workingDirectory; 35 | this.modulesDirectory = modulesDirectory; 36 | // textlintrc 37 | this.isValid = isValid; 38 | this.textValue = textValue; 39 | this.jsonValue = jsonValue; 40 | this.packageNames = packageNames; 41 | this.canAccessToFile = canAccessToFile; 42 | this.filePath = filePath; 43 | // state 44 | this.isLoading = isLoading; 45 | this.installFailureError = installFailureError; 46 | } 47 | 48 | /** 49 | * 50 | * @param {TextlintApp} textlintApp 51 | * @returns {TextlintrcEditorState} 52 | */ 53 | update({ textlintApp }) { 54 | const currentWorkspace = textlintApp.workspaces.current; 55 | const textlintrc = currentWorkspace.textlintrc; 56 | return new TextlintrcEditorState(Object.assign({}, this, { 57 | workingDirectory: currentWorkspace.directory, 58 | modulesDirectory: currentWorkspace.modulesDirectory, 59 | isValid: textlintrc.isValid, 60 | textValue: textlintrc.textValue, 61 | jsonValue: textlintrc.jsonValue, 62 | packageNames: textlintrc.packageNames, 63 | canAccessToFile: textlintrc.canAccessToFile, 64 | filePath: textlintrc.filePath 65 | })); 66 | } 67 | 68 | reduce(payload) { 69 | switch (payload.type) { 70 | case InstallTextlintPackageUseCase.Events.beginInstall: 71 | return new TextlintrcEditorState(Object.assign({}, this, { isLoading: true })); 72 | case InstallTextlintPackageUseCase.Events.successInstall: 73 | return new TextlintrcEditorState(Object.assign({}, this, { isLoading: false })); 74 | case InstallTextlintPackageUseCase.Events.failureInstall: 75 | return new TextlintrcEditorState(Object.assign({}, this, { 76 | isLoading: false, 77 | installFailureError: new Error(i18next.t("Failed to install. Please check .textlintrc and Press Install again.")) 78 | })); 79 | case DismissInstallErrorType: 80 | return new TextlintrcEditorState(Object.assign({}, this, { 81 | installFailureError: undefined 82 | })); 83 | default: 84 | return this; 85 | } 86 | } 87 | } 88 | export default class TextlintrcEditorStore extends Store { 89 | /** 90 | * @param {TextlintAppRepository} textlintAppRepository 91 | */ 92 | constructor({ textlintAppRepository }) { 93 | super(); 94 | this.state = new TextlintrcEditorState({ 95 | isLoading: false 96 | }); 97 | this.textlintAppRepository = textlintAppRepository; 98 | } 99 | 100 | receivePayload(payload) { 101 | const app = this.textlintAppRepository.lastUsed(); 102 | if (!app) { 103 | return; 104 | } 105 | const baseState = this.state.update({ textlintApp: app }); 106 | this.setState(baseState.reduce(payload)); 107 | } 108 | 109 | getState() { 110 | return this.state; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/renderer/use-case/InitializeUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const remote = require("electron").remote; 4 | const app = remote.app; 5 | const path = require("path"); 6 | const debug = require("debug")("textlint-app:InitializeUseCase"); 7 | import {UseCase} from "almin"; 8 | // domain 9 | import TextlintAppFactory from "../domain/TextlintAppFactory"; 10 | import Textlintrc from "../domain/workspace/textlintrc/Textlintrc"; 11 | // repository 12 | import textlintAppRepository from "../infra/repository/TextlintAppRepository"; 13 | // api 14 | import PackageManger from "../infra/api/PackageManger"; 15 | export default class InitializeUseCase extends UseCase { 16 | static create() { 17 | return new InitializeUseCase({ 18 | textlintAppRepository 19 | }); 20 | } 21 | 22 | /** 23 | * @param {TextlintAppRepository} textlintAppRepository 24 | */ 25 | constructor({textlintAppRepository}) { 26 | super(); 27 | this.textlintAppRepository = textlintAppRepository; 28 | } 29 | 30 | execute() { 31 | const defaultWorkspaceDirectory = path.join(app.getPath("userData"), "textlint/default"); 32 | return PackageManger.getTextlinrc(defaultWorkspaceDirectory).then(({content, filePath}) => { 33 | debug("load textlintrc", filePath); 34 | const newApp = TextlintAppFactory.create({ 35 | directory: defaultWorkspaceDirectory, 36 | textlintrc: new Textlintrc({content, filePath}) 37 | }); 38 | this.textlintAppRepository.save(newApp); 39 | }).catch(error => { 40 | debug("Not found textlintrc", error); 41 | const newApp = TextlintAppFactory.create({ 42 | directory: defaultWorkspaceDirectory 43 | }); 44 | this.textlintAppRepository.save(newApp); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlint-editor/FixTextUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const debug = require("debug")("textlint-app:FixTextUseCase"); 4 | import {UseCase} from "almin"; 5 | import TextLintAPI from "../../infra/textlint/TextlintAPI"; 6 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 7 | 8 | export default class FixTextUseCase extends UseCase { 9 | static create() { 10 | return new this({textlintAppRepository}); 11 | } 12 | 13 | constructor({textlintAppRepository}) { 14 | super(); 15 | this.textlintAppRepository = textlintAppRepository; 16 | } 17 | 18 | execute() { 19 | const app = this.textlintAppRepository.lastUsed(); 20 | const currentWorkspace = app.workspaces.current; 21 | if (!currentWorkspace.isSetupTextlint) { 22 | debug("Current workspace is not setup yet."); 23 | return; 24 | } 25 | const textlintAPI = new TextLintAPI({ 26 | configFile: currentWorkspace.textlintrc.filePath, 27 | rulesBaseDirectory: currentWorkspace.modulesDirectory 28 | }); 29 | return textlintAPI.fixText(app.textlintEditor.content.text, app.textlintEditor.content.fileExtension).then(result => { 30 | app.textlintEditor.updateText(result.output); 31 | this.textlintAppRepository.save(app); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlint-editor/OpenNewFileUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const fs = require("fs"); 4 | import {UseCase} from "almin"; 5 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 6 | 7 | export default class OpenNewFileUseCase extends UseCase { 8 | static create() { 9 | return new this({textlintAppRepository}); 10 | } 11 | 12 | constructor({textlintAppRepository}) { 13 | super(); 14 | this.textlintAppRepository = textlintAppRepository; 15 | } 16 | 17 | execute(filePath) { 18 | return Promise.resolve().then(() => { 19 | const text = fs.readFileSync(filePath, "utf-8"); 20 | /** @type {TextlintApp} */ 21 | const app = this.textlintAppRepository.lastUsed(); 22 | app.textlintEditor.openNewFile({ 23 | text, 24 | filePath 25 | }); 26 | this.textlintAppRepository.save(app); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlint-editor/SaveAsNewFileUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const fs = require("fs"); 4 | import { UseCase } from "almin"; 5 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 6 | 7 | export default class SaveAsNewFileUseCase extends UseCase { 8 | static create() { 9 | return new this({ textlintAppRepository }); 10 | } 11 | 12 | constructor({ textlintAppRepository }) { 13 | super(); 14 | this.textlintAppRepository = textlintAppRepository; 15 | } 16 | 17 | execute(filePath) { 18 | return Promise.resolve().then(() => { 19 | /** @type {TextlintApp} */ 20 | const app = this.textlintAppRepository.lastUsed(); 21 | const text = app.textlintEditor.content.text; 22 | fs.writeFileSync(filePath, text, "utf-8"); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlint-editor/UpdateTextUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const fs = require("fs"); 4 | import {UseCase} from "almin"; 5 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 6 | 7 | export default class UpdateTextUseCase extends UseCase { 8 | static create() { 9 | return new this({textlintAppRepository}); 10 | } 11 | 12 | constructor({textlintAppRepository}) { 13 | super(); 14 | this.textlintAppRepository = textlintAppRepository; 15 | } 16 | 17 | /** 18 | * @param {string} text 19 | */ 20 | execute(text) { 21 | /** @type {TextlintApp} */ 22 | const app = this.textlintAppRepository.lastUsed(); 23 | app.textlintEditor.updateText(text); 24 | this.textlintAppRepository.save(app); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlintrc/DismissInstallErrorUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | export const DismissInstallErrorType = Symbol("DismissInstallErrorUseCase"); 4 | export const DismissInstallErrorUseCase = ({ dispatcher }) => { 5 | return () => { 6 | dispatcher.dispatch({ 7 | type: DismissInstallErrorType 8 | }); 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlintrc/UpdateTextlintrcUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import {UseCase} from "almin"; 4 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 5 | export default class UpdateTextlintrcUseCase extends UseCase { 6 | static create() { 7 | return new this({textlintAppRepository}); 8 | } 9 | 10 | constructor({textlintAppRepository}) { 11 | super(); 12 | this.textlintAppRepository = textlintAppRepository; 13 | } 14 | 15 | /** 16 | * @param {string} content 17 | */ 18 | execute(content) { 19 | const app = this.textlintAppRepository.lastUsed(); 20 | app.workspaces.current.updateCurrentContent(content); 21 | this.textlintAppRepository.save(app); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/use-case/textlintrc/WriteToTextlintrcUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import { UseCase } from "almin"; 4 | const debug = require("debug")("textlint-app:WriteToTextlintrcUseCase"); 5 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 6 | import PackageManger from "../../infra/api/PackageManger"; 7 | export default class WriteToTextlintrcUseCase extends UseCase { 8 | static create() { 9 | return new this({ textlintAppRepository }); 10 | } 11 | 12 | constructor({ textlintAppRepository }) { 13 | super(); 14 | this.textlintAppRepository = textlintAppRepository; 15 | } 16 | 17 | execute() { 18 | const app = this.textlintAppRepository.lastUsed(); 19 | const textlintrc = app.workspaces.current.textlintrc; 20 | if (!textlintrc.canAccessToFile) { 21 | debug("can not access .textlint: %s", textlintrc.filePath); 22 | return; 23 | } 24 | const directory = app.workspaces.current.directory; 25 | const textContent = textlintrc.textValue; 26 | return PackageManger.writeTextlintrc(directory, textContent); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/renderer/use-case/workspace/InstallTextlintPackageUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import {UseCase} from "almin"; 4 | // repository 5 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 6 | import ReloadCurrentWorkspaceUseCase from "./ReloadCurrentWorkspaceUseCase"; 7 | // api 8 | import PackageManger from "../../infra/api/PackageManger"; 9 | export default class InstallTextlintPackageUseCase extends UseCase { 10 | static create() { 11 | return new InstallTextlintPackageUseCase({ 12 | textlintAppRepository 13 | }); 14 | } 15 | 16 | static Events = { 17 | beginInstall: Symbol("begin"), 18 | successInstall: Symbol("success"), 19 | failureInstall: Symbol("failure") 20 | }; 21 | 22 | /** 23 | * @param {TextlintAppRepository} textlintAppRepository 24 | */ 25 | constructor({textlintAppRepository}) { 26 | super(); 27 | this.textlintAppRepository = textlintAppRepository; 28 | } 29 | 30 | execute() { 31 | const app = this.textlintAppRepository.lastUsed(); 32 | this.dispatch({type: InstallTextlintPackageUseCase.Events.beginInstall}); 33 | return PackageManger.install(app.workspaces.current).then(() => { 34 | this.dispatch({type: InstallTextlintPackageUseCase.Events.successInstall}); 35 | }).then(() => { 36 | return this.context.useCase(ReloadCurrentWorkspaceUseCase.create()).execute(); 37 | }).catch(error => { 38 | this.dispatch({type: InstallTextlintPackageUseCase.Events.failureInstall}); 39 | return Promise.reject(error); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/renderer/use-case/workspace/ReloadCurrentWorkspaceUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | import {UseCase} from "almin"; 4 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 5 | import PackageManger from "../../infra/api/PackageManger"; 6 | import Textlintrc from "../../domain/workspace/textlintrc/Textlintrc"; 7 | 8 | export default class ReloadCurrentWorkspaceUseCase extends UseCase { 9 | static create() { 10 | return new this({textlintAppRepository}); 11 | } 12 | 13 | constructor({textlintAppRepository}) { 14 | super(); 15 | this.textlintAppRepository = textlintAppRepository; 16 | } 17 | 18 | execute() { 19 | const app = this.textlintAppRepository.lastUsed(); 20 | const currentWorkspace = app.workspaces.current; 21 | return PackageManger.getTextlinrc(currentWorkspace.directory).then(({content, filePath}) => { 22 | const textlintrc = new Textlintrc({content, filePath}); 23 | currentWorkspace.update({textlintrc, directory: currentWorkspace.directory}); 24 | this.textlintAppRepository.save(app); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/use-case/workspace/UpdateWorkspaceDirectoryUseCase.js: -------------------------------------------------------------------------------- 1 | // MIT © 2017 azu 2 | "use strict"; 3 | const debug = require("debug")("textlint-app:UpdateWorkspaceDirectoryUseCase"); 4 | import {UseCase} from "almin"; 5 | // domain 6 | import WorkspaceFactory from "../../domain/workspace/WorkspaceFactory"; 7 | import Textlintrc from "../../domain/workspace/textlintrc/Textlintrc"; 8 | import PackageManger from "../../infra/api/PackageManger"; 9 | import textlintAppRepository from "../../infra/repository/TextlintAppRepository"; 10 | // repository 11 | export default class UpdateWorkspaceDirectoryUseCase extends UseCase { 12 | static create() { 13 | return new this({textlintAppRepository}); 14 | } 15 | 16 | constructor({textlintAppRepository}) { 17 | super(); 18 | this.textlintAppRepository = textlintAppRepository; 19 | } 20 | 21 | /** 22 | * @param {string} workspaceDirectory 23 | * @returns {Promise} 24 | */ 25 | execute(workspaceDirectory) { 26 | const app = this.textlintAppRepository.lastUsed(); 27 | return PackageManger.getTextlinrc(workspaceDirectory).then(({content, filePath}) => { 28 | debug("load textlintrc", filePath); 29 | const newWorkspace = WorkspaceFactory.create({ 30 | directory: workspaceDirectory, 31 | textlintrc: new Textlintrc({content, filePath}) 32 | }); 33 | app.workspaces.useWorkspace(newWorkspace); 34 | this.textlintAppRepository.save(app); 35 | }).catch(error => { 36 | debug("Not found textlintrc", error); 37 | const newWorkspace = WorkspaceFactory.create({ 38 | directory: workspaceDirectory 39 | }); 40 | app.workspaces.useWorkspace(newWorkspace); 41 | this.textlintAppRepository.save(app); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --compilers js:babel-register -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const pkg = require("./package.json"); 5 | const dependencyNames = Object.keys(pkg.dependencies); 6 | // Node process 7 | const node = { 8 | entry: { 9 | "node": "./src/node/index.js" 10 | }, 11 | devtool: process.env.WEBPACK_DEVTOOL || "source-map", 12 | target: "electron", 13 | output: { 14 | path: path.join(__dirname, "app", "build"), 15 | publicPath: "/build/", 16 | filename: "[name].js" 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | exclude: /node_modules/, 23 | loader: "babel-loader", 24 | options: { 25 | cacheDirectory: true 26 | } 27 | } 28 | ] 29 | }, 30 | plugins: [ 31 | new webpack.DefinePlugin({ 32 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) 33 | }), 34 | // textlint in node.js 35 | new webpack.ExternalsPlugin("commonjs", dependencyNames) 36 | ], 37 | node: { 38 | __dirname: false, 39 | __filename: false 40 | } 41 | }; 42 | // renderer process 43 | const renderer = { 44 | entry: { 45 | "renderer": "./src/renderer/index.js" 46 | }, 47 | devtool: process.env.WEBPACK_DEVTOOL || "source-map", 48 | target: "electron", 49 | output: { 50 | path: path.join(__dirname, "app", "build"), 51 | publicPath: "/build/", 52 | filename: "[name].js" 53 | }, 54 | module: { 55 | rules: [ 56 | { 57 | test: /\.js$/, 58 | exclude: /node_modules/, 59 | loader: "babel-loader", 60 | options: { 61 | cacheDirectory: true 62 | } 63 | }, 64 | { 65 | test: /\.css$/, 66 | loaders: ["style-loader", "css-loader"] 67 | }, 68 | { 69 | test: /\.(jpg|png)$/, 70 | loader: "url-loader", 71 | options: { 72 | limit: 45000 73 | } 74 | } 75 | ] 76 | }, 77 | 78 | plugins: [ 79 | new webpack.DefinePlugin({ 80 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) 81 | }) 82 | ], 83 | node: { 84 | __dirname: false, 85 | __filename: false, 86 | fs: "empty", 87 | module: "empty" 88 | } 89 | }; 90 | 91 | module.exports = [node, renderer]; 92 | --------------------------------------------------------------------------------