parent
a6e737bdbb
commit
6230c2703e
|
@ -39,6 +39,7 @@
|
||||||
"compression": "^1.7.1",
|
"compression": "^1.7.1",
|
||||||
"cross-env": "^5.1.3",
|
"cross-env": "^5.1.3",
|
||||||
"css-loader": "^0.28.7",
|
"css-loader": "^0.28.7",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
"esm": "^3.0.12",
|
"esm": "^3.0.12",
|
||||||
"events": "^2.0.0",
|
"events": "^2.0.0",
|
||||||
"express": "^4.16.2",
|
"express": "^4.16.2",
|
||||||
|
|
|
@ -58,10 +58,10 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { replaceAll } from '../../_utils/strings'
|
|
||||||
import { mark, stop } from '../../_utils/marks'
|
import { mark, stop } from '../../_utils/marks'
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { classname } from '../../_utils/classname'
|
import { classname } from '../../_utils/classname'
|
||||||
|
import { emojifyText } from '../../_utils/emojifyText'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
|
@ -79,20 +79,9 @@
|
||||||
},
|
},
|
||||||
massagedContent: (originalStatus, $autoplayGifs) => {
|
massagedContent: (originalStatus, $autoplayGifs) => {
|
||||||
let content = originalStatus.content
|
let content = originalStatus.content
|
||||||
// emojify
|
|
||||||
if (originalStatus.emojis && originalStatus.emojis.length) {
|
content = emojifyText(content, originalStatus.emojis, $autoplayGifs)
|
||||||
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}" />`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// GNU Social and Pleroma don't add <p> tags
|
// GNU Social and Pleroma don't add <p> tags
|
||||||
if (!content.startsWith('<p>')) {
|
if (!content.startsWith('<p>')) {
|
||||||
content = `<p>${content}</p>`
|
content = `<p>${content}</p>`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="status-spoiler {{isStatusInNotification ? 'status-in-notification' : ''}} {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
|
<div class="status-spoiler {{isStatusInNotification ? 'status-in-notification' : ''}} {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
|
||||||
<p>{{originalStatus.spoiler_text}}</p>
|
<p>{{{massagedSpoilerText}}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-spoiler-button {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
|
<div class="status-spoiler-button {{isStatusInOwnThread ? 'status-in-own-thread' : ''}}">
|
||||||
<button type="button" delegate-key="{{delegateKey}}">
|
<button type="button" delegate-key="{{delegateKey}}">
|
||||||
|
@ -16,6 +16,12 @@
|
||||||
margin: 10px 5px;
|
margin: 10px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.status-spoiler .status-emoji) {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: -3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.status-spoiler.status-in-own-thread {
|
.status-spoiler.status-in-own-thread {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
margin: 20px 5px 10px;
|
margin: 20px 5px 10px;
|
||||||
|
@ -42,6 +48,8 @@
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate'
|
import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate'
|
||||||
import { mark, stop } from '../../_utils/marks'
|
import { mark, stop } from '../../_utils/marks'
|
||||||
|
import { emojifyText } from '../../_utils/emojifyText'
|
||||||
|
import escapeHtml from 'escape-html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
|
@ -52,6 +60,11 @@
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
|
spoilerText: (originalStatus) => originalStatus.spoiler_text,
|
||||||
|
massagedSpoilerText: (spoilerText, originalStatus, $autoplayGifs) => {
|
||||||
|
spoilerText = escapeHtml(spoilerText)
|
||||||
|
return emojifyText(spoilerText, originalStatus.emojis, $autoplayGifs)
|
||||||
|
},
|
||||||
delegateKey: (uuid) => `spoiler-${uuid}`
|
delegateKey: (uuid) => `spoiler-${uuid}`
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
17
routes/_utils/emojifyText.js
Normal file
17
routes/_utils/emojifyText.js
Normal 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
|
||||||
|
}
|
|
@ -32,3 +32,28 @@ test('content warnings are not posted if removed', async t => {
|
||||||
.expect(getNthStatus(0).innerText).notContains('content warning!')
|
.expect(getNthStatus(0).innerText).notContains('content warning!')
|
||||||
.expect(getNthStatus(0).find('.status-content').innerText).contains('hi this is another toot')
|
.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 a new issue