{'@' + account.acct}
@@ -71,6 +71,7 @@
import Avatar from '../Avatar.html'
import SearchResult from './SearchResult.html'
import IconButton from '../IconButton.html'
+ import AccountDisplayName from '../profile/AccountDisplayName.html'
export default {
data: () => ({
@@ -89,7 +90,8 @@
components: {
Avatar,
SearchResult,
- IconButton
+ IconButton,
+ AccountDisplayName
}
}
\ No newline at end of file
diff --git a/routes/_components/status/StatusAuthorName.html b/routes/_components/status/StatusAuthorName.html
index 606b4e0c..4a91db62 100644
--- a/routes/_components/status/StatusAuthorName.html
+++ b/routes/_components/status/StatusAuthorName.html
@@ -3,7 +3,7 @@
title="{'@' + originalAccount.acct}"
focus-key={focusKey}
>
- {originalAccount.display_name || originalAccount.username}
+
\ No newline at end of file
diff --git a/routes/_components/status/StatusContent.html b/routes/_components/status/StatusContent.html
index ae2c03d6..245afd63 100644
--- a/routes/_components/status/StatusContent.html
+++ b/routes/_components/status/StatusContent.html
@@ -21,14 +21,6 @@
display: block;
}
- :global(.status-content .status-emoji) {
- width: 1.4em;
- height: 1.4em;
- margin: -0.1em 0;
- object-fit: contain;
- vertical-align: middle;
- }
-
:global(.status-content p) {
margin: 0 0 20px;
}
diff --git a/routes/_components/status/StatusHeader.html b/routes/_components/status/StatusHeader.html
index 66d4f0f1..baa3583e 100644
--- a/routes/_components/status/StatusHeader.html
+++ b/routes/_components/status/StatusHeader.html
@@ -16,7 +16,7 @@
class="status-header-author"
title="{'@' + account.acct}"
focus-key={focusKey} >
- {account.display_name || account.username}
+
{/if}
@@ -103,10 +103,12 @@
\ No newline at end of file
diff --git a/routes/_utils/ajax.js b/routes/_utils/ajax.js
index 837a59e1..76aad597 100644
--- a/routes/_utils/ajax.js
+++ b/routes/_utils/ajax.js
@@ -39,7 +39,7 @@ async function _fetch (url, fetchOptions, options) {
return throwErrorIfInvalidResponse(response)
}
-async function _putOrPost (method, url, body, headers, options) {
+async function _putOrPostOrPatch (method, url, body, headers, options) {
let fetchOptions = makeFetchOptions(method, headers)
if (body) {
if (body instanceof FormData) {
@@ -53,11 +53,15 @@ async function _putOrPost (method, url, body, headers, options) {
}
export async function put (url, body, headers, options) {
- return _putOrPost('PUT', url, body, headers, options)
+ return _putOrPostOrPatch('PUT', url, body, headers, options)
}
export async function post (url, body, headers, options) {
- return _putOrPost('POST', url, body, headers, options)
+ return _putOrPostOrPatch('POST', url, body, headers, options)
+}
+
+export async function patch (url, body, headers, options) {
+ return _putOrPostOrPatch('PATCH', url, body, headers, options)
}
export async function get (url, headers, options) {
diff --git a/routes/_utils/emojifyText.js b/routes/_utils/emojifyText.js
index d2da59d2..eb11baa9 100644
--- a/routes/_utils/emojifyText.js
+++ b/routes/_utils/emojifyText.js
@@ -8,7 +8,7 @@ export function emojifyText (text, emojis, autoplayGifs) {
text = replaceAll(
text,
shortcodeWithColons,
- `
`
)
}
diff --git a/scss/global.scss b/scss/global.scss
index fff6f443..ebbe2e64 100644
--- a/scss/global.scss
+++ b/scss/global.scss
@@ -197,4 +197,13 @@ textarea {
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
+}
+
+/* this gets injected as raw HTML, so it's easiest to just define it in global.scss */
+.inline-custom-emoji {
+ width: 1.4em;
+ height: 1.4em;
+ margin: -0.1em 0;
+ object-fit: contain;
+ vertical-align: middle;
}
\ No newline at end of file
diff --git a/templates/2xx.html b/templates/2xx.html
index 1c7acae6..aaea7a71 100644
--- a/templates/2xx.html
+++ b/templates/2xx.html
@@ -17,7 +17,7 @@
diff --git a/tests/serverActions.js b/tests/serverActions.js
index f53e0399..f5ac0695 100644
--- a/tests/serverActions.js
+++ b/tests/serverActions.js
@@ -6,6 +6,7 @@ import { postStatus } from '../routes/_api/statuses'
import { deleteStatus } from '../routes/_api/delete'
import { authorizeFollowRequest, getFollowRequests } from '../routes/_actions/followRequests'
import { followAccount, unfollowAccount } from '../routes/_api/follow'
+import { updateCredentials } from '../routes/_api/updateCredentials'
global.fetch = fetch
global.File = FileApi.File
@@ -46,3 +47,7 @@ export async function followAs (username, userToFollow) {
export async function unfollowAs (username, userToFollow) {
return unfollowAccount(instanceName, users[username].accessToken, users[userToFollow].id)
}
+
+export async function updateUserDisplayNameAs (username, displayName) {
+ return updateCredentials(instanceName, users[username].accessToken, {display_name: displayName})
+}
diff --git a/tests/spec/016-external-links.js b/tests/spec/016-external-links.js
index ebb30a3d..d624dbf9 100644
--- a/tests/spec/016-external-links.js
+++ b/tests/spec/016-external-links.js
@@ -37,7 +37,7 @@ test('converts external links in profiles', async t => {
.hover(getNthStatus(0))
.navigateTo('/accounts/4')
.expect(getUrl()).contains('/accounts/4')
- .expect($('.account-profile-name').innerText).eql('External Lonk')
+ .expect($('.account-profile-name').innerText).contains('External Lonk')
.expect($('.account-profile-name a').getAttribute('href')).eql('http://localhost:3000/@ExternalLinks')
.expect($('.account-profile-name a').getAttribute('rel')).eql('nofollow noopener')
.expect(getAnchorInProfile(0).getAttribute('href')).eql('https://joinmastodon.org')
diff --git a/tests/spec/110-compose-content-warnings.js b/tests/spec/110-compose-content-warnings.js
index 4b342aa3..8effc799 100644
--- a/tests/spec/110-compose-content-warnings.js
+++ b/tests/spec/110-compose-content-warnings.js
@@ -44,9 +44,9 @@ test('content warnings can have emoji', async t => {
.typeText(composeContentWarning, 'can you feel the :blobpats: tonight')
.click(composeButton)
.expect(getNthStatus(0).innerText).contains('can you feel the', {timeout: 30000})
- .expect($(`${getNthStatusSelector(0)} .status-spoiler img.status-emoji`).getAttribute('alt')).eql(':blobpats:')
+ .expect($(`${getNthStatusSelector(0)} .status-spoiler img.inline-custom-emoji`).getAttribute('alt')).eql(':blobpats:')
.click(getNthShowOrHideButton(0))
- .expect($(`${getNthStatusSelector(0)} .status-content img.status-emoji`).getAttribute('alt')).eql(':blobnom:')
+ .expect($(`${getNthStatusSelector(0)} .status-content img.inline-custom-emoji`).getAttribute('alt')).eql(':blobnom:')
})
test('no XSS in content warnings or text', async t => {
diff --git a/tests/spec/118-display-name-custom-emoji.js b/tests/spec/118-display-name-custom-emoji.js
new file mode 100644
index 00000000..56400393
--- /dev/null
+++ b/tests/spec/118-display-name-custom-emoji.js
@@ -0,0 +1,27 @@
+import { loginAsFoobar } from '../roles'
+import { displayNameInComposeBox, getNthStatusSelector, getUrl, sleep } from '../utils'
+import { updateUserDisplayNameAs } from '../serverActions'
+import { Selector as $ } from 'testcafe'
+
+fixture`118-display-name-custom-emoji.js`
+ .page`http://localhost:4002`
+
+test('Can put custom emoji in display name', async t => {
+ await updateUserDisplayNameAs('foobar', 'foobar :blobpats:')
+ await sleep(1000)
+ await loginAsFoobar(t)
+ await t
+ .expect(displayNameInComposeBox.innerText).eql('foobar ')
+ .expect($('.compose-box-display-name img').getAttribute('alt')).eql(':blobpats:')
+ .click(displayNameInComposeBox)
+ .expect(getUrl()).contains('/accounts/2')
+ .expect($(`${getNthStatusSelector(0)} .status-author-name img`).getAttribute('alt')).eql(':blobpats:')
+})
+
+test('Cannot XSS using display name HTML', async t => {
+ await updateUserDisplayNameAs('foobar', '')
+ await sleep(1000)
+ await loginAsFoobar(t)
+ await t
+ .expect(displayNameInComposeBox.innerText).eql('')
+})
diff --git a/tests/utils.js b/tests/utils.js
index 43633f4f..71e1031e 100644
--- a/tests/utils.js
+++ b/tests/utils.js
@@ -40,6 +40,7 @@ export const mastodonLogInButton = $('button[type="submit"]')
export const followsButton = $('.account-profile-details > *:nth-child(2)')
export const followersButton = $('.account-profile-details > *:nth-child(3)')
export const avatarInComposeBox = $('.compose-box-avatar')
+export const displayNameInComposeBox = $('.compose-box-display-name')
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
innerCount: el => parseInt(el.innerText, 10)