@ckeditor/ckeditor5-ui
Version:
The UI framework and standard UI library of CKEditor 5.
103 lines (102 loc) • 3.47 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module ui/highlightedtext/highlightedtextview
*/
import View from '../view.js';
import { escape } from 'es-toolkit/compat';
import '../../theme/components/highlightedtext/highlightedtext.css';
/**
* A class representing a view that displays a text which subset can be highlighted using the
* {@link #highlightText} method.
*/
export default class HighlightedTextView extends View {
/**
* @inheritDoc
*/
constructor() {
super();
this.set('text', undefined);
this.setTemplate({
tag: 'span',
attributes: {
class: ['ck', 'ck-highlighted-text']
}
});
this.on('render', () => {
// Classic setTemplate binding for #text will not work because highlightText() replaces the
// pre-rendered DOM text node new a new one (and <mark> elements).
this.on('change:text', () => {
this._updateInnerHTML(this.text);
});
this._updateInnerHTML(this.text);
});
}
/**
* Highlights view's {@link #text} according to the specified `RegExp`. If the passed RegExp is `null`, the
* highlighting is removed
*
* @param regExp
*/
highlightText(regExp) {
this._updateInnerHTML(markText(this.text || '', regExp));
}
/**
* Updates element's `innerHTML` with the passed content.
*/
_updateInnerHTML(newInnerHTML) {
this.element.innerHTML = newInnerHTML || '';
}
}
/**
* Replaces `regExp` occurrences with `<mark>` tags in a text.
*
* @param text A text to get marked.
* @param regExp An optional `RegExp`. If not passed, this is a pass-through function.
* @returns A text with `RegExp` occurrences marked by `<mark>`.
*/
function markText(text, regExp) {
if (!regExp) {
return escape(text);
}
const textParts = [];
let lastMatchEnd = 0;
let matchInfo = regExp.exec(text);
// Iterate over all matches and create an array of text parts. The idea is to mark which parts are query matches
// so that later on they can be highlighted.
while (matchInfo !== null) {
const curMatchStart = matchInfo.index;
// Detect if there was something between last match and this one.
if (curMatchStart !== lastMatchEnd) {
textParts.push({
text: text.substring(lastMatchEnd, curMatchStart),
isMatch: false
});
}
textParts.push({
text: matchInfo[0],
isMatch: true
});
lastMatchEnd = regExp.lastIndex;
matchInfo = regExp.exec(text);
}
// Your match might not be the last part of a string. Be sure to add any plain text following the last match.
if (lastMatchEnd !== text.length) {
textParts.push({
text: text.substring(lastMatchEnd),
isMatch: false
});
}
const outputHtml = textParts
// The entire text should be escaped.
.map(part => {
part.text = escape(part.text);
return part;
})
// Only matched text should be wrapped with HTML mark element.
.map(part => part.isMatch ? `<mark>${part.text}</mark>` : part.text)
.join('');
return outputHtml;
}