mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
DEV: Move avatar-utils into dedicated discourse-common module (#22517)
These avatar-related helper functions are used in pretty-text, which currently means we load the entire `discourse/lib/utilities` module into the mini-racer when running pretty-text on the server side. This stops us adding any logic or imports to discourse/lib/utilities which may depend on other `discourse/` namespace features. This commit moves the avatar-related utils into a dedicated module in the `discourse-common` namespace, adds backwards-compatibility shims, and updates the pretty-text config accordingly.
This commit is contained in:
parent
aca0bf69ef
commit
2fde58def4
|
@ -0,0 +1,86 @@
|
|||
import { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||
import { helperContext } from "discourse-common/lib/helpers";
|
||||
import { escape } from "pretty-text/sanitizer";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
|
||||
let allowedSizes = null;
|
||||
|
||||
export function translateSize(size) {
|
||||
switch (size) {
|
||||
case "tiny":
|
||||
return 24;
|
||||
case "small":
|
||||
return 24;
|
||||
case "medium":
|
||||
return 48;
|
||||
case "large":
|
||||
return 48;
|
||||
case "extra_large":
|
||||
return 96;
|
||||
case "huge":
|
||||
return 144;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
export function getRawSize(size) {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
let rawSize = 1;
|
||||
if (pixelRatio > 1.1 && pixelRatio < 2.1) {
|
||||
rawSize = 2;
|
||||
} else if (pixelRatio >= 2.1) {
|
||||
rawSize = 3;
|
||||
}
|
||||
return size * rawSize;
|
||||
}
|
||||
|
||||
export function getRawAvatarSize(size) {
|
||||
allowedSizes ??= helperContext()
|
||||
.siteSettings["avatar_sizes"].split("|")
|
||||
.map((s) => parseInt(s, 10))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
size = getRawSize(size);
|
||||
|
||||
for (let i = 0; i < allowedSizes.length; i++) {
|
||||
if (allowedSizes[i] >= size) {
|
||||
return allowedSizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return allowedSizes[allowedSizes.length - 1];
|
||||
}
|
||||
|
||||
export function avatarUrl(template, size, { customGetURL } = {}) {
|
||||
if (!template) {
|
||||
return "";
|
||||
}
|
||||
const rawSize = getRawAvatarSize(translateSize(size));
|
||||
const templatedPath = template.replace(/\{size\}/g, rawSize);
|
||||
return (customGetURL || getURLWithCDN)(templatedPath);
|
||||
}
|
||||
|
||||
export function avatarImg(options, customGetURL) {
|
||||
const size = translateSize(options.size);
|
||||
let url = avatarUrl(options.avatarTemplate, size, { customGetURL });
|
||||
|
||||
// We won't render an invalid url
|
||||
if (!url) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const classes =
|
||||
"avatar" + (options.extraClasses ? " " + options.extraClasses : "");
|
||||
|
||||
let title = "";
|
||||
if (options.title) {
|
||||
const escaped = escape(options.title || "");
|
||||
title = ` title='${escaped}' aria-label='${escaped}'`;
|
||||
}
|
||||
|
||||
return `<img loading='lazy' alt='' width='${size}' height='${size}' src='${url}' class='${classes}'${title}>`;
|
||||
}
|
||||
|
||||
export function tinyAvatar(avatarTemplate, options) {
|
||||
return avatarImg(deepMerge({ avatarTemplate, size: "tiny" }, options));
|
||||
}
|
|
@ -6,8 +6,8 @@ import {
|
|||
caretPosition,
|
||||
formatUsername,
|
||||
inCodeBlock,
|
||||
tinyAvatar,
|
||||
} from "discourse/lib/utilities";
|
||||
import { tinyAvatar } from "discourse-common/lib/avatar-utils";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
debounce,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { avatarImg } from "discourse/lib/utilities";
|
||||
import { avatarImg } from "discourse-common/lib/avatar-utils";
|
||||
import { htmlHelper } from "discourse-common/lib/helpers";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { addExtraUserClasses } from "discourse/helpers/user-avatar";
|
||||
import { avatarImg } from "discourse/lib/utilities";
|
||||
import { avatarImg } from "discourse-common/lib/avatar-utils";
|
||||
import { get } from "@ember/object";
|
||||
import { htmlHelper } from "discourse-common/lib/helpers";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { avatarImg, formatUsername } from "discourse/lib/utilities";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { avatarImg } from "discourse-common/lib/avatar-utils";
|
||||
import I18n from "I18n";
|
||||
import { get } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import Handlebars from "handlebars";
|
||||
import I18n from "I18n";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { escape } from "pretty-text/sanitizer";
|
||||
import { helperContext } from "discourse-common/lib/helpers";
|
||||
import toMarkdown from "discourse/lib/to-markdown";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import * as AvatarUtils from "discourse-common/lib/avatar-utils";
|
||||
|
||||
let _defaultHomepage;
|
||||
|
||||
function deprecatedAvatarUtil(name) {
|
||||
return function () {
|
||||
deprecated(
|
||||
`${name} should be imported from discourse-common/lib/avatar-utils instead of discourse/lib/utilities`,
|
||||
{ id: "discourse.avatar-utils" }
|
||||
);
|
||||
return AvatarUtils[name](...arguments);
|
||||
};
|
||||
}
|
||||
|
||||
export const translateSize = deprecatedAvatarUtil("translateSize");
|
||||
export const getRawSize = deprecatedAvatarUtil("getRawSize");
|
||||
export const getRawAvatarSize = deprecatedAvatarUtil("getRawAvatarSize");
|
||||
export const avatarUrl = deprecatedAvatarUtil("avatarUrl");
|
||||
export const avatarImg = deprecatedAvatarUtil("avatarImg");
|
||||
export const tinyAvatar = deprecatedAvatarUtil("tinyAvatar");
|
||||
|
||||
export function splitString(str, separator = ",") {
|
||||
if (typeof str === "string") {
|
||||
return str.split(separator).filter(Boolean);
|
||||
|
@ -17,24 +34,6 @@ export function splitString(str, separator = ",") {
|
|||
}
|
||||
}
|
||||
|
||||
export function translateSize(size) {
|
||||
switch (size) {
|
||||
case "tiny":
|
||||
return 24;
|
||||
case "small":
|
||||
return 24;
|
||||
case "medium":
|
||||
return 48;
|
||||
case "large":
|
||||
return 48;
|
||||
case "extra_large":
|
||||
return 96;
|
||||
case "huge":
|
||||
return 144;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
export function escapeExpression(string) {
|
||||
if (!string) {
|
||||
return "";
|
||||
|
@ -58,70 +57,6 @@ export function replaceFormatter(fn) {
|
|||
_usernameFormatDelegate = fn;
|
||||
}
|
||||
|
||||
export function avatarUrl(template, size, { customGetURL } = {}) {
|
||||
if (!template) {
|
||||
return "";
|
||||
}
|
||||
const rawSize = getRawAvatarSize(translateSize(size));
|
||||
const templatedPath = template.replace(/\{size\}/g, rawSize);
|
||||
return (customGetURL || getURLWithCDN)(templatedPath);
|
||||
}
|
||||
|
||||
let allowedSizes = null;
|
||||
|
||||
export function getRawAvatarSize(size) {
|
||||
allowedSizes ??= helperContext()
|
||||
.siteSettings["avatar_sizes"].split("|")
|
||||
.map((s) => parseInt(s, 10))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
size = getRawSize(size);
|
||||
|
||||
for (let i = 0; i < allowedSizes.length; i++) {
|
||||
if (allowedSizes[i] >= size) {
|
||||
return allowedSizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return allowedSizes[allowedSizes.length - 1];
|
||||
}
|
||||
|
||||
export function getRawSize(size) {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
let rawSize = 1;
|
||||
if (pixelRatio > 1.1 && pixelRatio < 2.1) {
|
||||
rawSize = 2;
|
||||
} else if (pixelRatio >= 2.1) {
|
||||
rawSize = 3;
|
||||
}
|
||||
return size * rawSize;
|
||||
}
|
||||
|
||||
export function avatarImg(options, customGetURL) {
|
||||
const size = translateSize(options.size);
|
||||
let url = avatarUrl(options.avatarTemplate, size, { customGetURL });
|
||||
|
||||
// We won't render an invalid url
|
||||
if (!url) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const classes =
|
||||
"avatar" + (options.extraClasses ? " " + options.extraClasses : "");
|
||||
|
||||
let title = "";
|
||||
if (options.title) {
|
||||
const escaped = escapeExpression(options.title || "");
|
||||
title = ` title='${escaped}' aria-label='${escaped}'`;
|
||||
}
|
||||
|
||||
return `<img loading='lazy' alt='' width='${size}' height='${size}' src='${url}' class='${classes}'${title}>`;
|
||||
}
|
||||
|
||||
export function tinyAvatar(avatarTemplate, options) {
|
||||
return avatarImg(deepMerge({ avatarTemplate, size: "tiny" }, options));
|
||||
}
|
||||
|
||||
export function postUrl(slug, topicId, postNumber) {
|
||||
let url = getURL("/t/");
|
||||
if (slug) {
|
||||
|
@ -659,6 +594,3 @@ export function mergeSortedLists(list1, list2, comparator) {
|
|||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
// This prevents a mini racer crash
|
||||
export default {};
|
||||
|
|
|
@ -5,11 +5,8 @@ import discourseComputed, {
|
|||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import {
|
||||
emailValid,
|
||||
escapeExpression,
|
||||
tinyAvatar,
|
||||
} from "discourse/lib/utilities";
|
||||
import { emailValid, escapeExpression } from "discourse/lib/utilities";
|
||||
import { tinyAvatar } from "discourse-common/lib/avatar-utils";
|
||||
import Draft from "discourse/models/draft";
|
||||
import I18n from "I18n";
|
||||
import { Promise } from "rsvp";
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
||||
import {
|
||||
avatarUrl,
|
||||
formatUsername,
|
||||
translateSize,
|
||||
} from "discourse/lib/utilities";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { avatarUrl, translateSize } from "discourse-common/lib/avatar-utils";
|
||||
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||
import DecoratorHelper from "discourse/widgets/decorator-helper";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import {
|
||||
avatarImg,
|
||||
avatarUrl,
|
||||
getRawAvatarSize,
|
||||
} from "discourse-common/lib/avatar-utils";
|
||||
import { module, test } from "qunit";
|
||||
import { setupURL } from "discourse-common/lib/get-url";
|
||||
import { setupTest } from "ember-qunit";
|
||||
|
||||
module("Unit | Utilities", function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test("getRawAvatarSize avoids redirects", function (assert) {
|
||||
assert.strictEqual(
|
||||
getRawAvatarSize(1),
|
||||
24,
|
||||
"returns the first size larger on the menu"
|
||||
);
|
||||
|
||||
assert.strictEqual(getRawAvatarSize(2000), 288, "caps at highest");
|
||||
});
|
||||
|
||||
test("avatarUrl", function (assert) {
|
||||
assert.blank(avatarUrl("", "tiny"), "no template returns blank");
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "tiny"),
|
||||
"/fake/template/" + getRawAvatarSize(24) + ".png",
|
||||
"simple avatar url"
|
||||
);
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"/fake/template/" + getRawAvatarSize(48) + ".png",
|
||||
"different size"
|
||||
);
|
||||
|
||||
setupURL("https://app-cdn.example.com", "https://example.com", "");
|
||||
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"https://app-cdn.example.com/fake/template/" +
|
||||
getRawAvatarSize(48) +
|
||||
".png",
|
||||
"uses CDN if present"
|
||||
);
|
||||
});
|
||||
|
||||
let setDevicePixelRatio = function (value) {
|
||||
if (Object.defineProperty && !window.hasOwnProperty("devicePixelRatio")) {
|
||||
Object.defineProperty(window, "devicePixelRatio", { value: 2 });
|
||||
} else {
|
||||
window.devicePixelRatio = value;
|
||||
}
|
||||
};
|
||||
|
||||
test("avatarImg", function (assert) {
|
||||
let oldRatio = window.devicePixelRatio;
|
||||
setDevicePixelRatio(2);
|
||||
|
||||
let avatarTemplate = "/path/to/avatar/{size}.png";
|
||||
assert.strictEqual(
|
||||
avatarImg({ avatarTemplate, size: "tiny" }),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar'>",
|
||||
"it returns the avatar html"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
avatarImg({
|
||||
avatarTemplate,
|
||||
size: "tiny",
|
||||
title: "evilest trout",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
|
||||
"it adds a title if supplied"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
avatarImg({
|
||||
avatarTemplate,
|
||||
size: "tiny",
|
||||
extraClasses: "evil fish",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar evil fish'>",
|
||||
"it adds extra classes if supplied"
|
||||
);
|
||||
|
||||
assert.blank(
|
||||
avatarImg({ avatarTemplate: "", size: "tiny" }),
|
||||
"it doesn't render avatars for invalid avatar template"
|
||||
);
|
||||
|
||||
setDevicePixelRatio(oldRatio);
|
||||
});
|
||||
});
|
|
@ -1,6 +1,4 @@
|
|||
import {
|
||||
avatarImg,
|
||||
avatarUrl,
|
||||
caretRowCol,
|
||||
clipboardCopyAsync,
|
||||
defaultHomepage,
|
||||
|
@ -8,7 +6,6 @@ import {
|
|||
escapeExpression,
|
||||
extractDomainFromUrl,
|
||||
fillMissingDates,
|
||||
getRawAvatarSize,
|
||||
inCodeBlock,
|
||||
initializeDefaultHomepage,
|
||||
mergeSortedLists,
|
||||
|
@ -25,7 +22,6 @@ import { chromeTest } from "discourse/tests/helpers/qunit-helpers";
|
|||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { click, render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { setupURL } from "discourse-common/lib/get-url";
|
||||
import { setupTest } from "ember-qunit";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
|
@ -86,87 +82,6 @@ module("Unit | Utilities", function (hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
test("getRawAvatarSize avoids redirects", function (assert) {
|
||||
assert.strictEqual(
|
||||
getRawAvatarSize(1),
|
||||
24,
|
||||
"returns the first size larger on the menu"
|
||||
);
|
||||
|
||||
assert.strictEqual(getRawAvatarSize(2000), 288, "caps at highest");
|
||||
});
|
||||
|
||||
test("avatarUrl", function (assert) {
|
||||
assert.blank(avatarUrl("", "tiny"), "no template returns blank");
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "tiny"),
|
||||
"/fake/template/" + getRawAvatarSize(24) + ".png",
|
||||
"simple avatar url"
|
||||
);
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"/fake/template/" + getRawAvatarSize(48) + ".png",
|
||||
"different size"
|
||||
);
|
||||
|
||||
setupURL("https://app-cdn.example.com", "https://example.com", "");
|
||||
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"https://app-cdn.example.com/fake/template/" +
|
||||
getRawAvatarSize(48) +
|
||||
".png",
|
||||
"uses CDN if present"
|
||||
);
|
||||
});
|
||||
|
||||
let setDevicePixelRatio = function (value) {
|
||||
if (Object.defineProperty && !window.hasOwnProperty("devicePixelRatio")) {
|
||||
Object.defineProperty(window, "devicePixelRatio", { value: 2 });
|
||||
} else {
|
||||
window.devicePixelRatio = value;
|
||||
}
|
||||
};
|
||||
|
||||
test("avatarImg", function (assert) {
|
||||
let oldRatio = window.devicePixelRatio;
|
||||
setDevicePixelRatio(2);
|
||||
|
||||
let avatarTemplate = "/path/to/avatar/{size}.png";
|
||||
assert.strictEqual(
|
||||
avatarImg({ avatarTemplate, size: "tiny" }),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar'>",
|
||||
"it returns the avatar html"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
avatarImg({
|
||||
avatarTemplate,
|
||||
size: "tiny",
|
||||
title: "evilest trout",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
|
||||
"it adds a title if supplied"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
avatarImg({
|
||||
avatarTemplate,
|
||||
size: "tiny",
|
||||
extraClasses: "evil fish",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar evil fish'>",
|
||||
"it adds extra classes if supplied"
|
||||
);
|
||||
|
||||
assert.blank(
|
||||
avatarImg({ avatarTemplate: "", size: "tiny" }),
|
||||
"it doesn't render avatars for invalid avatar template"
|
||||
);
|
||||
|
||||
setDevicePixelRatio(oldRatio);
|
||||
});
|
||||
|
||||
test("defaultHomepage via meta tag", function (assert) {
|
||||
let meta = document.createElement("meta");
|
||||
meta.name = "discourse_current_homepage";
|
||||
|
|
|
@ -104,9 +104,9 @@ module PrettyText
|
|||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/object")
|
||||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/deprecated")
|
||||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/escape")
|
||||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/avatar-utils")
|
||||
apply_es6_file(ctx, root_path, "discourse-common/addon/utils/watched-words")
|
||||
apply_es6_file(ctx, root_path, "discourse/app/lib/to-markdown")
|
||||
apply_es6_file(ctx, root_path, "discourse/app/lib/utilities")
|
||||
|
||||
ctx.load("#{Rails.root}/lib/pretty_text/shims.js")
|
||||
ctx.eval("__setUnicode(#{Emoji.unicode_replacements_json})")
|
||||
|
@ -260,7 +260,7 @@ module PrettyText
|
|||
__optInput = {};
|
||||
__optInput.avatar_sizes = #{SiteSetting.avatar_sizes.to_json};
|
||||
__paths = #{paths_json};
|
||||
__utils.avatarImg({size: #{size.inspect}, avatarTemplate: #{avatar_template.inspect}}, __getURL);
|
||||
require("discourse-common/lib/avatar-utils").avatarImg({size: #{size.inspect}, avatarTemplate: #{avatar_template.inspect}}, __getURL);
|
||||
JS
|
||||
end
|
||||
|
||||
|
|
|
@ -24,8 +24,6 @@ define("discourse-common/lib/helpers", ["exports"], function (exports) {
|
|||
};
|
||||
});
|
||||
|
||||
__utils = require("discourse/lib/utilities");
|
||||
|
||||
__emojiUnicodeReplacer = null;
|
||||
|
||||
__setUnicode = function (replacements) {
|
||||
|
@ -119,7 +117,7 @@ function __hashtagLookup(slug, cookingUserId, typesInPriorityOrder) {
|
|||
}
|
||||
|
||||
function __lookupAvatar(p) {
|
||||
return __utils.avatarImg(
|
||||
return require("discourse-common/lib/avatar-utils").avatarImg(
|
||||
{ size: "tiny", avatarTemplate: __helpers.avatar_template(p) },
|
||||
__getURL
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user