Skip to content

Commit 49c1c3d

Browse files
authored
Merge pull request #17 from hackmdio/feature/customize-linter
2 parents e748901 + fad9261 commit 49c1c3d

3 files changed

Lines changed: 146 additions & 22 deletions

File tree

addon/hint/show-hint.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
cursor: pointer;
3131
}
3232

33+
.CodeMirror-hint:hover {
34+
background: #08f;
35+
color: white;
36+
}
37+
3338
li.CodeMirror-hint-active {
3439
background: #08f;
3540
color: white;

addon/lint/lint.js

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"use strict";
1313
var GUTTER_ID = "CodeMirror-lint-markers";
1414

15-
function showTooltip(cm, e, content) {
15+
function showTooltip(cm, e, content, node) {
1616
var tt = document.createElement("div");
1717
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
1818
tt.appendChild(content.cloneNode(true));
@@ -21,13 +21,21 @@
2121
else
2222
document.body.appendChild(tt);
2323

24-
function position(e) {
25-
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
26-
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
27-
tt.style.left = (e.clientX + 5) + "px";
24+
if (cm.state.lint.options.fixedTooltip) {
25+
const { top, left } = node.getBoundingClientRect()
26+
27+
tt.style.top = top - 5 + 'px'
28+
tt.style.left = left + 20 + 'px'
29+
} else {
30+
function position(e) {
31+
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
32+
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
33+
tt.style.left = (e.clientX + 5) + "px";
34+
}
35+
CodeMirror.on(document, "mousemove", position);
36+
position(e);
2837
}
29-
CodeMirror.on(document, "mousemove", position);
30-
position(e);
38+
3139
if (tt.style.opacity != null) tt.style.opacity = 1;
3240
return tt;
3341
}
@@ -42,29 +50,101 @@
4250
}
4351

4452
function showTooltipFor(cm, e, content, node) {
45-
var tooltip = showTooltip(cm, e, content);
53+
var tooltip = showTooltip(cm, e, content, node);
4654
function hide() {
4755
CodeMirror.off(node, "mouseout", hide);
4856
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
4957
}
50-
var poll = setInterval(function() {
51-
if (tooltip) for (var n = node;; n = n.parentNode) {
52-
if (n && n.nodeType == 11) n = n.host;
53-
if (n == document.body) return;
54-
if (!n) { hide(); break; }
55-
}
56-
if (!tooltip) return clearInterval(poll);
57-
}, 400);
58+
59+
if (!cm.state.lint.options.fixedTooltip) {
60+
var poll = setInterval(function() {
61+
if (tooltip) for (var n = node;; n = n.parentNode) {
62+
if (n && n.nodeType == 11) n = n.host;
63+
if (n == document.body) return;
64+
if (!n) { hide(); break; }
65+
}
66+
if (!tooltip) return clearInterval(poll);
67+
}, 400);
68+
}
69+
5870
CodeMirror.on(node, "mouseout", hide);
5971
}
72+
73+
/**
74+
*
75+
* @param {*} cm
76+
* @param {Array<{ content: string, html: string, onClick: any }>} menus
77+
* @param {*} e
78+
*/
79+
function showMenu (cm, menus, e) {
80+
var target = e.target || e.srcElement;
81+
var state = cm.state.lint
82+
83+
if (state.hints) {
84+
remove(state.hints)
85+
}
86+
87+
if (!menus && menus.length > 0) {
88+
return
89+
}
90+
91+
// build menu
92+
var hints = document.createElement("ul");
93+
var theme = cm.options.theme;
94+
hints.className = "CodeMirror-hints " + theme;
95+
hints.style.position = 'fixed'
96+
hints.style.zIndex = '999'
97+
98+
for (let item of menus) {
99+
const elt = hints.appendChild(document.createElement('li'))
100+
elt.className = 'CodeMirror-hint'
101+
if (item.content) {
102+
elt.textContent = item.content
103+
} else {
104+
elt.innerHTML = item.html
105+
}
106+
const onClick = item.onClick
107+
elt.addEventListener('click', (e) => {
108+
if (typeof onClick === 'function') {
109+
onClick(e)
110+
}
111+
remove(hints)
112+
})
113+
}
114+
115+
var removal = function () { setTimeout(function () { remove(hints) }, 100) }
116+
function remove (hints) {
117+
if (hints.parentNode) {
118+
hints.parentNode.removeChild(hints)
119+
}
120+
state.hints = null
121+
122+
cm.off("mousedown", removal)
123+
cm.off("scroll", removal)
124+
}
125+
126+
state.hints = hints
127+
document.body.appendChild(hints)
128+
const { left, top } = target.getBoundingClientRect()
129+
130+
hints.style.top = top + 5 + 'px'
131+
hints.style.left = left + 20 + 'px'
132+
133+
134+
cm.on("mousedown", removal)
135+
cm.on("scroll", removal)
136+
}
60137

61138
function LintState(cm, options, hasGutter) {
62139
this.marked = [];
63140
this.options = options;
64141
this.timeout = null;
65142
this.hasGutter = hasGutter;
66143
this.onMouseOver = function(e) { onMouseOver(cm, e); };
144+
this.onClick = function(e) { onClick(cm, e); };
67145
this.waitingFor = 0
146+
147+
this.contextMenuEnable = typeof options.contextmenu === 'function'
68148
}
69149

70150
function parseOptions(_cm, options) {
@@ -81,7 +161,7 @@
81161
state.marked.length = 0;
82162
}
83163

84-
function makeMarker(cm, labels, severity, multiple, tooltips) {
164+
function makeMarker(cm, labels, severity, multiple, tooltips, annotations) {
85165
var marker = document.createElement("div"), inner = marker;
86166
marker.className = "CodeMirror-lint-marker-" + severity;
87167
if (multiple) {
@@ -93,6 +173,13 @@
93173
showTooltipFor(cm, e, labels, inner);
94174
});
95175

176+
if (cm.state.lint.contextMenuEnable) {
177+
marker.addEventListener('click', function (e) {
178+
const menus = cm.state.lint.options.contextmenu(annotations)
179+
showMenu(cm, menus, e)
180+
})
181+
}
182+
96183
return marker;
97184
}
98185

@@ -190,7 +277,7 @@
190277

191278
if (state.hasGutter)
192279
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
193-
state.options.tooltips));
280+
state.options.tooltips, anns));
194281
}
195282
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
196283
}
@@ -211,8 +298,8 @@
211298
}
212299
showTooltipFor(cm, e, tooltip, target);
213300
}
214-
215-
function onMouseOver(cm, e) {
301+
302+
function handleMarkerAction (cm, e, cb) {
216303
var target = e.target || e.srcElement;
217304
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
218305
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
@@ -223,7 +310,19 @@
223310
var ann = spans[i].__annotation;
224311
if (ann) annotations.push(ann);
225312
}
226-
if (annotations.length) popupTooltips(cm, annotations, e);
313+
314+
if (annotations.length) cb(cm, annotations, e)
315+
}
316+
317+
function onMouseOver(cm, e) {
318+
handleMarkerAction(cm, e, popupTooltips)
319+
}
320+
321+
function onClick (cm, e) {
322+
handleMarkerAction(cm, e, function (cm, annotations, e) {
323+
const menus = cm.state.lint.options.contextmenu(annotations)
324+
showMenu(cm, menus, e)
325+
})
227326
}
228327

229328
CodeMirror.defineOption("lint", false, function(cm, val, old) {
@@ -244,6 +343,9 @@
244343
cm.on("change", onChange);
245344
if (state.options.tooltips != false && state.options.tooltips != "gutter")
246345
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
346+
if (state.contextMenuEnable) {
347+
CodeMirror.on(cm.getWrapperElement(), "click", state.onClick);
348+
}
247349

248350
startLinting(cm);
249351
}

demo/lint.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<link rel="stylesheet" href="../lib/codemirror.css">
88
<link rel="stylesheet" href="../addon/lint/lint.css">
9+
<link rel="stylesheet" href="../addon/hint/show-hint.css">
910
<script src="../lib/codemirror.js"></script>
1011
<script src="../mode/javascript/javascript.js"></script>
1112
<script src="../mode/css/css.js"></script>
@@ -164,7 +165,23 @@ <h2>Linter Demo</h2>
164165
lineNumbers: true,
165166
mode: "css",
166167
gutters: ["CodeMirror-lint-markers"],
167-
lint: true
168+
lint: {
169+
fixedTooltip: true,
170+
contextmenu: annotations => ([
171+
{
172+
html: 'Hello World <code>code inside</code>',
173+
onClick (e) {
174+
console.log(annotations)
175+
}
176+
},
177+
{
178+
content: 'Another fixed me',
179+
onClick (e) {
180+
console.log(annotations)
181+
}
182+
}
183+
])
184+
}
168185
});
169186
</script>
170187

0 commit comments

Comments
 (0)