From a0dd75ba887e9bb89af19bb87a08a8e67bf2436b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 30 Oct 2017 12:40:58 -0400 Subject: [PATCH] FEATURE: New API to create a custom formatter for displaying usernames This is not exhaustive right now, but a good start and we can add to it over time. --- .../discourse/helpers/format-username.js.es6 | 4 ++++ .../discourse/lib/link-mentions.js.es6 | 3 ++- .../discourse/lib/plugin-api.js.es6 | 23 +++++++++++++++++-- .../javascripts/discourse/lib/text.js.es6 | 4 +++- .../discourse/lib/utilities.js.es6 | 11 +++++++++ .../components/user-card-contents.hbs | 2 +- .../javascripts/discourse/templates/user.hbs | 2 +- .../widgets/notification-item.js.es6 | 6 ++--- .../discourse/widgets/poster-name.js.es6 | 10 ++++---- .../discourse/widgets/user-menu.js.es6 | 3 ++- .../pretty-text/pretty-text.js.es6 | 5 ++-- 11 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 app/assets/javascripts/discourse/helpers/format-username.js.es6 diff --git a/app/assets/javascripts/discourse/helpers/format-username.js.es6 b/app/assets/javascripts/discourse/helpers/format-username.js.es6 new file mode 100644 index 00000000000..dcb8be1840a --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/format-username.js.es6 @@ -0,0 +1,4 @@ +import { registerUnbound } from 'discourse-common/lib/helpers'; +import { formatUsername } from 'discourse/lib/utilities'; + +export default registerUnbound('format-username', formatUsername); diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 index d8cd9702993..688207c5b95 100644 --- a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 @@ -1,5 +1,6 @@ import { ajax } from 'discourse/lib/ajax'; import { userPath } from 'discourse/lib/url'; +import { formatUsername } from 'discourse/lib/utilities'; function replaceSpan($e, username, opts) { let extra = ""; @@ -16,7 +17,7 @@ function replaceSpan($e, username, opts) { extra = `data-name='${username}'`; extraClass = "cannot-see"; } - $e.replaceWith(`@${username}`); + $e.replaceWith(`@${formatUsername(username)}`); } } diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 38772203aee..03467421cb9 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -20,10 +20,10 @@ import { addPostTransformCallback } from 'discourse/widgets/post-stream'; import { attachAdditionalPanel } from 'discourse/widgets/header'; import { registerIconRenderer, replaceIcon } from 'discourse-common/lib/icon-library'; import { addNavItem } from 'discourse/models/nav-item'; - +import { replaceFormatter } from 'discourse/lib/utilities'; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = '0.8.11'; +const PLUGIN_API_VERSION = '0.8.12'; class PluginApi { constructor(version, container) { @@ -570,6 +570,25 @@ class PluginApi { addNavItem(item); } } + + + /** + * + * Registers a function that will format a username when displayed. This will not + * be applied when the username is used as an `id` or in URL strings. + * + * Example: + * + * ``` + * // display usernames in UPPER CASE + * api.formatUsername(username => username.toUpperCase()); + * + * ``` + * + **/ + formatUsername(fn) { + replaceFormatter(fn); + } } let _pluginv01; diff --git a/app/assets/javascripts/discourse/lib/text.js.es6 b/app/assets/javascripts/discourse/lib/text.js.es6 index 874f519010b..09a7592828c 100644 --- a/app/assets/javascripts/discourse/lib/text.js.es6 +++ b/app/assets/javascripts/discourse/lib/text.js.es6 @@ -3,6 +3,7 @@ import { performEmojiUnescape, buildEmojiUrl } from 'pretty-text/emoji'; import WhiteLister from 'pretty-text/white-lister'; import { sanitize as textSanitize } from 'pretty-text/sanitizer'; import loadScript from 'discourse/lib/load-script'; +import { formatUsername } from 'discourse/lib/utilities'; function getOpts(opts) { const siteSettings = Discourse.__container__.lookup('site-settings:main'), @@ -12,7 +13,8 @@ function getOpts(opts) { getURL: Discourse.getURLWithCDN, currentUser: Discourse.__container__.lookup('current-user:main'), censoredWords: site.censored_words, - siteSettings + siteSettings, + formatUsername }, opts); return buildOptions(opts); diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index a1e55234e27..fe3616abb54 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -21,6 +21,17 @@ export function escapeExpression(string) { return escape(string); } +let _usernameFormatDelegate = username => username; + +export function formatUsername(username) { + return _usernameFormatDelegate(username || ''); +} + +export function replaceFormatter(fn) { + _usernameFormatDelegate = fn; +} + + export function avatarUrl(template, size) { if (!template) { return ""; } const rawSize = getRawSize(translateSize(size)); diff --git a/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs b/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs index 07c4effa3e5..55190fb27f6 100644 --- a/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs @@ -16,7 +16,7 @@

- {{if nameFirst user.name username}} {{user-status user currentUser=currentUser}} + {{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}}

{{#unless nameFirst}} diff --git a/app/assets/javascripts/discourse/templates/user.hbs b/app/assets/javascripts/discourse/templates/user.hbs index 17677dd3d18..9307c321028 100644 --- a/app/assets/javascripts/discourse/templates/user.hbs +++ b/app/assets/javascripts/discourse/templates/user.hbs @@ -61,7 +61,7 @@
-

{{if nameFirst model.name model.username}} {{user-status model currentUser=currentUser}}

+

{{if nameFirst model.name (format-username model.username)}} {{user-status model currentUser=currentUser}}

{{#if nameFirst}}{{model.username}}{{else}}{{model.name}}{{/if}}

{{#if model.title}}

{{model.title}}

diff --git a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 index f2878b5f126..83f0c368fab 100644 --- a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 @@ -4,7 +4,7 @@ import { createWidget } from 'discourse/widgets/widget'; import DiscourseURL from 'discourse/lib/url'; import { h } from 'virtual-dom'; import { emojiUnescape } from 'discourse/lib/text'; -import { postUrl, escapeExpression } from 'discourse/lib/utilities'; +import { postUrl, escapeExpression, formatUsername } from 'discourse/lib/utilities'; import { setTransientHeader } from 'discourse/lib/ajax'; import { userPath } from 'discourse/lib/url'; import { iconNode } from 'discourse-common/lib/icon-library'; @@ -79,11 +79,11 @@ createWidget('notification-item', { return I18n.t(scope, { count, group_name }); } - const username = data.display_username; + const username = formatUsername(data.display_username); const description = this.description(); if (notificationType === LIKED_TYPE && data.count > 1) { const count = data.count - 2; - const username2 = data.username2; + const username2 = formatUsername(data.username2); if (count===0) { return I18n.t('notifications.liked_2', {description, username, username2}); } else { diff --git a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 index 2bf64d738db..363f769101f 100644 --- a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 +++ b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 @@ -1,6 +1,7 @@ import { iconNode } from 'discourse-common/lib/icon-library'; import { createWidget } from 'discourse/widgets/widget'; import { h } from 'virtual-dom'; +import { formatUsername } from 'discourse/lib/utilities'; function sanitizeName(name){ return name.toLowerCase().replace(/[\s_-]/g,''); @@ -17,10 +18,11 @@ export default createWidget('poster-name', { }, userLink(attrs, text) { - return h('a', { attributes: { - href: attrs.usernameUrl, - 'data-user-card': attrs.username - } }, text); + return h( + 'a', + { attributes: { href: attrs.usernameUrl, 'data-user-card': attrs.username } }, + formatUsername(text) + ); }, html(attrs) { diff --git a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 index 6ba446b8be4..9142695573a 100644 --- a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 @@ -1,5 +1,6 @@ import { createWidget } from 'discourse/widgets/widget'; import { h } from 'virtual-dom'; +import { formatUsername } from 'discourse/lib/utilities'; let extraGlyphs; @@ -50,7 +51,7 @@ createWidget('user-menu-links', { model: currentUser, className: 'user-activity-link', icon: 'user', - rawLabel: currentUser.username + rawLabel: formatUsername(currentUser.username) }; if (currentUser.is_anonymous) { diff --git a/app/assets/javascripts/pretty-text/pretty-text.js.es6 b/app/assets/javascripts/pretty-text/pretty-text.js.es6 index 9c7a814940a..3d3f85fc624 100644 --- a/app/assets/javascripts/pretty-text/pretty-text.js.es6 +++ b/app/assets/javascripts/pretty-text/pretty-text.js.es6 @@ -24,7 +24,8 @@ export function buildOptions(state) { lookupImageUrls, previewing, linkify, - censoredWords + censoredWords, + mentionLookup } = state; let features = { @@ -56,7 +57,7 @@ export function buildOptions(state) { getCurrentUser, currentUser, lookupAvatarByPostNumber, - mentionLookup: state.mentionLookup, + mentionLookup, emojiUnicodeReplacer, lookupInlineOnebox, lookupImageUrls,