diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 4893b342..1abd15a9 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -26,6 +26,8 @@ import {languageMaps} from '../lib/CMLanguageList' import snippetManager from '../lib/SnippetManager' import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator' import markdownlint from 'markdownlint' +import Jsonlint from 'jsonlint-mod' +import { DEFAULT_CONFIG } from '../main/lib/ConfigManager' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -38,38 +40,6 @@ function translateHotkey (hotkey) { return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') } -const validatorOfMarkdown = (text, updateLinting) => { - const lintOptions = { - 'strings': { - 'content': text - } - } - - return markdownlint(lintOptions, (err, result) => { - if (!err) { - const foundIssues = [] - result.content.map(item => { - let ruleNames = '' - item.ruleNames.map((ruleName, index) => { - ruleNames += ruleName - if (index === item.ruleNames.length - 1) { - ruleNames += ': ' - } else { - ruleNames += '/' - } - }) - foundIssues.push({ - from: CodeMirror.Pos(item.lineNumber, 0), - to: CodeMirror.Pos(item.lineNumber, 1), - message: ruleNames + item.ruleDescription, - severity: 'warning' - }) - }) - updateLinting(foundIssues) - } - }) -} - export default class CodeEditor extends React.Component { constructor (props) { super(props) @@ -116,6 +86,8 @@ export default class CodeEditor extends React.Component { this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchState = null this.scrollToLineHandeler = this.scrollToLine.bind(this) + this.getCodeEditorLintConfig = this.getCodeEditorLintConfig.bind(this) + this.validatorOfMarkdown = this.validatorOfMarkdown.bind(this) this.formatTable = () => this.handleFormatTable() @@ -283,13 +255,12 @@ export default class CodeEditor extends React.Component { } componentDidMount () { - const { rulers, enableRulers } = this.props + const { rulers, enableRulers, enableMarkdownLint } = this.props eventEmitter.on('line:jump', this.scrollToLineHandeler) snippetManager.init() this.updateDefaultKeyMap() - const checkMarkdownNoteIsOpening = this.props.mode === 'Boost Flavored Markdown' this.value = this.props.value this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), @@ -306,10 +277,7 @@ export default class CodeEditor extends React.Component { inputStyle: 'textarea', dragDrop: false, foldGutter: true, - lint: checkMarkdownNoteIsOpening ? { - 'getAnnotations': validatorOfMarkdown, - 'async': true - } : false, + lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], autoCloseBrackets: { pairs: this.props.matchingPairs, @@ -320,6 +288,8 @@ export default class CodeEditor extends React.Component { extraKeys: this.defaultKeyMap }) + document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none' + if (!this.props.mode && this.props.value && this.props.autoDetect) { this.autoDetectLanguage(this.props.value) } else { @@ -546,7 +516,9 @@ export default class CodeEditor extends React.Component { let needRefresh = false const { rulers, - enableRulers + enableRulers, + enableMarkdownLint, + customMarkdownLintConfig } = this.props if (prevProps.mode !== this.props.mode) { this.setMode(this.props.mode) @@ -564,6 +536,16 @@ export default class CodeEditor extends React.Component { if (prevProps.keyMap !== this.props.keyMap) { needRefresh = true } + if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) { + if (!enableMarkdownLint) { + this.editor.setOption('lint', {default: false}) + document.querySelector('.CodeMirror-lint-markers').style.display = 'none' + } else { + this.editor.setOption('lint', this.getCodeEditorLintConfig()) + document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block' + } + needRefresh = true + } if ( prevProps.enableRulers !== enableRulers || @@ -644,6 +626,56 @@ export default class CodeEditor extends React.Component { } } + getCodeEditorLintConfig () { + const { mode } = this.props + const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown' + + return checkMarkdownNoteIsOpen ? { + 'getAnnotations': this.validatorOfMarkdown, + 'async': true + } : false + } + + validatorOfMarkdown (text, updateLinting) { + const { customMarkdownLintConfig } = this.props + let lintConfigJson + try { + Jsonlint.parse(customMarkdownLintConfig) + lintConfigJson = JSON.parse(customMarkdownLintConfig) + } catch (err) { + eventEmitter.emit('APP_SETTING_ERROR') + return + } + const lintOptions = { + 'strings': { + 'content': text + }, + 'config': lintConfigJson + } + + return markdownlint(lintOptions, (err, result) => { + if (!err) { + const foundIssues = [] + const splitText = text.split('\n') + result.content.map(item => { + let ruleNames = '' + item.ruleNames.map((ruleName, index) => { + ruleNames += ruleName + ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/' + }) + const lineNumber = item.lineNumber - 1 + foundIssues.push({ + from: CodeMirror.Pos(lineNumber, 0), + to: CodeMirror.Pos(lineNumber, splitText[lineNumber].length), + message: ruleNames + item.ruleDescription, + severity: 'warning' + }) + }) + updateLinting(foundIssues) + } + }) + } + setMode (mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text')) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') @@ -1105,13 +1137,11 @@ export default class CodeEditor extends React.Component { } ref='root' tabIndex='-1' - style={ - { + style={{ fontFamily, fontSize: fontSize, width: width - } - } + }} onDrop={ e => this.handleDropImage(e) } @@ -1149,7 +1179,9 @@ CodeEditor.propTypes = { onChange: PropTypes.func, readOnly: PropTypes.bool, autoDetect: PropTypes.bool, - spellCheck: PropTypes.bool + spellCheck: PropTypes.bool, + enableMarkdownLint: PropTypes.bool, + customMarkdownLintConfig: PropTypes.string } CodeEditor.defaultProps = { @@ -1161,5 +1193,7 @@ CodeEditor.defaultProps = { indentSize: 4, indentType: 'space', autoDetect: false, - spellCheck: false + spellCheck: false, + enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint, + customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 593f7d99..526ecb39 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -319,6 +319,8 @@ class MarkdownEditor extends React.Component { enableSmartPaste={config.editor.enableSmartPaste} hotkey={config.hotkey} switchPreview={config.editor.switchPreview} + enableMarkdownLint={config.editor.enableMarkdownLint} + customMarkdownLintConfig={config.editor.customMarkdownLintConfig} />
this.handleMouseDown(e)} >
diff --git a/browser/lib/Languages.js b/browser/lib/Languages.js index 8c3747a9..435747a9 100644 --- a/browser/lib/Languages.js +++ b/browser/lib/Languages.js @@ -62,10 +62,12 @@ const languages = [ { name: 'Spanish', locale: 'es-ES' - }, { + }, + { name: 'Turkish', locale: 'tr' - }, { + }, + { name: 'Thai', locale: 'th' } @@ -82,4 +84,3 @@ module.exports = { return languages } } - diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index f20b3d88..bea019fa 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -11,6 +11,10 @@ const consts = require('browser/lib/consts') let isInitialized = false +const DEFAULT_MARKDOWN_LINT_CONFIG = `{ + "default": true +}` + export const DEFAULT_CONFIG = { zoom: 1, isSideNavFolded: false, @@ -59,7 +63,9 @@ export const DEFAULT_CONFIG = { enableFrontMatterTitle: true, frontMatterTitleField: 'title', spellcheck: false, - enableSmartPaste: false + enableSmartPaste: false, + enableMarkdownLint: false, + customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG }, preview: { fontSize: '14', diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 994ca3d3..5de4b12b 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -30,7 +30,9 @@ class UiTab extends React.Component { componentDidMount () { CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css') + CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript') this.customCSSCM.getCodeMirror().setSize('400px', '400px') + this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px') this.handleSettingDone = () => { this.setState({UiAlert: { type: 'success', @@ -101,7 +103,9 @@ class UiTab extends React.Component { matchingTriples: this.refs.matchingTriples.value, explodingPairs: this.refs.explodingPairs.value, spellcheck: this.refs.spellcheck.checked, - enableSmartPaste: this.refs.enableSmartPaste.checked + enableSmartPaste: this.refs.enableSmartPaste.checked, + enableMarkdownLint: this.refs.enableMarkdownLint.checked, + customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue() }, preview: { fontSize: this.refs.previewFontSize.value, @@ -637,6 +641,34 @@ class UiTab extends React.Component { />
+
+
+ {i18n.__('Custom MarkdownLint Rules')} +
+
+ this.handleUIChange(e)} + checked={this.state.config.editor.enableMarkdownLint} + ref='enableMarkdownLint' + type='checkbox' + />  + {i18n.__('Enable MarkdownLint')} +
+ this.handleUIChange(e)} + ref={e => (this.customMarkdownLintConfigCM = e)} + value={config.editor.customMarkdownLintConfig} + options={{ + lineNumbers: true, + mode: 'application/json', + theme: codemirrorTheme, + lint: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'] + }} /> +
+
+
{i18n.__('Preview')}
diff --git a/lib/main.html b/lib/main.html index 87a88e2d..0d9feade 100644 --- a/lib/main.html +++ b/lib/main.html @@ -72,6 +72,10 @@ border-left-color: rgba(142, 142, 142, 0.5); mix-blend-mode: difference; } + + .CodeMirror-lint-tooltip { + z-index: 1003; + } @@ -126,7 +130,9 @@ + + diff --git a/locales/ja.json b/locales/ja.json index 087bce36..e1dc553b 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -79,6 +79,7 @@ "Matching character pairs": "自動補完する括弧ペアの列記", "Matching character triples": "自動補完する3文字括弧の列記", "Exploding character pairs": "改行時に空行を挿入する括弧ペアの列記", + "Custom MarkdownLint Rules": "カスタムMarkdownLintルール", "Preview": "プレビュー", "Preview Font Size": "プレビュー時フォントサイズ", "Preview Font Family": "プレビュー時フォント", diff --git a/package.json b/package.json index e64f707b..5bcf9d1c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.16", + "version": "0.11.17", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", @@ -74,6 +74,7 @@ "immutable": "^3.8.1", "invert-color": "^2.0.0", "js-yaml": "^3.12.0", + "jsonlint-mod": "^1.7.4", "katex": "^0.9.0", "lodash": "^4.11.1", "lodash-move": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index faa3b3bc..6b6f95a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,6 +101,11 @@ version "8.10.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3" +"JSV@>= 4.0.x": + version "4.0.2" + resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57" + integrity sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c= + abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -1620,9 +1625,10 @@ chalk@0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^0.4.0: +chalk@^0.4.0, chalk@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" + integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8= dependencies: ansi-styles "~1.0.0" has-color "~0.1.0" @@ -5587,6 +5593,14 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" +jsonlint-mod@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/jsonlint-mod/-/jsonlint-mod-1.7.4.tgz#310390e1a6a85cef99f45f200e662ef23b48f7a6" + integrity sha512-FYOkwHqiuBbdVCHgXYlmtL+iUOz9AxCgjotzXl+edI0Hc1km1qK6TrBEAyPpO+5R0/IX3XENRp66mfob4jwxow== + dependencies: + JSV ">= 4.0.x" + nomnom ">= 1.5.x" + jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" @@ -6495,6 +6509,14 @@ nodeify@^1.0.1: is-promise "~1.0.0" promise "~1.3.0" +"nomnom@>= 1.5.x": + version "1.8.1" + resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" + integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc= + dependencies: + chalk "~0.4.0" + underscore "~1.6.0" + nopt@^3.0.1: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"