Skip to content

Commit 7999db6

Browse files
author
Christopher Baker
authoredMar 6, 2018
utilize Prism from bit-docs-prettify (#8)
* utilize prism from bit-docs-prettify * add collapse support * move collapse and highlight * remove jquery
1 parent 50678fb commit 7999db6

File tree

6 files changed

+373
-229
lines changed

6 files changed

+373
-229
lines changed
 

‎index.js

+105-132
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,118 @@
1-
require("./static/styles/highlight-line.less");
2-
var $ = require("jquery");
1+
require("bit-docs-prettify");
32

4-
var getLines = function(lineString) {
5-
var lineArray = lineString.split(',');
6-
var result = {};
3+
require("prismjs/plugins/line-highlight/prism-line-highlight");
4+
require("prismjs/plugins/line-highlight/prism-line-highlight.css");
75

8-
for (var i = 0; i < lineArray.length; i++) {
9-
var val = lineArray[i];
6+
require("./prism-collapse");
7+
require("./prism-collapse.less");
108

11-
// Matches any string with 1+ digits dash 1+ digits
12-
// will ignore non matching strings
13-
if (/^([\d]+-[\d]+)$/.test(val)) {
14-
var values = val.split('-'),
15-
start = (values[0] - 1),
16-
finish = (values[1] - 1);
9+
/**
10+
* Get node for provided line number
11+
* Copied from prism-line-numbers.js and modified to support nested spans
12+
* Original version assumed all line number spans were inside .line-numbers-rows
13+
* but now they may be may be nested inside collapsed sections
14+
*
15+
* @param {Element} element pre element
16+
* @param {Number} number line number
17+
* @return {Element|undefined}
18+
*/
19+
Prism.plugins.lineNumbers.getLine = function (element, number) {
20+
if (element.tagName !== 'PRE' || !element.classList.contains('line-numbers')) {
21+
return;
22+
}
1723

18-
for (var j = start; finish >= j; j++) {
19-
result[j] = true;
20-
}
21-
//matches one or more digits
22-
} else if (/^[\d]+$/.test(val)) {
23-
result[val - 1] = true;
24-
} else {
25-
result[val] = true;
26-
}
24+
var lineNumberRows = element.querySelector('.line-numbers-rows');
25+
var lineNumbers = lineNumberRows.querySelectorAll('span'); // added
26+
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
27+
var lineNumberEnd = lineNumberStart + (lineNumbers.length - 1);
2728

29+
if (number < lineNumberStart) {
30+
number = lineNumberStart;
31+
}
32+
if (number > lineNumberEnd) {
33+
number = lineNumberEnd;
2834
}
29-
return result;
35+
36+
var lineIndex = number - lineNumberStart;
37+
38+
return lineNumbers[lineIndex];
3039
};
3140

32-
/**
33-
* @parent bit-docs-html-highlight-line/static
34-
* @module {function} bit-docs-html-highlight-line/highlight-line.js
35-
*
36-
* Main front end JavaScript file for static portion of this plugin.
37-
*
38-
* @signature `addHighlights()`
39-
*
40-
* Goes through the lines in a `<code>` block and highlights the specified
41-
* ranges.
42-
*
43-
* Finds all `<span highlight-line="..."></span>` elements and uses those as
44-
* directives for what to highlight.
45-
*
46-
* If the `only` option was specified to the
47-
* [bit-docs-html-highlight-line/tags/highlight] tag, then non-highlighted
48-
* lines will be collapsed if they exist greater than three lines away from a
49-
* highlighted line.
50-
*/
51-
function addHighlights() {
52-
53-
$('span[line-highlight]').each(function(i, el) {
54-
var $el = $(el);
55-
var lines = getLines($el.attr('line-highlight'));
56-
var codeBlock = $el.parent().prev('pre').children('code');
57-
codeBlock.addClass("line-highlight");
58-
59-
var lineMap = [[]];
60-
var k = 0;
61-
codeBlock.children().each(function(i, el) {
62-
var nodeText = $(el).text();
63-
if (/\n/.test(nodeText)) {
64-
65-
var cNames = $(el).attr('class');
66-
var str = nodeText.split('\n');
67-
var l = str.length;
68-
69-
for (var j = 0; j < l; j++) {
70-
var text = j === (l - 1) ? str[j] : str[j] + '\n';
71-
var newNode = document.createElement('span');
72-
newNode.className = cNames;
73-
$(newNode).text(text);
74-
lineMap[k].push(newNode);
75-
76-
if (j !== (l - 1)) {
77-
k++;
78-
lineMap[k] = [];
79-
}
80-
}
81-
} else {
82-
lineMap[k].push(el);
41+
var padding = 3;
42+
var getConfig = function(lineString, lineCount) {
43+
var lines = lineString
44+
.split(',')
45+
.map(function(data) {
46+
return data.trim();
47+
})
48+
.filter(function(data) {
49+
return data;
50+
})
51+
;
52+
53+
var collapse = [];
54+
var index = lines.indexOf('only');
55+
if (index > -1) {
56+
lines.splice(index, 1);
57+
58+
var current = 1;
59+
for (var i = 0; i < lines.length; i++) {
60+
var range = lines[i]
61+
.split('-')
62+
.map(function(val) {
63+
return parseInt(val);
64+
})
65+
.filter(function(val) {
66+
return typeof val === 'number' && !isNaN(val);
67+
})
68+
;
69+
70+
if (range[0] > current + padding) {
71+
collapse.push(current + '-' + (range[0] - 1 - padding));
8372
}
84-
});
85-
86-
codeBlock.empty();
87-
if(lines.only) {
88-
var segments = [];
89-
lineMap.forEach(function(lineNodes, lineNumber){
90-
var visible = lines[lineNumber];
91-
var lineNode = document.createElement('span');
92-
$(lineNode).append(lineNodes);
93-
lineNode.className = lines[lineNumber] ? 'line highlight line-'+lineNumber: 'line line-'+lineNumber ;
94-
95-
var lastSegment = segments[segments.length - 1];
96-
if(!lastSegment || lastSegment.visible !== visible) {
97-
segments.push(lastSegment = {visible: visible, lines: []});
98-
}
99-
lastSegment.lines.push(lineNode);
100-
101-
102-
});
103-
segments.forEach(function(segment, index){
104-
var next = segments[index+1];
105-
106-
if(segment.visible) {
107-
// take 3 lines from next if possible
108-
if(next) {
109-
var first = next.lines.splice(0,3);
110-
segment.lines = segment.lines.concat(first);
111-
}
112-
codeBlock.append(segment.lines);
113-
} else {
114-
// move 3 lines to next if possible
115-
if(next) {
116-
var last = segment.lines.splice(segment.lines.length-3);
117-
next.lines = last.concat(next.lines);
118-
}
119-
if(segment.lines.length > 2) {
120-
var expander = document.createElement('div');
121-
expander.className = "expand";
122-
expander.addEventListener("click", function(){
123-
$(expander).replaceWith(segment.lines);
124-
});
125-
codeBlock.append(expander);
126-
} else {
127-
codeBlock.append(segment.lines);
128-
}
129-
}
130-
});
131-
132-
133-
} else {
134-
lineMap.forEach(function(lineNodes, lineNumber){
135-
var newNode = document.createElement('span');
136-
newNode.className = lines[lineNumber] ? 'line highlight': 'line' ;
137-
$(newNode).append(lineNodes);
138-
codeBlock.append(newNode);
139-
});
73+
74+
current = (range[1] || range[0]) + padding + 1;
14075
}
14176

142-
});
77+
if (current < lineCount) {
78+
collapse.push(current + '-' + lineCount);
79+
}
80+
}
81+
82+
return {
83+
lines: lines.length ? lines.join(',') : false,
84+
collapse: collapse.length ? collapse.join(',') : false,
85+
};
86+
};
87+
88+
function findPreviousSibling(el, tag) {
89+
tag = tag.toUpperCase();
90+
91+
while (el = el.previousSibling) {
92+
if (el.tagName && el.tagName.toUpperCase() === tag) {
93+
return el;
94+
}
95+
}
14396
}
14497

145-
module.exports = addHighlights;
98+
module.exports = function() {
99+
var highlights = document.querySelectorAll('span[line-highlight]')
100+
101+
for (var i = 0; i < highlights.length; i++) {
102+
var highlight = highlights[i];
103+
104+
var preBlock = findPreviousSibling(highlight.parentElement, 'pre');
105+
var codeBlock = preBlock.childNodes.item(0);
106+
107+
var total = codeBlock.innerHTML.split('\n').length - 1;
108+
var config = getConfig(highlight.getAttribute('line-highlight'), total);
109+
110+
if (preBlock) {
111+
preBlock.setAttribute('data-line', config.lines);
112+
113+
if (config.collapse) {
114+
preBlock.setAttribute('data-collapse', config.collapse);
115+
}
116+
}
117+
};
118+
};

‎package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"preversion": "npm test",
88
"test": "mocha test.js --reporter spec",
9+
"release:pre": "npm version prerelease && npm publish --tag=pre",
910
"release:patch": "npm version patch && npm publish",
1011
"release:minor": "npm version minor && npm publish",
1112
"release:major": "npm version major && npm publish"
@@ -25,12 +26,13 @@
2526
},
2627
"homepage": "https://github.com/bit-docs/bit-docs-html-highlight-line#readme",
2728
"dependencies": {
28-
"jquery": "^2.2.4"
29+
"bit-docs-prettify": "^0.2.2-8",
30+
"prismjs": "^1.11.0"
2931
},
3032
"devDependencies": {
3133
"bit-docs-generate-html": "^0.1.0",
3234
"connect": "^2.14.4",
3335
"mocha": "^2.5.3",
34-
"zombie": "^4.2.1"
36+
"zombie": "^4.3.0"
3537
}
3638
}

‎prism-collapse.js

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
2+
throw new Error('Prism must be loaded before prism-collapse');
3+
}
4+
5+
function hasClass(element, className) {
6+
className = " " + className + " ";
7+
return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1
8+
}
9+
10+
function adjustHighlights(pre, collapseRange, visible) {
11+
collapseRange = collapseRange.split('-').map(function(value) {
12+
return parseInt(value);
13+
});
14+
15+
var highlights = pre.querySelectorAll('.line-highlight');
16+
for (var i = 0; i < highlights.length; i++) {
17+
var highlight = highlights[i];
18+
19+
var range = highlight.getAttribute('data-range').split('-').map(function(value) {
20+
return parseInt(value);
21+
});
22+
23+
if (range.length === 1) {
24+
var line = range[0];
25+
if (line < collapseRange[0]) {
26+
continue;
27+
}
28+
29+
if (line > collapseRange[1]) {
30+
var lineNode = Prism.plugins.lineNumbers.getLine(pre, line);
31+
if (lineNode) {
32+
highlight.style.top = lineNode.offsetTop + 'px';
33+
}
34+
35+
continue;
36+
}
37+
38+
highlight.style.display = visible ? 'block' : 'none';
39+
}
40+
41+
if (range.length === 2) {
42+
if (range[1] < collapseRange[0]) {
43+
continue;
44+
}
45+
46+
if (range[0] > collapseRange[1]) {
47+
var lineNode = Prism.plugins.lineNumbers.getLine(pre, range[0]);
48+
if (lineNode) {
49+
highlight.style.top = lineNode.offsetTop + 'px';
50+
}
51+
52+
continue;
53+
}
54+
55+
highlight.style.display = visible ? 'block' : 'none';
56+
}
57+
};
58+
}
59+
60+
function collapseLines(pre, config) {
61+
var inserts = [];
62+
63+
var ranges = config.split(',');
64+
for (var i = 0; i < ranges.length; i++) {
65+
var range = ranges[i];
66+
var parts = range.split('-');
67+
68+
var wrapper = '<div class="collapse collapsed" data-index="' + i + '" data-range="' + range + '">';
69+
inserts.push([
70+
+parts[0],
71+
wrapper + '<div class="collapse-code">',
72+
wrapper + '<div class="collapse-lines">'
73+
]);
74+
75+
inserts.push([
76+
+parts[1] + 1,
77+
'</div></div>',
78+
'</div></div>'
79+
]);
80+
}
81+
82+
inserts.sort(function (a, b) {
83+
return b[0] - a[0];
84+
});
85+
86+
87+
var codeContainer = pre.children[0];
88+
89+
var numbersContainer = codeContainer.lastChild;
90+
var numbers = numbersContainer.innerHTML.split('<span></span>');
91+
numbersContainer.remove();
92+
93+
var code = codeContainer.innerHTML.split('\n');
94+
code = code.map(function(line, index) {
95+
if (index === code.length - 1) {
96+
return line;
97+
}
98+
99+
return line + '\n';
100+
});
101+
102+
for (var i = 0; i < inserts.length; i++) {
103+
var line = Math.min(code.length - 1, inserts[i][0] - 1);
104+
105+
code.splice(line, 0, inserts[i][1]);
106+
numbers[line] += inserts[i][2];
107+
}
108+
109+
codeContainer.innerHTML = code.join('');
110+
numbersContainer.innerHTML = numbers.join('<span></span>');
111+
codeContainer.appendChild(numbersContainer);
112+
113+
for (var i = 0; i < ranges.length; i++) {
114+
var range = ranges[i];
115+
adjustHighlights(pre, range, false);
116+
}
117+
}
118+
119+
function findPreviousParent(el, tag) {
120+
tag = tag.toUpperCase();
121+
122+
while (el = el.parentElement) {
123+
if (el.tagName && el.tagName.toUpperCase() === tag) {
124+
return el;
125+
}
126+
}
127+
}
128+
129+
Prism.hooks.add('complete', function completeHook(env) {
130+
var pre = env.element.parentNode;
131+
var config = pre && pre.getAttribute('data-collapse');
132+
133+
if (!pre || !config || !/pre/i.test(pre.nodeName)) {
134+
return;
135+
}
136+
137+
var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
138+
139+
if (hasClass(pre, 'line-numbers') && !isLineNumbersLoaded) {
140+
Prism.hooks.add('line-numbers', completeHook);
141+
} else {
142+
collapseLines(pre, config);
143+
}
144+
});
145+
146+
document.body.addEventListener('click', function(event) {
147+
var collapse = event.target;
148+
if (!collapse.classList.contains('collapse') || !collapse.classList.contains('collapsed')) {
149+
return;
150+
}
151+
152+
var index = collapse.getAttribute('data-index');
153+
var code = findPreviousParent(collapse, 'code');
154+
var pre = findPreviousParent(code, 'pre');
155+
156+
var lines = code.querySelectorAll('.collapse[data-index="' + index + '"]');
157+
for (var i = 0; i < lines.length; i++) {
158+
lines[i].classList.remove('collapsed');
159+
}
160+
161+
adjustHighlights(pre, collapse.getAttribute('data-range'), true);
162+
}, false);

‎prism-collapse.less

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
pre code {
2+
.collapse.collapsed {
3+
color: black;
4+
text-align: center;
5+
background-color: #eee;
6+
border-top: 1px solid #ccc;
7+
border-bottom: 1px solid #ccc;
8+
padding: calc(~"0.25em - 1px") 0;
9+
margin: 0 -1em;
10+
11+
> .collapse-code {
12+
display: none;
13+
}
14+
15+
&:hover {
16+
opacity: .6;
17+
cursor: pointer;
18+
}
19+
20+
&:before {
21+
display: block;
22+
content: '⇅ EXPAND ⇅';
23+
margin: 0 auto;
24+
25+
width: 1em;
26+
height: 1em;
27+
line-height: 1em;
28+
}
29+
}
30+
}
31+
32+
pre.line-numbers code {
33+
> .line-numbers-rows > .collapse.collapsed {
34+
border: none;
35+
padding: 0;
36+
margin: 0;
37+
height: 1.5em;
38+
39+
opacity: 0;
40+
41+
> .collapse-lines {
42+
position: absolute;
43+
height: 0;
44+
overflow: hidden;
45+
}
46+
47+
&:before {
48+
display: none;
49+
}
50+
}
51+
}
52+
53+
54+
/* conditional positioning of colored bar */
55+
56+
pre[data-line] code .collapse.collapsed {
57+
margin-left: -3.8em;
58+
}
59+
60+
pre.line-numbers code .collapse.collapsed {
61+
margin-left: -4em;
62+
}
63+
64+
65+
/* line-numbers overwrites so that spans can be nested */
66+
67+
.line-numbers-rows span {
68+
pointer-events: none;
69+
display: block;
70+
counter-increment: linenumber;
71+
}
72+
73+
.line-numbers-rows span:before {
74+
content: counter(linenumber);
75+
color: #999;
76+
display: block;
77+
padding-right: .8em;
78+
text-align: right;
79+
}

‎static/styles/highlight-line.less

-38
This file was deleted.

‎test.js

+23-57
Original file line numberDiff line numberDiff line change
@@ -3,68 +3,38 @@ var generate = require("bit-docs-generate-html/generate");
33
var path = require("path");
44
var fs = require("fs");
55

6-
var Browser = require("zombie"),
7-
connect = require("connect");
6+
var Browser = require("zombie");
7+
var connect = require("connect");
88

9-
var find = function(browser, property, callback, done){
10-
var start = new Date();
11-
var check = function(){
12-
if(browser.window && browser.window[property]) {
13-
callback(browser.window[property]);
14-
} else if(new Date() - start < 2000){
15-
setTimeout(check, 20);
16-
} else {
17-
done("failed to find "+property);
18-
}
19-
};
20-
check();
21-
};
22-
23-
var waitFor = function(browser, checker, callback, done){
24-
var start = new Date();
25-
var check = function(){
26-
if(checker(browser.window)) {
27-
callback(browser.window);
28-
} else if(new Date() - start < 2000){
29-
setTimeout(check, 20);
30-
} else {
31-
done(new Error("checker was never true"));
32-
}
33-
};
34-
check();
35-
};
36-
37-
38-
var open = function(url, callback, done){
39-
var server = connect().use(connect.static(path.join(__dirname))).listen(8081);
9+
var open = function(url, callback, done) {
10+
var server = connect().use(connect.static(path.join(__dirname, "temp"))).listen(8081);
4011
var browser = new Browser();
41-
browser.visit("http://localhost:8081/"+url)
42-
.then(function(){
43-
callback(browser, function(){
12+
browser.visit("http://localhost:8081/" + url)
13+
.then(function() {
14+
callback(browser, function() {
4415
server.close();
4516
});
46-
}).catch(function(e){
17+
}).catch(function(e) {
4718
server.close();
4819
done(e);
4920
});
5021
};
5122

52-
describe("bit-docs-tag-demo", function(){
53-
it("basics works", function(done){
23+
describe("bit-docs-html-highlight-line", function() {
24+
it("basics works", function(done) {
5425
this.timeout(60000);
5526

5627
var docMap = Promise.resolve({
5728
index: {
5829
name: "index",
5930
demo: "path/to/demo.html",
60-
body: ""+fs.readFileSync(__dirname+"/test-demo.md")
31+
body: fs.readFileSync(__dirname+"/test-demo.md", "utf8")
6132
}
6233
});
6334

6435
generate(docMap, {
6536
html: {
6637
dependencies: {
67-
"bit-docs-prettify": "^0.1.0",
6838
"bit-docs-html-highlight-line": __dirname
6939
}
7040
},
@@ -73,23 +43,19 @@ describe("bit-docs-tag-demo", function(){
7343
forceBuild: true,
7444
debug: true,
7545
minifyBuild: false
76-
}).then(function(){
46+
}).then(function() {
47+
open("index.html",function(browser, close) {
48+
var doc = browser.window.document;
49+
50+
var lineCodes = doc.querySelectorAll('pre[data-line] code');
51+
var collapseCodes = doc.querySelectorAll('pre[data-collapse] code');
52+
53+
assert.ok(lineCodes.length, "there are code blocks with data-line");
54+
assert.ok(collapseCodes.length, "there are code blocks with data-collapse");
7755

78-
open("temp/index.html",function(browser, close){
79-
waitFor(browser, function(window){
80-
return window.document.getElementsByClassName("highlight").length;
81-
}, function(){
82-
var doc = browser.window.document;
83-
var highlights = doc.getElementsByClassName("highlight");
84-
// NOTE: there should be 2 lines. But it seems
85-
// like prettify doesn't work in zombie right.
86-
assert.ok(highlights.length, "there are 2 tabs");
87-
var codeBlocks = doc.getElementsByClassName("line-highlight");
88-
assert.ok(codeBlocks.length, "there are code blocks with highlight class");
89-
close();
90-
done();
91-
}, done);
92-
},done);
56+
close();
57+
done();
58+
}, done);
9359
}, done);
9460
});
9561
});

0 commit comments

Comments
 (0)
Please sign in to comment.