Revert "FIX: word boundary regex (\b) not working in Unicode languages. (#9163)"
Lookbehind regex is not supported in Firefox or IE11
This reverts commit 572bb5988f
.
This commit is contained in:
parent
9521a88984
commit
d62d258fe5
|
@ -1,6 +1,5 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { on } from "discourse-common/utils/decorators";
|
import { on } from "discourse-common/utils/decorators";
|
||||||
import highlightHTML from "discourse/lib/highlight-html";
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: ["site-text"],
|
classNames: ["site-text"],
|
||||||
|
@ -11,13 +10,11 @@ export default Component.extend({
|
||||||
const term = this._searchTerm();
|
const term = this._searchTerm();
|
||||||
|
|
||||||
if (term) {
|
if (term) {
|
||||||
highlightHTML(
|
$(
|
||||||
this.element.querySelector(".site-text-id, .site-text-value"),
|
this.element.querySelector(".site-text-id, .site-text-value")
|
||||||
term,
|
).highlight(term, {
|
||||||
{
|
|
||||||
className: "text-highlight"
|
className: "text-highlight"
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
$(this.element.querySelector(".site-text-value")).ellipsis();
|
$(this.element.querySelector(".site-text-value")).ellipsis();
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<div class='fps-topic'>
|
<div class='fps-topic'>
|
||||||
<div class='topic'>
|
<div class='topic'>
|
||||||
<a class='search-link' href={{result.url}}>
|
<a class='search-link' href={{result.url}}>
|
||||||
{{topic-status topic=result.topic disableActions=true}}<span class='topic-title'>{{#highlight-search highlight=term}}{{html-safe result.topic.fancyTitle}}{{/highlight-search}}</span>
|
{{topic-status topic=result.topic disableActions=true}}<span class='topic-title'>{{#highlight-text highlight=term}}{{html-safe result.topic.fancyTitle}}{{/highlight-text}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class='search-category'>
|
<div class='search-category'>
|
||||||
|
@ -54,9 +54,9 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{{#if result.blurb}}
|
{{#if result.blurb}}
|
||||||
{{#highlight-search highlight=term}}
|
{{#highlight-text highlight=term}}
|
||||||
{{html-safe result.blurb}}
|
{{html-safe result.blurb}}
|
||||||
{{/highlight-search}}
|
{{/highlight-text}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import highlightSearch from "discourse/lib/highlight-search";
|
import highlightText from "discourse/lib/highlight-text";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: "span",
|
tagName: "span",
|
||||||
|
|
||||||
_highlightOnInsert: function() {
|
_highlightOnInsert: function() {
|
||||||
const term = this.highlight;
|
const term = this.highlight;
|
||||||
highlightSearch($(this.element), term);
|
highlightText($(this.element), term);
|
||||||
}
|
}
|
||||||
.observes("highlight")
|
.observes("highlight")
|
||||||
.on("didInsertElement")
|
.on("didInsertElement")
|
|
@ -1,93 +0,0 @@
|
||||||
function highlight(node, pattern, nodeName, className) {
|
|
||||||
if (
|
|
||||||
![Node.ELEMENT_NODE, Node.TEXT_NODE].includes(node.nodeType) ||
|
|
||||||
["SCRIPT", "STYLE"].includes(node.tagName) ||
|
|
||||||
(node.tagName === nodeName && node.className === className)
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE && node.childNodes) {
|
|
||||||
for (let i = 0; i < node.childNodes.length; i++) {
|
|
||||||
i += highlight(node.childNodes[i], pattern, nodeName, className);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
|
||||||
const match = node.data.match(pattern);
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const element = document.createElement(nodeName);
|
|
||||||
element.className = className;
|
|
||||||
element.innerText = match[0];
|
|
||||||
const matchNode = node.splitText(match.index);
|
|
||||||
matchNode.splitText(match[0].length);
|
|
||||||
matchNode.parentNode.replaceChild(element, matchNode);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function(node, words, opts = {}) {
|
|
||||||
let settings = {
|
|
||||||
nodeName: "span",
|
|
||||||
className: "highlighted",
|
|
||||||
wholeWord: false,
|
|
||||||
matchCase: false
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(settings, opts);
|
|
||||||
words = typeof words === "string" ? [words] : words;
|
|
||||||
words = words
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(word => word.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"));
|
|
||||||
|
|
||||||
if (!words.length) return node;
|
|
||||||
|
|
||||||
let pattern = `(${words.join("|")})`;
|
|
||||||
let flag;
|
|
||||||
|
|
||||||
if (settings.wholeWord) {
|
|
||||||
const hasUnicode = words.some(word => {
|
|
||||||
return !word.match(new RegExp(`\b${word}\b`));
|
|
||||||
});
|
|
||||||
pattern = hasUnicode
|
|
||||||
? `(?<=[\\s,.:;"']|^)${pattern}(?=[\\s,.:;"']|$)`
|
|
||||||
: `\b${pattern}\b`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.matchCase) {
|
|
||||||
flag = "i";
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight(
|
|
||||||
node,
|
|
||||||
new RegExp(pattern, flag),
|
|
||||||
settings.nodeName.toUpperCase(),
|
|
||||||
settings.className
|
|
||||||
);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unhighlightHTML(opts = {}) {
|
|
||||||
let settings = {
|
|
||||||
nodeName: "span",
|
|
||||||
className: "highlighted"
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(settings, opts);
|
|
||||||
|
|
||||||
document
|
|
||||||
.querySelectorAll(`${settings.nodeName}.${settings.className}`)
|
|
||||||
.forEach(e => {
|
|
||||||
const parentNode = e.parentNode;
|
|
||||||
parentNode.replaceChild(e.firstChild, e);
|
|
||||||
parentNode.normalize();
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { PHRASE_MATCH_REGEXP_PATTERN } from "discourse/lib/concerns/search-constants";
|
import { PHRASE_MATCH_REGEXP_PATTERN } from "discourse/lib/concerns/search-constants";
|
||||||
import highlightHTML from "discourse/lib/highlight-html";
|
|
||||||
|
|
||||||
export const CLASS_NAME = "search-highlight";
|
export const CLASS_NAME = "search-highlight";
|
||||||
|
|
||||||
|
@ -12,10 +11,8 @@ export default function($elem, term, opts = {}) {
|
||||||
);
|
);
|
||||||
|
|
||||||
words = words.map(w => w.replace(/^"(.*)"$/, "$1"));
|
words = words.map(w => w.replace(/^"(.*)"$/, "$1"));
|
||||||
const highlightOpts = { wholeWord: true };
|
const highlightOpts = { wordsOnly: true };
|
||||||
if (!opts.defaultClassName) highlightOpts.className = CLASS_NAME;
|
if (!opts.defaultClassName) highlightOpts.className = CLASS_NAME;
|
||||||
for (let i = 0; i <= $elem.length; i++) {
|
$elem.highlight(words, highlightOpts);
|
||||||
highlightHTML($elem[0], words, highlightOpts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
<a class='search-link' href={{result.url}} {{action "logClick" result.topic_id}}>
|
<a class='search-link' href={{result.url}} {{action "logClick" result.topic_id}}>
|
||||||
{{topic-status topic=result.topic disableActions=true showPrivateMessageIcon=true}}
|
{{topic-status topic=result.topic disableActions=true showPrivateMessageIcon=true}}
|
||||||
<span class='topic-title'>{{#highlight-search highlight=q}}{{html-safe result.topic.fancyTitle}}{{/highlight-search}}</span>
|
<span class='topic-title'>{{#highlight-text highlight=q}}{{html-safe result.topic.fancyTitle}}{{/highlight-text}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class='search-category'>
|
<div class='search-category'>
|
||||||
|
@ -112,9 +112,9 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{{#if result.blurb}}
|
{{#if result.blurb}}
|
||||||
{{#highlight-search highlight=highlightQuery}}
|
{{#highlight-text highlight=highlightQuery}}
|
||||||
{{html-safe result.blurb}}
|
{{html-safe result.blurb}}
|
||||||
{{/highlight-search}}
|
{{/highlight-text}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,7 @@ import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { isValidLink } from "discourse/lib/click-track";
|
import { isValidLink } from "discourse/lib/click-track";
|
||||||
import { number } from "discourse/lib/formatter";
|
import { number } from "discourse/lib/formatter";
|
||||||
import highlightSearch from "discourse/lib/highlight-search";
|
import highlightText from "discourse/lib/highlight-text";
|
||||||
import {
|
|
||||||
default as highlightHTML,
|
|
||||||
unhighlightHTML
|
|
||||||
} from "discourse/lib/highlight-html";
|
|
||||||
|
|
||||||
let _decorators = [];
|
let _decorators = [];
|
||||||
|
|
||||||
|
@ -56,13 +52,13 @@ export default class PostCooked {
|
||||||
|
|
||||||
if (highlight && highlight.length > 2) {
|
if (highlight && highlight.length > 2) {
|
||||||
if (this._highlighted) {
|
if (this._highlighted) {
|
||||||
unhighlightHTML($html[0]);
|
$html.unhighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightSearch($html, highlight, { defaultClassName: true });
|
highlightText($html, highlight, { defaultClassName: true });
|
||||||
this._highlighted = true;
|
this._highlighted = true;
|
||||||
} else if (this._highlighted) {
|
} else if (this._highlighted) {
|
||||||
unhighlightHTML($html[0]);
|
$html.unhighlight();
|
||||||
this._highlighted = false;
|
this._highlighted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,8 +175,10 @@ export default class PostCooked {
|
||||||
div.html(result.cooked);
|
div.html(result.cooked);
|
||||||
_decorators.forEach(cb => cb(div, this.decoratorHelper));
|
_decorators.forEach(cb => cb(div, this.decoratorHelper));
|
||||||
|
|
||||||
highlightHTML(div[0], originalText, {
|
div.highlight(originalText, {
|
||||||
matchCase: true
|
caseSensitive: true,
|
||||||
|
element: "span",
|
||||||
|
className: "highlighted"
|
||||||
});
|
});
|
||||||
$blockQuote.showHtml(div, "fast", finished);
|
$blockQuote.showHtml(div, "fast", finished);
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { dateNode } from "discourse/helpers/node";
|
||||||
import RawHtml from "discourse/widgets/raw-html";
|
import RawHtml from "discourse/widgets/raw-html";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import highlightSearch from "discourse/lib/highlight-search";
|
import highlightText from "discourse/lib/highlight-text";
|
||||||
import { escapeExpression, formatUsername } from "discourse/lib/utilities";
|
import { escapeExpression, formatUsername } from "discourse/lib/utilities";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
import renderTag from "discourse/lib/render-tag";
|
import renderTag from "discourse/lib/render-tag";
|
||||||
|
@ -15,7 +15,7 @@ class Highlighted extends RawHtml {
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate($html) {
|
decorate($html) {
|
||||||
highlightSearch($html, this.term);
|
highlightText($html, this.term);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,4 +29,5 @@
|
||||||
//= require jquery.autoellipsis-1.0.10
|
//= require jquery.autoellipsis-1.0.10
|
||||||
//= require virtual-dom
|
//= require virtual-dom
|
||||||
//= require virtual-dom-amd
|
//= require virtual-dom-amd
|
||||||
|
//= require highlight.js
|
||||||
//= require intersection-observer
|
//= require intersection-observer
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPostThumbnails < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_table :post_thumbnails do |t|
|
||||||
|
t.references :posts, foreign_key: { to_table: :posts }, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -94,7 +94,7 @@ QUnit.test("Search with context", async assert => {
|
||||||
|
|
||||||
const highlighted = [];
|
const highlighted = [];
|
||||||
|
|
||||||
find("#post_7 span.highlighted").map((_, span) => {
|
find("#post_7 span.highlight-strong").map((_, span) => {
|
||||||
highlighted.push(span.innerText);
|
highlighted.push(span.innerText);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import highlightSearch, { CLASS_NAME } from "discourse/lib/highlight-search";
|
|
||||||
import { fixture } from "helpers/qunit-helpers";
|
|
||||||
|
|
||||||
QUnit.module("lib:highlight-search");
|
|
||||||
|
|
||||||
QUnit.test("highlighting text", assert => {
|
|
||||||
fixture().html(
|
|
||||||
`
|
|
||||||
<p>This is some text to highlight</p>
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
highlightSearch(fixture(), "some text");
|
|
||||||
|
|
||||||
const terms = [];
|
|
||||||
|
|
||||||
fixture(`.${CLASS_NAME}`).each((_, elem) => {
|
|
||||||
terms.push(elem.textContent);
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
terms.join(" "),
|
|
||||||
"some text",
|
|
||||||
"it should highlight the terms correctly"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("highlighting unicode text", assert => {
|
|
||||||
fixture().html(
|
|
||||||
`
|
|
||||||
<p>This is some தமிழ் and русский text to highlight</p>
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
highlightSearch(fixture(), "தமிழ் русский");
|
|
||||||
|
|
||||||
const terms = [];
|
|
||||||
|
|
||||||
fixture(`.${CLASS_NAME}`).each((_, elem) => {
|
|
||||||
terms.push(elem.textContent);
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
terms.join(" "),
|
|
||||||
"தமிழ் русский",
|
|
||||||
"it should highlight the terms correctly"
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import highlightText, { CLASS_NAME } from "discourse/lib/highlight-text";
|
||||||
|
import { fixture } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
|
QUnit.module("lib:highlight-text");
|
||||||
|
|
||||||
|
QUnit.test("highlighting text", assert => {
|
||||||
|
fixture().html(
|
||||||
|
`
|
||||||
|
<p>This is some text to highlight</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
highlightText(fixture(), "some text");
|
||||||
|
|
||||||
|
const terms = [];
|
||||||
|
|
||||||
|
fixture(`.${CLASS_NAME}`).each((_, elem) => {
|
||||||
|
terms.push(elem.textContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
terms.join(" "),
|
||||||
|
"some text",
|
||||||
|
"it should highlight the terms correctly"
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,108 @@
|
||||||
|
// forked cause we may want to amend the logic a bit
|
||||||
|
/*
|
||||||
|
* jQuery Highlight plugin
|
||||||
|
*
|
||||||
|
* Based on highlight v3 by Johann Burkard
|
||||||
|
* http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
|
||||||
|
*
|
||||||
|
* Code a little bit refactored and cleaned (in my humble opinion).
|
||||||
|
* Most important changes:
|
||||||
|
* - has an option to highlight only entire words (wordsOnly - false by default),
|
||||||
|
* - has an option to be case sensitive (caseSensitive - false by default)
|
||||||
|
* - highlight element tag and class names can be specified in options
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* // wrap every occurrance of text 'lorem' in content
|
||||||
|
* // with <span class='highlight'> (default options)
|
||||||
|
* $('#content').highlight('lorem');
|
||||||
|
*
|
||||||
|
* // search for and highlight more terms at once
|
||||||
|
* // so you can save some time on traversing DOM
|
||||||
|
* $('#content').highlight(['lorem', 'ipsum']);
|
||||||
|
* $('#content').highlight('lorem ipsum');
|
||||||
|
*
|
||||||
|
* // search only for entire word 'lorem'
|
||||||
|
* $('#content').highlight('lorem', { wordsOnly: true });
|
||||||
|
*
|
||||||
|
* // don't ignore case during search of term 'lorem'
|
||||||
|
* $('#content').highlight('lorem', { caseSensitive: true });
|
||||||
|
*
|
||||||
|
* // wrap every occurrance of term 'ipsum' in content
|
||||||
|
* // with <em class='important'>
|
||||||
|
* $('#content').highlight('ipsum', { element: 'em', className: 'important' });
|
||||||
|
*
|
||||||
|
* // remove default highlight
|
||||||
|
* $('#content').unhighlight();
|
||||||
|
*
|
||||||
|
* // remove custom highlight
|
||||||
|
* $('#content').unhighlight({ element: 'em', className: 'important' });
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Bartek Szopka
|
||||||
|
*
|
||||||
|
* Licensed under MIT license.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
jQuery.extend({
|
||||||
|
highlight: function (node, re, nodeName, className) {
|
||||||
|
if (node.nodeType === 3) {
|
||||||
|
var match = node.data.match(re);
|
||||||
|
if (match) {
|
||||||
|
var highlight = document.createElement(nodeName || 'span');
|
||||||
|
highlight.className = className || 'highlight';
|
||||||
|
var wordNode = node.splitText(match.index);
|
||||||
|
wordNode.splitText(match[0].length);
|
||||||
|
var wordClone = wordNode.cloneNode(true);
|
||||||
|
highlight.appendChild(wordClone);
|
||||||
|
wordNode.parentNode.replaceChild(highlight, wordNode);
|
||||||
|
return 1; //skip added node in parent
|
||||||
|
}
|
||||||
|
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
|
||||||
|
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
|
||||||
|
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
|
||||||
|
for (var i = 0; i < node.childNodes.length; i++) {
|
||||||
|
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery.fn.unhighlight = function (options) {
|
||||||
|
var settings = { className: 'highlight-strong', element: 'span' };
|
||||||
|
jQuery.extend(settings, options);
|
||||||
|
|
||||||
|
return this.find(settings.element + "." + settings.className).each(function () {
|
||||||
|
var parent = this.parentNode;
|
||||||
|
parent.replaceChild(this.firstChild, this);
|
||||||
|
parent.normalize();
|
||||||
|
}).end();
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery.fn.highlight = function (words, options) {
|
||||||
|
var settings = { className: 'highlight-strong', element: 'span', caseSensitive: false, wordsOnly: false };
|
||||||
|
jQuery.extend(settings, options);
|
||||||
|
|
||||||
|
if (words.constructor === String) {
|
||||||
|
words = [words];
|
||||||
|
}
|
||||||
|
words = jQuery.grep(words, function(word){
|
||||||
|
return word !== '';
|
||||||
|
});
|
||||||
|
words = jQuery.map(words, function(word) {
|
||||||
|
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||||
|
});
|
||||||
|
if (words.length === 0) { return this; }
|
||||||
|
|
||||||
|
var flag = settings.caseSensitive ? "" : "i";
|
||||||
|
var pattern = "(" + words.join("|") + ")";
|
||||||
|
if (settings.wordsOnly) {
|
||||||
|
pattern = "\\b" + pattern + "\\b";
|
||||||
|
}
|
||||||
|
var re = new RegExp(pattern, flag);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
jQuery.highlight(this, re, settings.element, settings.className);
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue