Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: add HTML replacements #26883

Merged
merged 1 commit into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@
<span class="case-sensitive">{{i18n
"admin.watched_words.case_sensitive"
}}</span>
{{/if}}
{{#if this.isHtml}}
<span class="html">{{i18n "admin.watched_words.html"}}</span>
{{/if}}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ export default class AdminWatchedWord extends Component {
@service dialog;

@equal("actionKey", "replace") isReplace;

@equal("actionKey", "tag") isTag;

@equal("actionKey", "link") isLink;

@alias("word.case_sensitive") isCaseSensitive;
@alias("word.html") isHtml;

@discourseComputed("word.replacement")
tags(replacement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@
</label>
</div>

<div class="watched-word-input">
<label for="watched-html">{{i18n
"admin.watched_words.form.html_label"
}}</label>
<label class="html-checkbox checkbox-label">
<Input
@type="checkbox"
@checked={{this.isHtml}}
disabled={{this.formSubmitted}}
/>
{{i18n "admin.watched_words.form.html_description"}}
</label>
</div>

<DButton
@action={{this.submitForm}}
@disabled={{this.submitDisabled}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ export default class WatchedWordForm extends Component {
actionKey = null;
showMessage = false;
isCaseSensitive = false;
isHtml = false;
selectedTags = [];
words = [];

@empty("words") submitDisabled;

@equal("actionKey", "replace") canReplace;

@equal("actionKey", "tag") canTag;

@equal("actionKey", "link") canLink;

@discourseComputed("siteSettings.watched_words_regular_expressions")
Expand Down Expand Up @@ -102,6 +100,7 @@ export default class WatchedWordForm extends Component {
: null,
action: this.actionKey,
isCaseSensitive: this.isCaseSensitive,
isHtml: this.isHtml,
});

watchedWord
Expand All @@ -114,6 +113,7 @@ export default class WatchedWordForm extends Component {
showMessage: true,
message: I18n.t("admin.watched_words.form.success"),
isCaseSensitive: false,
isHtml: false,
});
if (result.words) {
result.words.forEach((word) => {
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/admin/addon/models/watched-word.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default class WatchedWord extends EmberObject {
replacement: this.replacement,
action_key: this.action,
case_sensitive: this.isCaseSensitive,
html: this.isHtml,
},
dataType: "json",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function isLinkClose(str) {
function findAllMatches(text, matchers) {
const matches = [];

for (const { word, pattern, replacement, link } of matchers) {
for (const { word, pattern, replacement, link, html } of matchers) {
if (matches.length >= MAX_MATCHES) {
break;
}
Expand All @@ -28,6 +28,7 @@ function findAllMatches(text, matchers) {
text: match[1],
replacement,
link,
html,
});

if (matches.length >= MAX_MATCHES) {
Expand Down Expand Up @@ -65,6 +66,7 @@ export function setup(helper) {
pattern: createWatchedWordRegExp(word),
replacement: options.replacement,
link: false,
html: options.html,
});
}
);
Expand Down Expand Up @@ -239,7 +241,8 @@ export function setup(helper) {
nodes.push(token);
}
} else {
token = new state.Token("text", "", 0);
let tokenType = matches[ln].html ? "html_inline" : "text";
token = new state.Token(tokenType, "", 0);
token.content = matches[ln].replacement;
token.level = level;
nodes.push(token);
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/admin/watched_words_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ def clear_all

def watched_words_params
@watched_words_params ||=
params.permit(:id, :replacement, :action_key, :case_sensitive, words: [])
params.permit(:id, :replacement, :action_key, :case_sensitive, :html, words: [])
end
end
13 changes: 8 additions & 5 deletions app/models/watched_word.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class WatchedWord < ActiveRecord::Base
validates :action, presence: true
validate :replacement_is_url, if: -> { action == WatchedWord.actions[:link] }
validate :replacement_is_tag_list, if: -> { action == WatchedWord.actions[:tag] }
validate :replacement_is_html, if: -> { replacement.present? && html? }

validates_each :word do |record, attr, val|
if WatchedWord.where(action: record.action).count >= MAX_WORDS_PER_ACTION
Expand Down Expand Up @@ -65,6 +66,7 @@ def self.create_or_update_word(params)
word.action_key = params[:action_key] if params[:action_key]
word.action = params[:action] if params[:action]
word.case_sensitive = params[:case_sensitive] if !params[:case_sensitive].nil?
word.html = params[:html] if params[:html]
word.watched_word_group_id = params[:watched_word_group_id]
word.save
word
Expand All @@ -79,11 +81,7 @@ def action_key=(arg)
end

def action_log_details
if replacement.present?
"#{word} → #{replacement}"
else
word
end
replacement.present? ? "#{word} → #{replacement}" : word
end

private
Expand All @@ -107,6 +105,10 @@ def replacement_is_tag_list
errors.add(:base, :invalid_tag_list)
end
end

def replacement_is_html
errors.add(:base, :invalid_html) if action != WatchedWord.actions[:replace]
end
end

# == Schema Information
Expand All @@ -121,6 +123,7 @@ def replacement_is_tag_list
# replacement :string
# case_sensitive :boolean default(FALSE), not null
# watched_word_group_id :bigint
# html :boolean default(FALSE), not null
#
# Indexes
#
Expand Down
13 changes: 12 additions & 1 deletion app/serializers/watched_word_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# frozen_string_literal: true

class WatchedWordSerializer < ApplicationSerializer
attributes :id, :word, :regexp, :replacement, :action, :case_sensitive, :watched_word_group_id
attributes :id,
:word,
:regexp,
:replacement,
:action,
:case_sensitive,
:watched_word_group_id,
:html

def regexp
WordWatcher.word_to_regexp(word, engine: :js)
Expand All @@ -14,4 +21,8 @@ def action
def include_replacement?
WatchedWord.has_replacement?(action)
end

def include_html?
object.action == WatchedWord.actions[:replace] && object.html
end
end
11 changes: 5 additions & 6 deletions app/services/word_watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ def self.words_for_action(action)
.where(action: WatchedWord.actions[action.to_sym])
.limit(WatchedWord::MAX_WORDS_PER_ACTION)
.order(:id)
.pluck(:word, :replacement, :case_sensitive)
.to_h do |w, r, c|
[
word_to_regexp(w, match_word: false),
{ word: w, replacement: r, case_sensitive: c }.compact,
]
.pluck(:word, :replacement, :case_sensitive, :html)
.to_h do |w, r, c, h|
opts = { word: w, replacement: r, case_sensitive: c }.compact
opts[:html] = true if h
[word_to_regexp(w, match_word: false), opts]
end
end

Expand Down
5 changes: 4 additions & 1 deletion config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4765,7 +4765,7 @@ en:
clear_filter: "Clear filter"
no_results:
title: "No results"
description: "We couldn’t find anything matching ‘%{filter}’.<br><br>Did you want to <a class=\"sidebar-additional-filter-settings\" href=\"%{settings_filter_url}\">search site settings</a> or the <a class=\"sidebar-additional-filter-users\" href=\"%{user_list_filter_url}\">admin user list?</a>"
description: 'We couldn’t find anything matching ‘%{filter}’.<br><br>Did you want to <a class="sidebar-additional-filter-settings" href="%{settings_filter_url}">search site settings</a> or the <a class="sidebar-additional-filter-users" href="%{user_list_filter_url}">admin user list?</a>'

welcome_topic_banner:
title: "Create your Welcome Topic"
Expand Down Expand Up @@ -6098,6 +6098,7 @@ en:
one: "show %{count} word"
other: "show %{count} words"
case_sensitive: "(case-sensitive)"
html: "(html)"
download: Download
clear_all: Clear All
clear_all_confirm: "Are you sure you want to clear all watched words for the %{action} action?"
Expand Down Expand Up @@ -6137,6 +6138,8 @@ en:
upload_successful: "Upload successful. Words have been added."
case_sensitivity_label: "Is case-sensitive"
case_sensitivity_description: "Only words with matching character casing"
html_label: "HTML"
html_description: "Outputs HTML in the replacement"
words_or_phrases: "words or phrases"
test:
button_label: "Test"
Expand Down
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ en:
base:
invalid_url: "Replacement URL is invalid"
invalid_tag_list: "Replacement tag list is invalid"
invalid_html: "HTML can only be used for replacement"
sidebar_section_link:
attributes:
linkable_type:
Expand Down
7 changes: 7 additions & 0 deletions db/migrate/20240506125839_add_html_to_watched_words.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddHtmlToWatchedWords < ActiveRecord::Migration[7.0]
def change
add_column :watched_words, :html, :boolean, default: false, null: false
end
end