diff --git a/package.json b/package.json index 45857e99..1c470b2c 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "markdown-it-sub": "~1.0.0", "markdown-it-sup": "~1.0.0", "markdown-pdf": "~9.0.0", + "markdownlint": "^0.16.0", "mathjax": "~2.7.5", "mattermost-redux": "~5.13.0", "mermaid": "~8.2.3", diff --git a/public/images/lint/mark-error.png b/public/images/lint/mark-error.png new file mode 100644 index 00000000..045901da Binary files /dev/null and b/public/images/lint/mark-error.png differ diff --git a/public/images/lint/mark-multiple.png b/public/images/lint/mark-multiple.png new file mode 100644 index 00000000..8af299b6 Binary files /dev/null and b/public/images/lint/mark-multiple.png differ diff --git a/public/images/lint/mark-warning.png b/public/images/lint/mark-warning.png new file mode 100644 index 00000000..d7f28bf6 Binary files /dev/null and b/public/images/lint/mark-warning.png differ diff --git a/public/images/lint/message-error.png b/public/images/lint/message-error.png new file mode 100644 index 00000000..8c3ca42f Binary files /dev/null and b/public/images/lint/message-error.png differ diff --git a/public/images/lint/message-warning.png b/public/images/lint/message-warning.png new file mode 100644 index 00000000..07472d04 Binary files /dev/null and b/public/images/lint/message-warning.png differ diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js index 291dd0a4..37257871 100644 --- a/public/js/lib/editor/index.js +++ b/public/js/lib/editor/index.js @@ -3,6 +3,7 @@ import * as utils from './utils' import config from './config' import statusBarTemplate from './statusbar.html' import toolBarTemplate from './toolbar.html' +import './markdown-lint' /* config section */ const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault @@ -552,6 +553,7 @@ export default class Editor { this.editor = CodeMirror.fromTextArea(textit, { mode: defaultEditorMode, backdrop: defaultEditorMode, + lint: true, keyMap: 'sublime', viewportMargin: viewportMargin, styleActiveLine: true, @@ -573,7 +575,8 @@ export default class Editor { gutters: [ 'CodeMirror-linenumbers', 'authorship-gutters', - 'CodeMirror-foldgutter' + 'CodeMirror-foldgutter', + 'CodeMirror-lint-markers' ], extraKeys: this.defaultExtraKeys, flattenSpans: true, diff --git a/public/js/lib/editor/markdown-lint/index.js b/public/js/lib/editor/markdown-lint/index.js new file mode 100644 index 00000000..ea4bd778 --- /dev/null +++ b/public/js/lib/editor/markdown-lint/index.js @@ -0,0 +1,42 @@ +import markdownlint from 'markdownlint' + +// load lint plugin explicitly +import 'script-loader!@hackmd/codemirror/addon/lint/lint' +import './lint.css' + +(function(mod) { + mod(CodeMirror); +})(function(CodeMirror) { + function validator(text, options) { + return lint(text).map(error => { + const lineNumber = error.lineNumber - 1 + + let start, end + if (error.errorRange) { + start = error.errorRange[0] - 1 + end = error.errorRange[1] - 1 + } else { + start = 0 + end = -1 + } + + return { + message: error.ruleDescription, + severity: 'error', + from: CodeMirror.Pos(lineNumber, start), + to: CodeMirror.Pos(lineNumber, end) + } + }) + } + + CodeMirror.registerHelper('lint', 'markdown', validator); +}); + +function lint (content) { + const { content: errors } = markdownlint.sync({ + strings: { + content + } + }) + return errors +} diff --git a/public/js/lib/editor/markdown-lint/lint.css b/public/js/lib/editor/markdown-lint/lint.css new file mode 100644 index 00000000..d474419a --- /dev/null +++ b/public/js/lib/editor/markdown-lint/lint.css @@ -0,0 +1,72 @@ +/* The lint marker gutter */ +.CodeMirror-lint-markers { + width: 16px; +} + +.CodeMirror-lint-tooltip { + background-color: #ffd; + border: 1px solid black; + border-radius: 4px 4px 4px 4px; + color: black; + font-family: monospace; + font-size: 10pt; + overflow: hidden; + padding: 2px 5px; + position: fixed; + white-space: pre; + white-space: pre-wrap; + z-index: 100; + max-width: 600px; + opacity: 0; + transition: opacity .4s; + -moz-transition: opacity .4s; + -webkit-transition: opacity .4s; + -o-transition: opacity .4s; + -ms-transition: opacity .4s; +} + +.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { + background-position: left bottom; + background-repeat: repeat-x; +} + +.CodeMirror-lint-mark-error { + background-image: url(/images/lint/mark-error.png); + ; +} + +.CodeMirror-lint-mark-warning { + background-image: url(/images/lint/mark-warning.png); +} + +.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; + position: relative; +} + +.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { + padding-left: 18px; + background-position: top left; + background-repeat: no-repeat; +} + +.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { + background-image: url(/images/lint/message-error.png); +} + +.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { + background-image: url(/images/lint/message-warning.png); +} + +.CodeMirror-lint-marker-multiple { + background-image: url(/images/lint/mark-multiple.png); + background-repeat: no-repeat; + background-position: right bottom; + width: 100%; height: 100%; +} diff --git a/public/js/url.js b/public/js/url.js new file mode 100644 index 00000000..22ed110b --- /dev/null +++ b/public/js/url.js @@ -0,0 +1 @@ +exports.URL = window.URL diff --git a/webpack.common.js b/webpack.common.js index a7fe2b86..cd5b5338 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -167,7 +167,11 @@ module.exports = { to: 'reveal.js/plugin' } ]), - new MiniCssExtractPlugin() + new MiniCssExtractPlugin(), + new webpack.NormalModuleReplacementPlugin( + /^url$/, + path.resolve(__dirname, './public/js/url.js') + ) ], entry: { @@ -374,7 +378,8 @@ module.exports = { 'js-sequence-diagrams': path.join(__dirname, 'node_modules/@hackmd/js-sequence-diagrams/build/main.js'), vega: path.join(__dirname, 'node_modules/vega/build/vega.min.js'), 'vega-lite': path.join(__dirname, 'node_modules/vega-lite/build/vega-lite.min.js'), - 'vega-embed': path.join(__dirname, 'node_modules/vega-embed/build/vega-embed.min.js') + 'vega-embed': path.join(__dirname, 'node_modules/vega-embed/build/vega-embed.min.js'), + 'markdown-it': path.join(__dirname, 'node_modules/markdown-it/dist/markdown-it.js') } }, diff --git a/yarn.lock b/yarn.lock index 6db3a214..7a708b09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7890,7 +7890,7 @@ markdown-it-sup@~1.0.0: resolved "https://registry.yarnpkg.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3" integrity sha1-y5yf+RpSVawI8/09YyhuFd8KH8M= -markdown-it@~9.0.1: +markdown-it@9.0.1, markdown-it@~9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.0.1.tgz#aafe363c43718720b6575fd10625cde6e4ff2d47" integrity sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw== @@ -7921,6 +7921,13 @@ markdown-table@^1.1.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== +markdownlint@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.16.0.tgz#69f73cc755a44231fbe5dc7c37a5909cedc0ac6e" + integrity sha512-Zo+iPezP3eM6lLhKepkUw+X98H44lipIdx4d6faaugfB0+7VuDB3R0hXmx7z9F1N3/ypn46oOFgAD9iF++Ie6A== + dependencies: + markdown-it "9.0.1" + marked@~0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.3.tgz#79babad78af638ba4d522a9e715cdfdd2429e946"