parent
							
								
									a6e737bdbb
								
							
						
					
					
						commit
						6230c2703e
					
				
					 5 changed files with 61 additions and 16 deletions
				
			
		| 
						 | 
				
			
			@ -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: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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).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…
	
	Add table
		
		Reference in a new issue