debian-mirror-gitlab/app/assets/javascripts/blob/line_highlighter.js

184 lines
5.5 KiB
JavaScript
Raw Normal View History

2020-05-24 23:13:21 +05:30
/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return */
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2021-03-08 18:12:59 +05:30
import { scrollToElement } from '~/lib/utils/common_utils';
2018-05-09 12:01:36 +05:30
2016-09-29 09:46:39 +05:30
// LineHighlighter
//
// Handles single- and multi-line selection and highlight for blob views.
//
//
// ### Example Markup
//
// <div id="blob-content-holder">
// <div class="file-content">
// <div class="line-numbers">
// <a href="#L1" id="L1" data-line-number="1">1</a>
// <a href="#L2" id="L2" data-line-number="2">2</a>
// <a href="#L3" id="L3" data-line-number="3">3</a>
// <a href="#L4" id="L4" data-line-number="4">4</a>
// <a href="#L5" id="L5" data-line-number="5">5</a>
// </div>
// <pre class="code highlight">
// <code>
// <span id="LC1" class="line">...</span>
// <span id="LC2" class="line">...</span>
// <span id="LC3" class="line">...</span>
// <span id="LC4" class="line">...</span>
// <span id="LC5" class="line">...</span>
// </code>
// </pre>
// </div>
// </div>
//
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
const LineHighlighter = function (options = {}) {
2018-03-17 18:26:18 +05:30
options.highlightLineClass = options.highlightLineClass || 'hll';
options.fileHolderSelector = options.fileHolderSelector || '.file-holder';
options.scrollFileHolder = options.scrollFileHolder || false;
2018-11-08 19:23:39 +05:30
options.hash = options.hash || window.location.hash;
2022-06-21 17:19:12 +05:30
options.scrollBehavior = options.scrollBehavior || 'smooth';
2016-09-13 17:45:13 +05:30
2018-03-17 18:26:18 +05:30
this.options = options;
this._hash = options.hash;
this.highlightLineClass = options.highlightLineClass;
this.setHash = this.setHash.bind(this);
this.highlightLine = this.highlightLine.bind(this);
this.clickHandler = this.clickHandler.bind(this);
this.highlightHash = this.highlightHash.bind(this);
this.bindEvents();
this.highlightHash();
};
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.bindEvents = function () {
2018-03-17 18:26:18 +05:30
const $fileHolder = $(this.options.fileHolderSelector);
$fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$fileHolder.on('highlight:line', this.highlightHash);
2021-03-08 18:12:59 +05:30
window.addEventListener('hashchange', (e) => this.highlightHash(e.target.location.hash));
2018-03-17 18:26:18 +05:30
};
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.highlightHash = function (newHash) {
2018-03-17 18:26:18 +05:30
let range;
if (newHash && typeof newHash === 'string') this._hash = newHash;
this.clearHighlight();
if (this._hash !== '') {
range = this.hashToRange(this._hash);
if (range[0]) {
this.highlightRange(range);
const lineSelector = `#L${range[0]}`;
2021-03-08 18:12:59 +05:30
scrollToElement(lineSelector, {
2018-03-17 18:26:18 +05:30
// Scroll to the first highlighted line on initial load
2021-03-08 18:12:59 +05:30
// Add an offset of -100 for some context
offset: -100,
2022-06-21 17:19:12 +05:30
behavior: this.options.scrollBehavior,
2021-03-08 18:12:59 +05:30
});
2018-03-17 18:26:18 +05:30
}
}
};
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.clickHandler = function (event) {
2019-12-26 22:10:19 +05:30
let range;
2018-03-17 18:26:18 +05:30
event.preventDefault();
this.clearHighlight();
2021-03-08 18:12:59 +05:30
const lineNumber = $(event.target).closest('a').data('lineNumber');
2019-12-26 22:10:19 +05:30
const current = this.hashToRange(this._hash);
2018-03-17 18:26:18 +05:30
if (!(current[0] && event.shiftKey)) {
// If there's no current selection, or there is but Shift wasn't held,
// treat this like a single-line selection.
this.setHash(lineNumber);
return this.highlightLine(lineNumber);
} else if (event.shiftKey) {
if (lineNumber < current[0]) {
range = [lineNumber, current[0]];
} else {
range = [current[0], lineNumber];
}
this.setHash(range[0], range[1]);
return this.highlightRange(range);
}
};
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.clearHighlight = function () {
2019-12-21 20:55:43 +05:30
return $(`.${this.highlightLineClass}`).removeClass(this.highlightLineClass);
2018-03-17 18:26:18 +05:30
};
// Convert a URL hash String into line numbers
//
// hash - Hash String
//
// Examples:
//
// hashToRange('#L5') # => [5, null]
// hashToRange('#L5-15') # => [5, 15]
// hashToRange('#foo') # => [null, null]
//
// Returns an Array
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.hashToRange = function (hash) {
2021-09-30 23:02:18 +05:30
// ?L(\d+)(?:-L?(\d+))?$/)
const matches = hash.match(/^#?L(\d+)(?:-L?(\d+))?$/);
2018-03-17 18:26:18 +05:30
if (matches && matches.length) {
2019-12-26 22:10:19 +05:30
const first = parseInt(matches[1], 10);
const last = matches[2] ? parseInt(matches[2], 10) : null;
2018-03-17 18:26:18 +05:30
return [first, last];
}
2020-05-24 23:13:21 +05:30
return [null, null];
2018-03-17 18:26:18 +05:30
};
// Highlight a single line
//
// lineNumber - Line number to highlight
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.highlightLine = function (lineNumber) {
2019-12-21 20:55:43 +05:30
return $(`#LC${lineNumber}`).addClass(this.highlightLineClass);
2018-03-17 18:26:18 +05:30
};
// Highlight all lines within a range
//
// range - Array containing the starting and ending line numbers
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.highlightRange = function (range) {
2018-03-17 18:26:18 +05:30
if (range[1]) {
2018-11-08 19:23:39 +05:30
const results = [];
const ref = range[0] <= range[1] ? range : range.reverse();
for (let lineNumber = range[0]; lineNumber <= ref[1]; lineNumber += 1) {
2018-03-17 18:26:18 +05:30
results.push(this.highlightLine(lineNumber));
}
2018-11-08 19:23:39 +05:30
2018-03-17 18:26:18 +05:30
return results;
}
2020-05-24 23:13:21 +05:30
return this.highlightLine(range[0]);
2018-03-17 18:26:18 +05:30
};
// Set the URL hash string
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.setHash = function (firstLineNumber, lastLineNumber) {
2019-12-26 22:10:19 +05:30
let hash;
2018-03-17 18:26:18 +05:30
if (lastLineNumber) {
2019-12-21 20:55:43 +05:30
hash = `#L${firstLineNumber}-${lastLineNumber}`;
2018-03-17 18:26:18 +05:30
} else {
2019-12-21 20:55:43 +05:30
hash = `#L${firstLineNumber}`;
2018-03-17 18:26:18 +05:30
}
this._hash = hash;
return this.__setLocationHash__(hash);
};
// Make the actual hash change in the browser
//
// This method is stubbed in tests.
2021-03-08 18:12:59 +05:30
LineHighlighter.prototype.__setLocationHash__ = function (value) {
2018-12-13 13:39:08 +05:30
return window.history.pushState(
{
url: value,
// We're using pushState instead of assigning location.hash directly to
// prevent the page from scrolling on the hashchange event
},
document.title,
value,
);
2018-03-17 18:26:18 +05:30
};
export default LineHighlighter;