| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- class MarkJs {
- constructor(p_adapter, p_container) {
- this.className = 'vx-search-match';
- this.currentMatchClassName = 'vx-current-search-match';
- this.adapter = p_adapter;
- this.container = p_container;
- this.markjs = null;
- this.cache = null;
- this.matchedNodes = null;
- this.currentMatchedNodes = null;
- this.adapter.on('basicMarkdownRendered', () => {
- this.clearCache();
- });
- this.adapter.on('rendered', () => {
- this.clearCache();
- });
- }
- // @p_options: {
- // findBackward,
- // caseSensitive,
- // wholeWordOnly,
- // regularExpression
- // }
- findText(p_texts, p_options, p_currentMatchLine) {
- if (!this.markjs) {
- this.markjs = new Mark(this.container);
- }
- if (!p_texts || p_texts.length == 0) {
- // Clear the cache and highlight.
- this.clearCache();
- return;
- }
- if (this.findInCache(p_texts, p_options, p_currentMatchLine)) {
- return;
- }
- // A new find.
- this.clearCache();
- let callbackFunc = function(markjs, texts, options, currentMatchLine) {
- let _markjs = markjs;
- let _texts = texts;
- let _options = options;
- let _currentMatchLine = currentMatchLine;
- return function() {
- if (_markjs.matchedNodes === null) {
- _markjs.matchedNodes = _markjs.container.getElementsByClassName(_markjs.className);
- _markjs.currentMatchedNodes = _markjs.container.getElementsByClassName(_markjs.currentMatchClassName);
- }
- // Update cache.
- _markjs.cache = {
- texts: _texts,
- options: _options,
- currentIdx: -1
- }
- _markjs.updateCurrentMatch(_texts, !_options.findBackward, _currentMatchLine);
- };
- }
- if (p_options.regularExpression) {
- this.findByOneRegExp({
- 'texts': p_texts,
- 'options': p_options,
- 'textIdx': 0,
- 'lastCallback': callbackFunc(this, p_texts, p_options, p_currentMatchLine)
- });
- } else {
- let opt = this.createMarkjsOptions(p_options);
- opt.done = callbackFunc(this, p_texts, p_options, p_currentMatchLine);
- this.markjs.mark(p_texts, opt);
- }
- }
- createMarkjsOptions(p_options) {
- let opt = {
- 'element': 'span',
- 'className': this.className,
- 'caseSensitive': p_options.caseSensitive,
- 'accuracy': p_options.wholeWordOnly ? 'exactly' : 'partially',
- // Ignore SVG, or SVG will be corrupted.
- 'exclude': ['svg *'],
- 'separateWordSearch': false,
- 'acrossElements': true
- }
- return opt;
- }
- // @p_paras: {
- // texts,
- // options,
- // textIdx,
- // lastCallback
- // }
- findByOneRegExp(p_paras) {
- console.log('findByOneRegExp', p_paras.texts.length, p_paras.textIdx);
- if (p_paras.textIdx >= p_paras.texts.length) {
- return;
- }
- let opt = this.createMarkjsOptions(p_paras.options);
- if (p_paras.textIdx == p_paras.texts.length - 1) {
- opt.done = p_paras.lastCallback;
- } else {
- let callbackFunc = function(markjs, paras) {
- let _markjs = markjs;
- let _paras = paras;
- return function() {
- _paras.textIdx += 1;
- _markjs.findByOneRegExp(_paras);
- };
- };
- opt.done = callbackFunc(this, p_paras);
- }
- // TODO: may need transformation from QRegularExpression to RegExp.
- this.markjs.markRegExp(new RegExp(p_paras.texts[p_paras.textIdx]), opt);
- }
- clearCache() {
- if (!this.markjs) {
- return;
- }
- this.cache = null;
- this.markjs.unmark();
- }
- findInCache(p_texts, p_options, p_currentMatchLine) {
- if (!this.cache) {
- return false;
- }
- if (p_texts.length != this.cache.texts.length) {
- return false;
- }
- for (let i = 0; i < p_texts.length; ++i) {
- if (!(p_texts[i] === this.cache.texts[i])) {
- return false;
- }
- }
- if (this.cache.options.caseSensitive == p_options.caseSensitive
- && this.cache.options.wholeWordOnly == p_options.wholeWordOnly
- && this.cache.options.regularExpression == p_options.regularExpression) {
- // Matched. Move current match forward or backward.
- this.updateCurrentMatch(p_texts, !p_options.findBackward, p_currentMatchLine);
- return true;
- }
- return false;
- }
- updateCurrentMatch(p_texts, p_forward, p_currentMatchLine) {
- let matches = this.matchedNodes.length;
- if (matches == 0) {
- this.adapter.showFindResult(p_texts, 0, 0);
- return;
- }
- if (this.currentMatchedNodes.length > 0) {
- console.assert(this.currentMatchedNodes.length == 1);
- if (this.cache.currentIdx >= matches
- || this.cache.currentIdx < 0
- || this.matchedNodes[this.cache.currentIdx] != this.currentMatchedNodes[0]) {
- // Need to update current index.
- // The mismatch may comes from the rendering of graphs which may change the matches.
- for (let i = 0; i < matches; ++i) {
- if (this.matchedNodes[i] === this.currentMatchedNodes[0]) {
- this.cache.currentIdx = i;
- break;
- }
- }
- }
- this.matchedNodes[this.cache.currentIdx].classList.remove(this.currentMatchClassName);
- } else {
- this.cache.currentIdx = -1;
- }
- if (p_currentMatchLine > -1) {
- this.cache.currentIdx = this.binarySearchCurrentIndexForLineNumber(p_currentMatchLine);
- } else if (p_forward) {
- this.cache.currentIdx += 1;
- if (this.cache.currentIdx >= matches) {
- this.cache.currentIdx = 0;
- }
- } else {
- this.cache.currentIdx -= 1;
- if (this.cache.currentIdx < 0) {
- this.cache.currentIdx = matches - 1;
- }
- }
- let node = this.matchedNodes[this.cache.currentIdx];
- node.classList.add(this.currentMatchClassName);
- if (!Utils.isVisible(node)) {
- node.scrollIntoView();
- }
- this.adapter.showFindResult(p_texts, matches, this.cache.currentIdx);
- }
- binarySearchCurrentIndexForLineNumber(p_lineNumber) {
- let viewY = this.adapter.nodeLineMapper.getViewYOfLine(p_lineNumber);
- if (viewY === null) {
- return 0;
- }
- let left = 0;
- let right = this.matchedNodes.length - 1;
- let lastIdx = -1;
- while (left <= right) {
- let mid = Math.floor((left + right) / 2);
- let y = this.matchedNodes[mid].getBoundingClientRect().top;
- if (y >= viewY) {
- lastIdx = mid;
- right = mid - 1;
- } else {
- left = mid + 1;
- }
- }
- if (lastIdx != -1) {
- return lastIdx;
- } else {
- return 0;
- }
- }
- }
|