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.
This commit is contained in:
Robin Ward
2017-10-30 12:40:58 -04:00
parent e02ad4249e
commit a0dd75ba88
11 changed files with 57 additions and 16 deletions

View File

@ -0,0 +1,4 @@
import { registerUnbound } from 'discourse-common/lib/helpers';
import { formatUsername } from 'discourse/lib/utilities';
export default registerUnbound('format-username', formatUsername);

View File

@ -1,5 +1,6 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import { formatUsername } from 'discourse/lib/utilities';
function replaceSpan($e, username, opts) { function replaceSpan($e, username, opts) {
let extra = ""; let extra = "";
@ -16,7 +17,7 @@ function replaceSpan($e, username, opts) {
extra = `data-name='${username}'`; extra = `data-name='${username}'`;
extraClass = "cannot-see"; extraClass = "cannot-see";
} }
$e.replaceWith(`<a href='${userPath(username.toLowerCase())}' class='mention ${extraClass}' ${extra}>@${username}</a>`); $e.replaceWith(`<a href='${userPath(username.toLowerCase())}' class='mention ${extraClass}' ${extra}>@${formatUsername(username)}</a>`);
} }
} }

View File

@ -20,10 +20,10 @@ import { addPostTransformCallback } from 'discourse/widgets/post-stream';
import { attachAdditionalPanel } from 'discourse/widgets/header'; import { attachAdditionalPanel } from 'discourse/widgets/header';
import { registerIconRenderer, replaceIcon } from 'discourse-common/lib/icon-library'; import { registerIconRenderer, replaceIcon } from 'discourse-common/lib/icon-library';
import { addNavItem } from 'discourse/models/nav-item'; 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 // 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 { class PluginApi {
constructor(version, container) { constructor(version, container) {
@ -570,6 +570,25 @@ class PluginApi {
addNavItem(item); 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; let _pluginv01;

View File

@ -3,6 +3,7 @@ import { performEmojiUnescape, buildEmojiUrl } from 'pretty-text/emoji';
import WhiteLister from 'pretty-text/white-lister'; import WhiteLister from 'pretty-text/white-lister';
import { sanitize as textSanitize } from 'pretty-text/sanitizer'; import { sanitize as textSanitize } from 'pretty-text/sanitizer';
import loadScript from 'discourse/lib/load-script'; import loadScript from 'discourse/lib/load-script';
import { formatUsername } from 'discourse/lib/utilities';
function getOpts(opts) { function getOpts(opts) {
const siteSettings = Discourse.__container__.lookup('site-settings:main'), const siteSettings = Discourse.__container__.lookup('site-settings:main'),
@ -12,7 +13,8 @@ function getOpts(opts) {
getURL: Discourse.getURLWithCDN, getURL: Discourse.getURLWithCDN,
currentUser: Discourse.__container__.lookup('current-user:main'), currentUser: Discourse.__container__.lookup('current-user:main'),
censoredWords: site.censored_words, censoredWords: site.censored_words,
siteSettings siteSettings,
formatUsername
}, opts); }, opts);
return buildOptions(opts); return buildOptions(opts);

View File

@ -21,6 +21,17 @@ export function escapeExpression(string) {
return escape(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) { export function avatarUrl(template, size) {
if (!template) { return ""; } if (!template) { return ""; }
const rawSize = getRawSize(translateSize(size)); const rawSize = getRawSize(translateSize(size));

View File

@ -16,7 +16,7 @@
<div class="names"> <div class="names">
<span> <span>
<h1 class="{{staff}} {{new_user}} {{if nameFirst "full-name" "username"}}"> <h1 class="{{staff}} {{new_user}} {{if nameFirst "full-name" "username"}}">
<a href={{user.path}} {{action "showUser"}}>{{if nameFirst user.name username}} {{user-status user currentUser=currentUser}}</a> <a href={{user.path}} {{action "showUser"}}>{{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}}</a>
</h1> </h1>
{{#unless nameFirst}} {{#unless nameFirst}}

View File

@ -61,7 +61,7 @@
</section> </section>
<div class="primary-textual"> <div class="primary-textual">
<h1 class="{{if nameFirst "full-name" "username"}}">{{if nameFirst model.name model.username}} {{user-status model currentUser=currentUser}}</h1> <h1 class="{{if nameFirst "full-name" "username"}}">{{if nameFirst model.name (format-username model.username)}} {{user-status model currentUser=currentUser}}</h1>
<h2 class="{{if nameFirst "username" "full-name"}}">{{#if nameFirst}}{{model.username}}{{else}}{{model.name}}{{/if}}</h2> <h2 class="{{if nameFirst "username" "full-name"}}">{{#if nameFirst}}{{model.username}}{{else}}{{model.name}}{{/if}}</h2>
{{#if model.title}} {{#if model.title}}
<h3>{{model.title}}</h3> <h3>{{model.title}}</h3>

View File

@ -4,7 +4,7 @@ import { createWidget } from 'discourse/widgets/widget';
import DiscourseURL from 'discourse/lib/url'; import DiscourseURL from 'discourse/lib/url';
import { h } from 'virtual-dom'; import { h } from 'virtual-dom';
import { emojiUnescape } from 'discourse/lib/text'; 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 { setTransientHeader } from 'discourse/lib/ajax';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import { iconNode } from 'discourse-common/lib/icon-library'; import { iconNode } from 'discourse-common/lib/icon-library';
@ -79,11 +79,11 @@ createWidget('notification-item', {
return I18n.t(scope, { count, group_name }); return I18n.t(scope, { count, group_name });
} }
const username = data.display_username; const username = formatUsername(data.display_username);
const description = this.description(); const description = this.description();
if (notificationType === LIKED_TYPE && data.count > 1) { if (notificationType === LIKED_TYPE && data.count > 1) {
const count = data.count - 2; const count = data.count - 2;
const username2 = data.username2; const username2 = formatUsername(data.username2);
if (count===0) { if (count===0) {
return I18n.t('notifications.liked_2', {description, username, username2}); return I18n.t('notifications.liked_2', {description, username, username2});
} else { } else {

View File

@ -1,6 +1,7 @@
import { iconNode } from 'discourse-common/lib/icon-library'; import { iconNode } from 'discourse-common/lib/icon-library';
import { createWidget } from 'discourse/widgets/widget'; import { createWidget } from 'discourse/widgets/widget';
import { h } from 'virtual-dom'; import { h } from 'virtual-dom';
import { formatUsername } from 'discourse/lib/utilities';
function sanitizeName(name){ function sanitizeName(name){
return name.toLowerCase().replace(/[\s_-]/g,''); return name.toLowerCase().replace(/[\s_-]/g,'');
@ -17,10 +18,11 @@ export default createWidget('poster-name', {
}, },
userLink(attrs, text) { userLink(attrs, text) {
return h('a', { attributes: { return h(
href: attrs.usernameUrl, 'a',
'data-user-card': attrs.username { attributes: { href: attrs.usernameUrl, 'data-user-card': attrs.username } },
} }, text); formatUsername(text)
);
}, },
html(attrs) { html(attrs) {

View File

@ -1,5 +1,6 @@
import { createWidget } from 'discourse/widgets/widget'; import { createWidget } from 'discourse/widgets/widget';
import { h } from 'virtual-dom'; import { h } from 'virtual-dom';
import { formatUsername } from 'discourse/lib/utilities';
let extraGlyphs; let extraGlyphs;
@ -50,7 +51,7 @@ createWidget('user-menu-links', {
model: currentUser, model: currentUser,
className: 'user-activity-link', className: 'user-activity-link',
icon: 'user', icon: 'user',
rawLabel: currentUser.username rawLabel: formatUsername(currentUser.username)
}; };
if (currentUser.is_anonymous) { if (currentUser.is_anonymous) {

View File

@ -24,7 +24,8 @@ export function buildOptions(state) {
lookupImageUrls, lookupImageUrls,
previewing, previewing,
linkify, linkify,
censoredWords censoredWords,
mentionLookup
} = state; } = state;
let features = { let features = {
@ -56,7 +57,7 @@ export function buildOptions(state) {
getCurrentUser, getCurrentUser,
currentUser, currentUser,
lookupAvatarByPostNumber, lookupAvatarByPostNumber,
mentionLookup: state.mentionLookup, mentionLookup,
emojiUnicodeReplacer, emojiUnicodeReplacer,
lookupInlineOnebox, lookupInlineOnebox,
lookupImageUrls, lookupImageUrls,