parent
a6e737bdbb
commit
6230c2703e
|
@ -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",
|
||||
|
|
|
@ -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>`
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue