mirror of
https://github.com/discourse/discourse.git
synced 2025-03-30 02:36:39 +08:00
FIX: Do not replace words in hashtags and mentions (#14760)
Watched words were replaced inside mentions and hashtags when watched word regular expressions were enabled.
This commit is contained in:
parent
cb0958fcea
commit
19ef6995a8
@ -31,6 +31,14 @@ function findAllMatches(text, matchers) {
|
|||||||
return matches.sort((a, b) => a.index - b.index);
|
return matches.sort((a, b) => a.index - b.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need this to load after mentions and hashtags which are priority 0
|
||||||
|
export const priority = 1;
|
||||||
|
|
||||||
|
const NONE = 0;
|
||||||
|
const MENTION = 1;
|
||||||
|
const HASHTAG_LINK = 2;
|
||||||
|
const HASHTAG_SPAN = 3;
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
const opts = helper.getOptions();
|
const opts = helper.getOptions();
|
||||||
|
|
||||||
@ -77,6 +85,39 @@ export function setup(helper) {
|
|||||||
|
|
||||||
let htmlLinkLevel = 0;
|
let htmlLinkLevel = 0;
|
||||||
|
|
||||||
|
// We scan once to mark tokens that must be skipped because they are
|
||||||
|
// mentions or hashtags
|
||||||
|
let lastType = NONE;
|
||||||
|
for (let i = 0; i < tokens.length; ++i) {
|
||||||
|
const currentToken = tokens[i];
|
||||||
|
|
||||||
|
if (currentToken.type === "mention_open") {
|
||||||
|
lastType = MENTION;
|
||||||
|
} else if (
|
||||||
|
(currentToken.type === "link_open" ||
|
||||||
|
currentToken.type === "span_open") &&
|
||||||
|
currentToken.attrs &&
|
||||||
|
currentToken.attrs.some(
|
||||||
|
(attr) => attr[0] === "class" && attr[1] === "hashtag"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
lastType =
|
||||||
|
currentToken.type === "link_open" ? HASHTAG_LINK : HASHTAG_SPAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastType !== NONE) {
|
||||||
|
currentToken.skipReplace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(lastType === MENTION && currentToken.type === "mention_close") ||
|
||||||
|
(lastType === HASHTAG_LINK && currentToken.type === "link_close") ||
|
||||||
|
(lastType === HASHTAG_SPAN && currentToken.type === "span_close")
|
||||||
|
) {
|
||||||
|
lastType = NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We scan from the end, to keep position when new tags added.
|
// We scan from the end, to keep position when new tags added.
|
||||||
// Use reversed logic in links start/end match
|
// Use reversed logic in links start/end match
|
||||||
for (let i = tokens.length - 1; i >= 0; i--) {
|
for (let i = tokens.length - 1; i >= 0; i--) {
|
||||||
@ -105,6 +146,11 @@ export function setup(helper) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip content of mentions or hashtags
|
||||||
|
if (currentToken.skipReplace) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentToken.type === "text") {
|
if (currentToken.type === "text") {
|
||||||
const text = currentToken.content;
|
const text = currentToken.content;
|
||||||
const matches = (cache[text] =
|
const matches = (cache[text] =
|
||||||
@ -121,14 +167,6 @@ export function setup(helper) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
matches[ln].index > 0 &&
|
|
||||||
(text[matches[ln].index - 1] === "@" ||
|
|
||||||
text[matches[ln].index - 1] === "#")
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches[ln].index > lastPos) {
|
if (matches[ln].index > lastPos) {
|
||||||
token = new state.Token("text", "", 0);
|
token = new state.Token("text", "", 0);
|
||||||
token.content = text.slice(lastPos, matches[ln].index);
|
token.content = text.slice(lastPos, matches[ln].index);
|
||||||
|
@ -1479,6 +1479,22 @@ HTML
|
|||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does not replace hashtags and mentions when watched words are regular expressions" do
|
||||||
|
SiteSetting.watched_words_regular_expressions = true
|
||||||
|
|
||||||
|
Fabricate(:user, username: "test")
|
||||||
|
category = Fabricate(:category, slug: "test")
|
||||||
|
Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "es", replacement: "discourse")
|
||||||
|
|
||||||
|
expect(PrettyText.cook("@test #test test")).to match_html(<<~HTML)
|
||||||
|
<p>
|
||||||
|
<a class="mention" href="/u/test">@test</a>
|
||||||
|
<a class="hashtag" href="/c/test/#{category.id}">#<span>test</span></a>
|
||||||
|
tdiscourset
|
||||||
|
</p>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
it "supports overlapping words" do
|
it "supports overlapping words" do
|
||||||
Fabricate(:watched_word, action: WatchedWord.actions[:link], word: "meta", replacement: "https://meta.discourse.org")
|
Fabricate(:watched_word, action: WatchedWord.actions[:link], word: "meta", replacement: "https://meta.discourse.org")
|
||||||
Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "iz", replacement: "is")
|
Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "iz", replacement: "is")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user