Skip to content

Commit 8452da1

Browse files
authored
Merge pull request #1257 from hackmdio/feature/markdownlint
Support markdownlint
2 parents dc29b2e + 1e00106 commit 8452da1

File tree

14 files changed

+186
-2
lines changed

14 files changed

+186
-2
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"markdown-it-sub": "~1.0.0",
9494
"markdown-it-sup": "~1.0.0",
9595
"markdown-pdf": "~9.0.0",
96+
"markdownlint": "^0.16.0",
9697
"mathjax": "~2.7.5",
9798
"mattermost-redux": "~5.13.0",
9899
"mermaid": "~8.2.3",

public/css/index.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,15 @@ div[contenteditable]:empty:not(:focus):before{
513513
.status-bar .status-indicators .status-keymap > a,
514514
.status-bar .status-indicators .status-theme > a,
515515
.status-bar .status-indicators .status-spellcheck > a,
516+
.status-bar .status-indicators .status-linter > a,
516517
.status-bar .status-indicators .status-preferences > a {
517518
color: inherit;
518519
text-decoration: none;
519520
}
520521

521522
.status-bar .status-indicators .status-theme,
522523
.status-bar .status-indicators .status-spellcheck,
524+
.status-bar .status-indicators .status-linter,
523525
.status-bar .status-indicators .status-preferences {
524526
padding: 0 4.3px;
525527
}
@@ -540,17 +542,20 @@ div[contenteditable]:empty:not(:focus):before{
540542
}
541543

542544
.ui-theme-toggle,
545+
.ui-linter-toggle,
543546
.ui-spellcheck-toggle {
544547
opacity: 0.2;
545548
cursor: pointer;
546549
}
547550

548551
.ui-theme-toggle.active,
552+
.ui-linter-toggle.active,
549553
.ui-spellcheck-toggle.active {
550554
opacity: 1;
551555
}
552556

553557
.ui-theme-toggle:hover,
558+
.ui-linter-toggle:hover,
554559
.ui-spellcheck-toggle:hover {
555560
opacity: 0.8;
556561
}

public/images/lint/mark-error.png

193 Bytes
Loading

public/images/lint/mark-multiple.png

126 Bytes
Loading

public/images/lint/mark-warning.png

215 Bytes
Loading

public/images/lint/message-error.png

194 Bytes
Loading
233 Bytes
Loading

public/js/lib/editor/index.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as utils from './utils'
33
import config from './config'
44
import statusBarTemplate from './statusbar.html'
55
import toolBarTemplate from './toolbar.html'
6+
import './markdown-lint'
67

78
/* config section */
89
const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
@@ -231,6 +232,7 @@ export default class Editor {
231232
this.statusLength = this.statusBar.find('.status-length')
232233
this.statusTheme = this.statusBar.find('.status-theme')
233234
this.statusSpellcheck = this.statusBar.find('.status-spellcheck')
235+
this.statusLinter = this.statusBar.find('.status-linter')
234236
this.statusPreferences = this.statusBar.find('.status-preferences')
235237
this.statusPanel = this.editor.addPanel(this.statusBar[0], {
236238
position: 'bottom'
@@ -240,6 +242,7 @@ export default class Editor {
240242
this.setKeymap()
241243
this.setTheme()
242244
this.setSpellcheck()
245+
this.setLinter()
243246
this.setPreferences()
244247
}
245248

@@ -498,6 +501,42 @@ export default class Editor {
498501
}
499502
}
500503

504+
toggleLinter (enable) {
505+
const gutters = this.editor.getOption('gutters')
506+
const lintGutter = 'CodeMirror-lint-markers'
507+
508+
if (enable) {
509+
if (!gutters.includes(lintGutter)) {
510+
this.editor.setOption('gutters', [lintGutter, ...gutters])
511+
}
512+
Cookies.set('linter', true, {
513+
expires: 365
514+
})
515+
} else {
516+
this.editor.setOption('gutters', gutters.filter(g => g !== lintGutter))
517+
Cookies.remove('linter')
518+
}
519+
this.editor.setOption('lint', enable)
520+
}
521+
522+
setLinter () {
523+
const linterToggle = this.statusLinter.find('.ui-linter-toggle')
524+
525+
const updateLinterStatus = (enable) => {
526+
linterToggle.toggleClass('active', enable)
527+
}
528+
529+
linterToggle.click(() => {
530+
const lintEnable = this.editor.getOption('lint')
531+
this.toggleLinter.bind(this)(!lintEnable)
532+
updateLinterStatus(!lintEnable)
533+
})
534+
535+
const enable = !!Cookies.get('linter')
536+
this.toggleLinter.bind(this)(enable)
537+
updateLinterStatus(enable)
538+
}
539+
501540
resetEditorKeymapToBrowserKeymap () {
502541
var keymap = this.editor.getOption('keyMap')
503542
if (!this.jumpToAddressBarKeymapValue) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* global CodeMirror */
2+
import markdownlint from 'markdownlint'
3+
4+
// load CM lint plugin explicitly
5+
import '@hackmd/codemirror/addon/lint/lint'
6+
import './lint.css'
7+
8+
(function (mod) {
9+
mod(CodeMirror)
10+
})(function (CodeMirror) {
11+
function validator (text) {
12+
return lint(text).map(error => {
13+
const {
14+
ruleNames,
15+
ruleDescription,
16+
lineNumber: ln,
17+
errorRange
18+
} = error
19+
const lineNumber = ln - 1
20+
21+
let start = 0; let end = -1
22+
if (errorRange) {
23+
[start, end] = errorRange.map(r => r - 1)
24+
}
25+
26+
return {
27+
messageHTML: `${ruleNames.join('/')}: ${ruleDescription}`,
28+
severity: 'error',
29+
from: CodeMirror.Pos(lineNumber, start),
30+
to: CodeMirror.Pos(lineNumber, end)
31+
}
32+
})
33+
}
34+
35+
CodeMirror.registerHelper('lint', 'markdown', validator)
36+
})
37+
38+
function lint (content) {
39+
const { content: errors } = markdownlint.sync({
40+
strings: {
41+
content
42+
}
43+
})
44+
return errors
45+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/* The lint marker gutter */
2+
.CodeMirror-lint-markers {
3+
width: 16px;
4+
}
5+
6+
.CodeMirror-lint-tooltip {
7+
background-color: #333333;
8+
border: 1px solid #eeeeee;
9+
border-radius: 4px;
10+
color: white;
11+
font-family: "Source Code Pro", Consolas, monaco, monospace;
12+
font-size: 10pt;
13+
overflow: hidden;
14+
padding: 2px 5px;
15+
position: fixed;
16+
white-space: pre;
17+
white-space: pre-wrap;
18+
z-index: 100;
19+
max-width: 600px;
20+
opacity: 0;
21+
transition: opacity .4s;
22+
-moz-transition: opacity .4s;
23+
-webkit-transition: opacity .4s;
24+
-o-transition: opacity .4s;
25+
-ms-transition: opacity .4s;
26+
}
27+
28+
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
29+
background-position: left bottom;
30+
background-repeat: repeat-x;
31+
}
32+
33+
.CodeMirror-lint-mark-error {
34+
background-image: url(/images/lint/mark-error.png);
35+
}
36+
37+
.CodeMirror-lint-mark-warning {
38+
background-image: url(/images/lint/mark-warning.png);
39+
}
40+
41+
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
42+
background-position: center center;
43+
background-repeat: no-repeat;
44+
cursor: pointer;
45+
display: inline-block;
46+
height: 16px;
47+
width: 16px;
48+
vertical-align: middle;
49+
position: relative;
50+
margin-left: 5px;
51+
}
52+
53+
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
54+
padding-left: 20px;
55+
background-position: top left;
56+
background-repeat: no-repeat;
57+
background-position-y: 2px;
58+
}
59+
60+
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
61+
background-image: url(/images/lint/message-error.png);
62+
}
63+
64+
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
65+
background-image: url(/images/lint/message-warning.png);
66+
}
67+
68+
.CodeMirror-lint-marker-multiple {
69+
background-image: url(/images/lint/mark-multiple.png);
70+
background-repeat: no-repeat;
71+
background-position: right bottom;
72+
width: 100%; height: 100%;
73+
}

0 commit comments

Comments
 (0)