Skip to content

Commit

Permalink
FIX: Split link watched words from replace (#13196)
Browse files Browse the repository at this point in the history
It was not clear that replace watched words can be used to replace text
with URLs. This introduces a new watched word type that makes it easier
to understand.
  • Loading branch information
nbianca committed Jun 2, 2021
1 parent eea9fea commit d9484db
Show file tree
Hide file tree
Showing 20 changed files with 188 additions and 64 deletions.
Expand Up @@ -9,6 +9,7 @@ export default Component.extend({

isReplace: equal("actionKey", "replace"),
isTag: equal("actionKey", "tag"),
isLink: equal("actionKey", "link"),

@discourseComputed("word.replacement")
tags(replacement) {
Expand Down
Expand Up @@ -15,9 +15,16 @@ export default Component.extend({
formSubmitted: false,
actionKey: null,
showMessage: false,
selectedTags: null,

canReplace: equal("actionKey", "replace"),
canTag: equal("actionKey", "tag"),
canLink: equal("actionKey", "link"),

didInsertElement() {
this._super(...arguments);
this.set("selectedTags", []);
},

@discourseComputed("siteSettings.watched_words_regular_expressions")
placeholderKey(watchedWordsRegularExpressions) {
Expand Down Expand Up @@ -47,6 +54,13 @@ export default Component.extend({
},

actions: {
changeSelectedTags(tags) {
this.setProperties({
selectedTags: tags,
replacement: tags.join(","),
});
},

submit() {
if (!this.isUniqueWord) {
this.setProperties({
Expand All @@ -61,7 +75,10 @@ export default Component.extend({

const watchedWord = WatchedWord.create({
word: this.word,
replacement: this.canReplace || this.canTag ? this.replacement : null,
replacement:
this.canReplace || this.canTag || this.canLink
? this.replacement
: null,
action: this.actionKey,
});

Expand Down
Expand Up @@ -6,23 +6,25 @@ import { equal } from "@ember/object/computed";
export default Controller.extend(ModalFunctionality, {
isReplace: equal("model.nameKey", "replace"),
isTag: equal("model.nameKey", "tag"),
isLink: equal("model.nameKey", "link"),

@discourseComputed(
"value",
"model.compiledRegularExpression",
"model.words",
"isReplace",
"isTag"
"isTag",
"isLink"
)
matches(value, regexpString, words, isReplace, isTag) {
matches(value, regexpString, words, isReplace, isTag, isLink) {
if (!value || !regexpString) {
return;
}

const regexp = new RegExp(regexpString, "ig");
const matches = value.match(regexp) || [];

if (isReplace) {
if (isReplace || isLink) {
return matches.map((match) => ({
match,
replacement: words.find((word) =>
Expand Down
@@ -1,5 +1,5 @@
{{d-icon "times"}} {{word.word}}
{{#if isReplace}}
{{#if (or isReplace isLink)}}
&rarr; <span class="replacement">{{word.replacement}}</span>
{{else if isTag}}
&rarr;
Expand Down
Expand Up @@ -5,15 +5,29 @@

{{#if canReplace}}
<div class="watched-word-input">
<label for="watched-replacement">{{i18n "admin.watched_words.form.replacement_label"}}</label>
{{text-field id="watched-replacement" value=replacement disabled=formSubmitted class="watched-word-input-field" autocorrect="off" autocapitalize="off" placeholderKey="admin.watched_words.form.replacement_placeholder"}}
<label for="watched-replacement">{{i18n "admin.watched_words.form.replace_label"}}</label>
{{text-field id="watched-replacement" value=replacement disabled=formSubmitted class="watched-word-input-field" autocorrect="off" autocapitalize="off" placeholderKey="admin.watched_words.form.replace_placeholder"}}
</div>
{{/if}}

{{#if canTag}}
<div class="watched-word-input">
<label for="watched-tag">{{i18n "admin.watched_words.form.tag_label"}}</label>
{{text-field id="watched-tag" value=replacement disabled=formSubmitted class="watched-word-input-field" autocorrect="off" autocapitalize="off" placeholderKey="admin.watched_words.form.tag_placeholder"}}
{{tag-chooser
id="watched-tag"
class="watched-word-input-field"
allowCreate=true
disabled=formSubmitted
tags=selectedTags
onChange=(action "changeSelectedTags")
}}
</div>
{{/if}}

{{#if canLink}}
<div class="watched-word-input">
<label for="watched-replacement">{{i18n "admin.watched_words.form.link_label"}}</label>
{{text-field id="watched-replacement" value=replacement disabled=formSubmitted class="watched-word-input-field" autocorrect="off" autocapitalize="off" placeholderKey="admin.watched_words.form.link_placeholder"}}
</div>
{{/if}}

Expand Down
Expand Up @@ -5,7 +5,7 @@
<p>
{{i18n "admin.watched_words.test.found_matches"}}
<ul>
{{#if isReplace}}
{{#if (or isReplace isLink)}}
{{#each matches as |match|}}
<li>
<span class="match">{{match.match}}</span>
Expand Down
Expand Up @@ -40,7 +40,7 @@
{{/if}}

{{#if showWordsList}}
<div class="watched-words-list">
<div class="watched-words-list watched-words-{{actionNameKey}}">
{{#each currentAction.words as |word| }}
<div class="watched-word-box">{{admin-watched-word actionKey=actionNameKey word=word action=(action "recordRemoved")}}</div>
{{/each}}
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/discourse/app/lib/text.js
Expand Up @@ -21,7 +21,8 @@ function getOpts(opts) {
customEmojiTranslation: context.site.custom_emoji_translation,
siteSettings: context.siteSettings,
formatUsername,
watchedWordsReplacements: context.site.watched_words_replace,
watchedWordsReplace: context.site.watched_words_replace,
watchedWordsLink: context.site.watched_words_link,
},
opts
);
Expand Down
Expand Up @@ -1675,17 +1675,29 @@ var bar = 'bar';

test("watched words replace", function (assert) {
const opts = {
watchedWordsReplacements: { fun: "times" },
watchedWordsReplace: { fun: "times" },
};

assert.cookedOptions("test fun", opts, "<p>test times</p>");
});

test("watched words link", function (assert) {
const opts = {
watchedWordsLink: { fun: "https://discourse.org" },
};

assert.cookedOptions(
"test fun",
opts,
'<p>test <a href="https://discourse.org">fun</a></p>'
);
});

test("watched words replace with bad regex", function (assert) {
const maxMatches = 100; // same limit as MD watched-words-replace plugin
const opts = {
siteSettings: { watched_words_regular_expressions: true },
watchedWordsReplacements: { "\\bu?\\b": "you" },
watchedWordsReplace: { "\\bu?\\b": "you" },
};

assert.cookedOptions(
Expand Down
6 changes: 4 additions & 2 deletions app/assets/javascripts/pretty-text/addon/pretty-text.js
Expand Up @@ -33,7 +33,8 @@ export function buildOptions(state) {
censoredRegexp,
disableEmojis,
customEmojiTranslation,
watchedWordsReplacements,
watchedWordsReplace,
watchedWordsLink,
} = state;

let features = {
Expand Down Expand Up @@ -83,7 +84,8 @@ export function buildOptions(state) {
siteSettings.enable_advanced_editor_preview_sync,
previewing,
disableEmojis,
watchedWordsReplacements,
watchedWordsReplace,
watchedWordsLink,
};

// note, this will mutate options due to the way the API is designed
Expand Down
Expand Up @@ -23,6 +23,7 @@ function findAllMatches(text, matchers) {
index: match.index,
text: match[0],
replacement: matcher.replacement,
link: matcher.link,
});
}
});
Expand All @@ -32,19 +33,39 @@ function findAllMatches(text, matchers) {

export function setup(helper) {
helper.registerPlugin((md) => {
const replacements = md.options.discourse.watchedWordsReplacements;
if (!replacements) {
return;
const matchers = [];

if (md.options.discourse.watchedWordsReplace) {
Object.entries(md.options.discourse.watchedWordsReplace).map(
([word, replacement]) => {
matchers.push({
pattern: new RegExp(word, "gi"),
replacement,
link: false,
});
}
);
}

const matchers = Object.keys(replacements).map((word) => ({
pattern: new RegExp(word, "gi"),
replacement: replacements[word],
}));
if (md.options.discourse.watchedWordsLink) {
Object.entries(md.options.discourse.watchedWordsLink).map(
([word, replacement]) => {
matchers.push({
pattern: new RegExp(word, "gi"),
replacement,
link: true,
});
}
);
}

if (matchers.length === 0) {
return;
}

const cache = {};

md.core.ruler.push("watched-words-replace", (state) => {
md.core.ruler.push("watched-words", (state) => {
for (let j = 0, l = state.tokens.length; j < l; j++) {
if (state.tokens[j].type !== "inline") {
continue;
Expand Down Expand Up @@ -82,10 +103,6 @@ export function setup(helper) {
}
}

if (htmlLinkLevel > 0) {
continue;
}

if (currentToken.type === "text") {
const text = currentToken.content;
const matches = (cache[text] =
Expand All @@ -109,25 +126,27 @@ export function setup(helper) {
nodes.push(token);
}

let url = state.md.normalizeLink(matches[ln].replacement);
if (state.md.validateLink(url) && /^https?/.test(url)) {
token = new state.Token("link_open", "a", 1);
token.attrs = [["href", url]];
token.level = level++;
token.markup = "linkify";
token.info = "auto";
nodes.push(token);

token = new state.Token("text", "", 0);
token.content = matches[ln].text;
token.level = level;
nodes.push(token);

token = new state.Token("link_close", "a", -1);
token.level = --level;
token.markup = "linkify";
token.info = "auto";
nodes.push(token);
if (matches[ln].link) {
const url = state.md.normalizeLink(matches[ln].replacement);
if (htmlLinkLevel === 0 && state.md.validateLink(url)) {
token = new state.Token("link_open", "a", 1);
token.attrs = [["href", url]];
token.level = level++;
token.markup = "linkify";
token.info = "auto";
nodes.push(token);

token = new state.Token("text", "", 0);
token.content = matches[ln].text;
token.level = level;
nodes.push(token);

token = new state.Token("link_close", "a", -1);
token.level = --level;
token.markup = "linkify";
token.info = "auto";
nodes.push(token);
}
} else {
token = new state.Token("text", "", 0);
token.content = matches[ln].replacement;
Expand Down
17 changes: 17 additions & 0 deletions app/assets/stylesheets/common/admin/staff_logs.scss
Expand Up @@ -332,6 +332,19 @@ table.screened-ip-addresses {
vertical-align: top;
}

.watched-words-link {
.watched-word-box {
min-width: 100%;
}
}

.watched-words-replace,
.watched-words-tag {
.watched-word-box {
min-width: calc(50% - 5px);
}
}

.watched-word-box,
.watched-words-test-modal {
.replacement {
Expand All @@ -354,6 +367,7 @@ table.screened-ip-addresses {
.watched-words-list {
margin-top: 20px;
display: inline-block;
width: 100%;
}

.watched-word {
Expand Down Expand Up @@ -396,6 +410,9 @@ table.screened-ip-addresses {
input.watched-word-input-field {
min-width: 300px;
}
.select-kit.multi-select.watched-word-input-field {
width: 300px;
}
}

// Search logs
Expand Down

0 comments on commit d9484db

Please sign in to comment.