FEATURE: Add a site setting to allow emojis to come from an external URL (#12180)
This commit is contained in:
parent
7a53873568
commit
83f332b5a5
|
@ -23,6 +23,7 @@ import { notEmpty } from "@ember/object/computed";
|
||||||
import { setting } from "discourse/lib/computed";
|
import { setting } from "discourse/lib/computed";
|
||||||
import { userPath } from "discourse/lib/url";
|
import { userPath } from "discourse/lib/url";
|
||||||
import { helperContext } from "discourse-common/lib/helpers";
|
import { helperContext } from "discourse-common/lib/helpers";
|
||||||
|
import { emojiBasePath } from "discourse/lib/settings";
|
||||||
|
|
||||||
export default Controller.extend(
|
export default Controller.extend(
|
||||||
ModalFunctionality,
|
ModalFunctionality,
|
||||||
|
@ -84,7 +85,7 @@ export default Controller.extend(
|
||||||
|
|
||||||
// random number between 2 -6 to render multiple skin tone waving hands
|
// random number between 2 -6 to render multiple skin tone waving hands
|
||||||
const random = Math.floor(Math.random() * (7 - 2) + 2);
|
const random = Math.floor(Math.random() * (7 - 2) + 2);
|
||||||
return getURL(`/images/emoji/${emojiSet}/wave/${random}.png`);
|
return getURL(`${emojiBasePath()}/${emojiSet}/wave/${random}.png`);
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { isEmpty } from "@ember/utils";
|
||||||
import { setting } from "discourse/lib/computed";
|
import { setting } from "discourse/lib/computed";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
import { helperContext } from "discourse-common/lib/helpers";
|
import { helperContext } from "discourse-common/lib/helpers";
|
||||||
|
import { emojiBasePath } from "discourse/lib/settings";
|
||||||
|
|
||||||
// This is happening outside of the app via popup
|
// This is happening outside of the app via popup
|
||||||
const AuthErrors = [
|
const AuthErrors = [
|
||||||
|
@ -71,7 +72,7 @@ export default Controller.extend(ModalFunctionality, {
|
||||||
|
|
||||||
// random number between 2 -6 to render multiple skin tone waving hands
|
// random number between 2 -6 to render multiple skin tone waving hands
|
||||||
const random = Math.floor(Math.random() * (7 - 2) + 2);
|
const random = Math.floor(Math.random() * (7 - 2) + 2);
|
||||||
return getURL(`/images/emoji/${emojiSet}/wave/${random}.png`);
|
return getURL(`${emojiBasePath()}/${emojiSet}/wave/${random}.png`);
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("showSecondFactor", "showSecurityKey")
|
@discourseComputed("showSecondFactor", "showSecurityKey")
|
||||||
|
|
|
@ -7,3 +7,11 @@ export function prioritizeNameInUx(name) {
|
||||||
!siteSettings.prioritize_username_in_ux && name && name.trim().length > 0
|
!siteSettings.prioritize_username_in_ux && name && name.trim().length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emojiBasePath() {
|
||||||
|
let siteSettings = helperContext().siteSettings;
|
||||||
|
|
||||||
|
return siteSettings.external_emoji_url === ""
|
||||||
|
? "/images/emojis"
|
||||||
|
: siteSettings.external_emoji_url;
|
||||||
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ function emojiOptions() {
|
||||||
emojiSet: siteSettings.emoji_set,
|
emojiSet: siteSettings.emoji_set,
|
||||||
enableEmojiShortcuts: siteSettings.enable_emoji_shortcuts,
|
enableEmojiShortcuts: siteSettings.enable_emoji_shortcuts,
|
||||||
inlineEmoji: siteSettings.enable_inline_emoji_translation,
|
inlineEmoji: siteSettings.enable_inline_emoji_translation,
|
||||||
|
emojiCDNUrl: siteSettings.external_emoji_url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@ const ORIGINAL_SETTINGS = {
|
||||||
enable_personal_messages: true,
|
enable_personal_messages: true,
|
||||||
unicode_usernames: false,
|
unicode_usernames: false,
|
||||||
secure_media: false,
|
secure_media: false,
|
||||||
|
external_emoji_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let siteSettings = Object.assign({}, ORIGINAL_SETTINGS);
|
let siteSettings = Object.assign({}, ORIGINAL_SETTINGS);
|
||||||
|
|
|
@ -17,6 +17,7 @@ const rawOpts = {
|
||||||
enable_emoji_shortcuts: true,
|
enable_emoji_shortcuts: true,
|
||||||
enable_mentions: true,
|
enable_mentions: true,
|
||||||
emoji_set: "google_classic",
|
emoji_set: "google_classic",
|
||||||
|
external_emoji_url: "",
|
||||||
highlighted_languages: "json|ruby|javascript",
|
highlighted_languages: "json|ruby|javascript",
|
||||||
default_code_lang: "auto",
|
default_code_lang: "auto",
|
||||||
enable_markdown_linkify: true,
|
enable_markdown_linkify: true,
|
||||||
|
@ -1519,6 +1520,19 @@ var bar = 'bar';
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("emoji - emojiCDN", function (assert) {
|
||||||
|
assert.cookedOptions(
|
||||||
|
":smile:",
|
||||||
|
{
|
||||||
|
siteSettings: {
|
||||||
|
emoji_set: "twitter",
|
||||||
|
external_emoji_url: "https://emoji.hosting.service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`<p><img src="https://emoji.hosting.service/twitter/smile.png?v=${v}" title=":smile:" class="emoji only-emoji" alt=":smile:"></p>`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("emoji - registerEmoji", function (assert) {
|
test("emoji - registerEmoji", function (assert) {
|
||||||
registerEmoji("foo", "/images/d-logo-sketch.png");
|
registerEmoji("foo", "/images/d-logo-sketch.png");
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,12 @@ export function buildEmojiUrl(code, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const noToneMatch = code.match(/([^:]+):?/);
|
const noToneMatch = code.match(/([^:]+):?/);
|
||||||
|
|
||||||
|
let emojiBasePath = "/images/emoji";
|
||||||
|
if (opts.emojiCDNUrl) {
|
||||||
|
emojiBasePath = opts.emojiCDNUrl;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
noToneMatch &&
|
noToneMatch &&
|
||||||
!url &&
|
!url &&
|
||||||
|
@ -187,7 +193,7 @@ export function buildEmojiUrl(code, opts) {
|
||||||
aliasHash.hasOwnProperty(noToneMatch[1]))
|
aliasHash.hasOwnProperty(noToneMatch[1]))
|
||||||
) {
|
) {
|
||||||
url = opts.getURL(
|
url = opts.getURL(
|
||||||
`/images/emoji/${opts.emojiSet}/${code.replace(/:t/, "/")}.png`
|
`${emojiBasePath}/${opts.emojiSet}/${code.replace(/:t/, "/")}.png`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -322,6 +322,7 @@ export function setup(helper) {
|
||||||
opts.features.inlineEmoji = !!siteSettings.enable_inline_emoji_translation;
|
opts.features.inlineEmoji = !!siteSettings.enable_inline_emoji_translation;
|
||||||
opts.emojiSet = siteSettings.emoji_set || "";
|
opts.emojiSet = siteSettings.emoji_set || "";
|
||||||
opts.customEmoji = state.customEmoji;
|
opts.customEmoji = state.customEmoji;
|
||||||
|
opts.emojiCDNUrl = siteSettings.external_emoji_url;
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.registerPlugin((md) => {
|
helper.registerPlugin((md) => {
|
||||||
|
|
|
@ -124,6 +124,7 @@ const rule = {
|
||||||
title = performEmojiUnescape(topicInfo.title, {
|
title = performEmojiUnescape(topicInfo.title, {
|
||||||
getURL: options.getURL,
|
getURL: options.getURL,
|
||||||
emojiSet: options.emojiSet,
|
emojiSet: options.emojiSet,
|
||||||
|
emojiCDNUrl: options.emojiCDNUrl,
|
||||||
enableEmojiShortcuts: options.enableEmojiShortcuts,
|
enableEmojiShortcuts: options.enableEmojiShortcuts,
|
||||||
inlineEmoji: options.inlineEmoji,
|
inlineEmoji: options.inlineEmoji,
|
||||||
});
|
});
|
||||||
|
@ -160,6 +161,7 @@ export function setup(helper) {
|
||||||
helper.registerOptions((opts, siteSettings) => {
|
helper.registerOptions((opts, siteSettings) => {
|
||||||
opts.enableEmoji = siteSettings.enable_emoji;
|
opts.enableEmoji = siteSettings.enable_emoji;
|
||||||
opts.emojiSet = siteSettings.emoji_set;
|
opts.emojiSet = siteSettings.emoji_set;
|
||||||
|
opts.emojiCDNUrl = siteSettings.external_emoji_url;
|
||||||
opts.enableEmojiShortcuts = siteSettings.enable_emoji_shortcuts;
|
opts.enableEmojiShortcuts = siteSettings.enable_emoji_shortcuts;
|
||||||
opts.inlineEmoji = siteSettings.enable_inline_emoji_translation;
|
opts.inlineEmoji = siteSettings.enable_inline_emoji_translation;
|
||||||
});
|
});
|
||||||
|
|
|
@ -194,6 +194,7 @@ module Jobs
|
||||||
local_bases = [
|
local_bases = [
|
||||||
Discourse.base_url,
|
Discourse.base_url,
|
||||||
Discourse.asset_host,
|
Discourse.asset_host,
|
||||||
|
SiteSetting.external_emoji_url.presence
|
||||||
].compact.map { |s| normalize_src(s) }
|
].compact.map { |s| normalize_src(s) }
|
||||||
|
|
||||||
if Discourse.store.has_been_uploaded?(src) || normalize_src(src).start_with?(*local_bases) || src =~ /\A\/[^\/]/i
|
if Discourse.store.has_been_uploaded?(src) || normalize_src(src).start_with?(*local_bases) || src =~ /\A\/[^\/]/i
|
||||||
|
|
|
@ -73,7 +73,11 @@ class Emoji
|
||||||
|
|
||||||
def self.url_for(name)
|
def self.url_for(name)
|
||||||
name = name.delete_prefix(':').delete_suffix(':').gsub(/(.+):t([1-6])/, '\1/\2')
|
name = name.delete_prefix(':').delete_suffix(':').gsub(/(.+):t([1-6])/, '\1/\2')
|
||||||
"#{Discourse.base_path}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}"
|
if SiteSetting.external_emoji_url.blank?
|
||||||
|
"#{Discourse.base_path}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}"
|
||||||
|
else
|
||||||
|
"#{SiteSetting.external_emoji_url}/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.cache_key(name)
|
def self.cache_key(name)
|
||||||
|
|
|
@ -1782,6 +1782,7 @@ en:
|
||||||
|
|
||||||
external_system_avatars_enabled: "Use external system avatars service."
|
external_system_avatars_enabled: "Use external system avatars service."
|
||||||
external_system_avatars_url: "URL of the external system avatars service. Allowed substitutions are {username} {first_letter} {color} {size}"
|
external_system_avatars_url: "URL of the external system avatars service. Allowed substitutions are {username} {first_letter} {color} {size}"
|
||||||
|
external_emoji_url: "URL of the external service for emoji images. Leave blank to disable."
|
||||||
use_site_small_logo_as_system_avatar: "Use the site's small logo instead of the system user's avatar. Requires the logo to be present."
|
use_site_small_logo_as_system_avatar: "Use the site's small logo instead of the system user's avatar. Requires the logo to be present."
|
||||||
restrict_letter_avatar_colors: "A list of 6-digit hexadecimal color values to be used for letter avatar background."
|
restrict_letter_avatar_colors: "A list of 6-digit hexadecimal color values to be used for letter avatar background."
|
||||||
|
|
||||||
|
|
|
@ -1339,6 +1339,9 @@ files:
|
||||||
default: "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png"
|
default: "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png"
|
||||||
client: true
|
client: true
|
||||||
regex: '^((https?:)?\/)?\/.+[^\/]'
|
regex: '^((https?:)?\/)?\/.+[^\/]'
|
||||||
|
external_emoji_url:
|
||||||
|
default: ""
|
||||||
|
client: true
|
||||||
restrict_letter_avatar_colors:
|
restrict_letter_avatar_colors:
|
||||||
default: ""
|
default: ""
|
||||||
type: list
|
type: list
|
||||||
|
|
|
@ -238,6 +238,7 @@ module PrettyText
|
||||||
__performEmojiUnescape(#{title.inspect}, {
|
__performEmojiUnescape(#{title.inspect}, {
|
||||||
getURL: __getURL,
|
getURL: __getURL,
|
||||||
emojiSet: #{set},
|
emojiSet: #{set},
|
||||||
|
emojiCDNUrl: #{SiteSetting.external_emoji_url.blank? ? "''" : SiteSetting.external_emoji_url},
|
||||||
customEmoji: #{custom},
|
customEmoji: #{custom},
|
||||||
enableEmojiShortcuts: #{SiteSetting.enable_emoji_shortcuts},
|
enableEmojiShortcuts: #{SiteSetting.enable_emoji_shortcuts},
|
||||||
inlineEmoji: #{SiteSetting.enable_inline_emoji_translation}
|
inlineEmoji: #{SiteSetting.enable_inline_emoji_translation}
|
||||||
|
|
|
@ -151,6 +151,45 @@ describe PrettyText do
|
||||||
|
|
||||||
expect(cook(md)).to eq(html.strip)
|
expect(cook(md)).to eq(html.strip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does use emoji CDN when enabled" do
|
||||||
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
||||||
|
|
||||||
|
html = <<~HTML
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with a regular emoji <img src="https://emoji.cdn.com/twitter/upside_down_face.png?v=9" title=":upside_down_face:" class="emoji" alt=":upside_down_face:"></p>
|
||||||
|
</blockquote>
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with an emoji shortcut <img src="https://emoji.cdn.com/twitter/slight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"></p>
|
||||||
|
</blockquote>
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with a Unicode emoji <img src="https://emoji.cdn.com/twitter/sunglasses.png?v=9" title=":sunglasses:" class="emoji" alt=":sunglasses:"></p>
|
||||||
|
</blockquote>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(cook(md)).to eq(html.strip)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does use emoji CDN when others CDNs are also enabled" do
|
||||||
|
set_cdn_url('https://cdn.com')
|
||||||
|
setup_s3
|
||||||
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
||||||
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
||||||
|
|
||||||
|
html = <<~HTML
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with a regular emoji <img src="https://emoji.cdn.com/twitter/upside_down_face.png?v=9" title=":upside_down_face:" class="emoji" alt=":upside_down_face:"></p>
|
||||||
|
</blockquote>
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with an emoji shortcut <img src="https://emoji.cdn.com/twitter/slight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"></p>
|
||||||
|
</blockquote>
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote with a Unicode emoji <img src="https://emoji.cdn.com/twitter/sunglasses.png?v=9" title=":sunglasses:" class="emoji" alt=":sunglasses:"></p>
|
||||||
|
</blockquote>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(cook(md)).to eq(html.strip)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "do off topic quoting of posts from secure categories" do
|
it "do off topic quoting of posts from secure categories" do
|
||||||
|
|
|
@ -440,6 +440,23 @@ describe Jobs::PullHotlinkedImages do
|
||||||
expect(subject.should_download_image?(src)).to eq(false)
|
expect(subject.should_download_image?(src)).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns false for emoji when emoji CDN configured" do
|
||||||
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
||||||
|
|
||||||
|
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
|
||||||
|
expect(subject.should_download_image?(src)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for emoji when app, S3 *and* emoji CDNs configured" do
|
||||||
|
setup_s3
|
||||||
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
||||||
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
||||||
|
set_cdn_url "https://mydomain.cdn/test"
|
||||||
|
|
||||||
|
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
|
||||||
|
expect(subject.should_download_image?(src)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
it "returns false for plugin assets" do
|
it "returns false for plugin assets" do
|
||||||
src = UrlHelper.cook_url("/plugins/discourse-amazing-plugin/myasset.png")
|
src = UrlHelper.cook_url("/plugins/discourse-amazing-plugin/myasset.png")
|
||||||
expect(subject.should_download_image?(src)).to eq(false)
|
expect(subject.should_download_image?(src)).to eq(false)
|
||||||
|
|
Loading…
Reference in New Issue