content warnings can have emoji (#122)

fixes #105
This commit is contained in:
Nolan Lawson 2018-04-14 15:50:06 -07:00 committed by GitHub
parent a6e737bdbb
commit 6230c2703e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 16 deletions

View file

@ -39,6 +39,7 @@
"compression": "^1.7.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.7",
"escape-html": "^1.0.3",
"esm": "^3.0.12",
"events": "^2.0.0",
"express": "^4.16.2",

View file

@ -58,10 +58,10 @@
</style>
<script>
import { replaceAll } from '../../_utils/strings'
import { mark, stop } from '../../_utils/marks'
import { store } from '../../_store/store'
import { classname } from '../../_utils/classname'
import { emojifyText } from '../../_utils/emojifyText'
export default {
oncreate() {
@ -79,20 +79,9 @@
},
massagedContent: (originalStatus, $autoplayGifs) => {
let content = originalStatus.content
// emojify
if (originalStatus.emojis && originalStatus.emojis.length) {
for (let emoji of originalStatus.emojis) {
let { shortcode, url, static_url } = emoji
let urlToUse = $autoplayGifs ? url : static_url
let shortcodeWithColons = `:${shortcode}:`
content = replaceAll(
content,
shortcodeWithColons,
`<img class="status-emoji" draggable="false" src="${urlToUse}"
alt="${shortcodeWithColons}" title="${shortcodeWithColons}" />`
)
}
}
content = emojifyText(content, originalStatus.emojis, $autoplayGifs)
// GNU Social and Pleroma don't add <p> tags
if (!content.startsWith('<p>')) {
content = `<p>${content}</p>`

View file

@ -1,5 +1,5 @@
<div class="status-spoiler {{isStatusInNotification ? 'status-in-notification' : ''}} {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
<p>{{originalStatus.spoiler_text}}</p>
<p>{{{massagedSpoilerText}}}</p>
</div>
<div class="status-spoiler-button {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
<button type="button" delegate-key="{{delegateKey}}">
@ -16,6 +16,12 @@
margin: 10px 5px;
}
:global(.status-spoiler .status-emoji) {
width: 20px;
height: 20px;
margin: -3px 0;
}
.status-spoiler.status-in-own-thread {
font-size: 1.3em;
margin: 20px 5px 10px;
@ -42,6 +48,8 @@
import { store } from '../../_store/store'
import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate'
import { mark, stop } from '../../_utils/marks'
import { emojifyText } from '../../_utils/emojifyText'
import escapeHtml from 'escape-html'
export default {
oncreate() {
@ -52,6 +60,11 @@
},
store: () => store,
computed: {
spoilerText: (originalStatus) => originalStatus.spoiler_text,
massagedSpoilerText: (spoilerText, originalStatus, $autoplayGifs) => {
spoilerText = escapeHtml(spoilerText)
return emojifyText(spoilerText, originalStatus.emojis, $autoplayGifs)
},
delegateKey: (uuid) => `spoiler-${uuid}`
},
methods: {

View file

@ -0,0 +1,17 @@
import { replaceAll } from './strings'
export function emojifyText (text, emojis, autoplayGifs) {
if (emojis && emojis.length) {
for (let emoji of emojis) {
let urlToUse = autoplayGifs ? emoji.url : emoji.static_url
let shortcodeWithColons = `:${emoji.shortcode}:`
text = replaceAll(
text,
shortcodeWithColons,
`<img class="status-emoji" draggable="false" src="${urlToUse}"
alt="${shortcodeWithColons}" title="${shortcodeWithColons}" />`
)
}
}
return text
}

View file

@ -32,3 +32,28 @@ test('content warnings are not posted if removed', async t => {
.expect(getNthStatus(0).innerText).notContains('content warning!')
.expect(getNthStatus(0).find('.status-content').innerText).contains('hi this is another toot')
})
test('content warnings can have emoji', async t => {
await t.useRole(foobarRole)
.typeText(composeInput, 'I can: :blobnom:')
.click(contentWarningButton)
.typeText(composeContentWarning, 'can you feel the :blobpats: tonight')
.click(composeButton)
.expect(getNthStatus(0).innerText).contains('can you feel the', {timeout: 30000})
.expect(getNthStatus(0).find('.status-spoiler img.status-emoji').getAttribute('alt')).eql(':blobpats:')
.click(getNthShowOrHideButton(0))
.expect(getNthStatus(0).find('.status-content img.status-emoji').getAttribute('alt')).eql(':blobnom:')
})
test('no XSS in content warnings or text', async t => {
let pwned1 = `<script>alert("pwned!")</script>`
let pwned2 = `<script>alert("pwned from CW!")</script>`
await t.useRole(foobarRole)
.typeText(composeInput, pwned1)
.click(contentWarningButton)
.typeText(composeContentWarning, pwned2)
.click(composeButton)
.expect(getNthStatus(0).find('.status-spoiler').innerText).contains(pwned2)
.click(getNthShowOrHideButton(0))
.expect(getNthStatus(0).find('.status-content').innerText).contains(pwned1)
})