add spoiler text support

This commit is contained in:
Nolan Lawson 2018-01-21 10:32:18 -08:00
parent 18ad6ab1ab
commit 6790cfd187
8 changed files with 90 additions and 33 deletions

View file

@ -5,20 +5,20 @@
<button type="button" <button type="button"
class="play-video-button" class="play-video-button"
aria-label="Play video" aria-label="Play video"
on:click="onClickPlayVideoButton(media, getSmallWidth(media), getSmallHeight(media))"> on:click="onClickPlayVideoButton(media, getSmallWidth(media), getSmallHeight(media), media.description)">
<div class="svg-wrapper"> <div class="svg-wrapper">
<svg> <svg>
<use xlink:href="#fa-play-circle" /> <use xlink:href="#fa-play-circle" />
</svg> </svg>
</div> </div>
<img aria-hidden="true" <img alt="{{media.description || ''}}"
alt=""
src="{{media.preview_url}}" src="{{media.preview_url}}"
width="{{getSmallWidth(media)}}" width="{{getSmallWidth(media)}}"
height="{{getSmallHeight(media)}}" /> height="{{getSmallHeight(media)}}" />
</button> </button>
{{elseif media.type === 'gifv'}} {{elseif media.type === 'gifv'}}
<video <video
aria-label="Animated GIF: {{media.description || ''}}"
poster="{{media.preview_url}}" poster="{{media.preview_url}}"
src="{{media.url}}" src="{{media.url}}"
width="{{getSmallWidth(media)}}" width="{{getSmallWidth(media)}}"
@ -29,8 +29,8 @@
playsinline playsinline
/> />
{{else}} {{else}}
<img src="{{media.preview_url}}" <img alt="{{media.description || ''}}"
alt="{{media.description || ''}}" src="{{media.preview_url}}"
width="{{getSmallWidth(media)}}" width="{{getSmallWidth(media)}}"
height="{{getSmallHeight(media)}}"/> height="{{getSmallHeight(media)}}"/>
{{/if}} {{/if}}
@ -112,8 +112,8 @@
minMediaWidth: (mediaAttachments) => Math.min.apply(Math, mediaAttachments.map(media => media.meta && media.meta.small && typeof media.meta.small.width === 'number' ? media.meta.small.width : DEFAULT_MEDIA_WIDTH)) minMediaWidth: (mediaAttachments) => Math.min.apply(Math, mediaAttachments.map(media => media.meta && media.meta.small && typeof media.meta.small.width === 'number' ? media.meta.small.width : DEFAULT_MEDIA_WIDTH))
}, },
methods: { methods: {
async onClickPlayVideoButton(media, width, height) { async onClickPlayVideoButton(media, width, height, description) {
showVideoDialog(media.preview_url, media.url, width, height) showVideoDialog(media.preview_url, media.url, width, height, description)
} }
} }
} }

View file

@ -1,29 +1,41 @@
<article class="status-article" tabindex="0" aria-posinset="{{index}}" aria-setsize="{{length}}"> <article class="status-article" tabindex="0" aria-posinset="{{index}}" aria-setsize="{{length}}" on:recalculateHeight>
{{#if status.reblog}} {{#if status.reblog}}
<div class="status-boosted"> <div class="status-boosted">
<svg> <svg>
<use xlink:href="#fa-retweet" /> <use xlink:href="#fa-retweet" />
</svg> </svg>
<span> <span>
<a href="/accounts/{{status.account.id}}"> <a href="/accounts/{{status.account.id}}">
{{status.account.username}} {{status.account.username}}
</a> boosted </a> boosted
</span> </span>
</div> </div>
{{/if}} {{/if}}
<div class="status-author"> <div class="status-author">
<a class="status-author-name" href="/accounts/{{originalAccount.id}}"> <a class="status-author-name" href="/accounts/{{originalAccount.id}}">
{{originalAccount.display_name || originalAccount.username}} {{originalAccount.display_name || originalAccount.username}}
</a> </a>
<span class="status-author-handle"> <span class="status-author-handle">
@{{originalAccount.acct}} {{'@' + originalAccount.acct}}
</span> </span>
<a class="status-author-date" rel="noopener" target="_blank" href="{{originalStatus.uri}}"> <a class="status-author-date" rel="noopener" target="_blank" href="{{originalStatus.uri}}">
<time datetime={{createdAtDate}} title="{{relativeDate}}">{{relativeDate}}</time> <time datetime={{createdAtDate}} title="{{relativeDate}}">{{relativeDate}}</time>
</a> </a>
</div> </div>
<Avatar account={{originalAccount}} className="status-sidebar"/> <Avatar account={{originalAccount}} className="status-sidebar"/>
<div class="status-content">{{{status.content}}}</div> {{#if status.spoiler_text}}
<div class="status-spoiler">{{status.spoiler_text}}</div>
{{/if}}
{{#if status.spoiler_text}}
<div class="status-spoiler-button">
<button type="button" on:click="onClickSpoilerButton()">Show more</button>
</div>
{{/if}}
{{#if !status.spoiler_text || spoilerShown}}
<div class="status-content">
{{{status.content}}}
</div>
{{/if}}
<Toolbar :status /> <Toolbar :status />
<Media mediaAttachments="{{originalMediaAttachments}}" /> <Media mediaAttachments="{{originalMediaAttachments}}" />
</article> </article>
@ -37,6 +49,8 @@
grid-template-areas: grid-template-areas:
".............. status-boosted" ".............. status-boosted"
"status-sidebar status-author" "status-sidebar status-author"
"status-sidebar status-spoiler"
"status-sidebar status-spoiler-button"
"status-sidebar status-content" "status-sidebar status-content"
".............. status-toolbar" ".............. status-toolbar"
"status-media status-media"; "status-media status-media";
@ -50,6 +64,25 @@
margin: 0 10px 0 0; margin: 0 10px 0 0;
} }
.status-spoiler {
grid-area: status-spoiler;
word-wrap: break-word;
overflow: hidden;
white-space: pre-wrap;
font-size: 1.1em;
margin: 5px;
}
.status-spoiler-button {
grid-area: status-spoiler-button;
margin: 5px;
}
.status-spoiler-button button {
padding: 5px 10px;
font-size: 1.1em;
}
.status-author { .status-author {
grid-area: status-author; grid-area: status-author;
display: flex; display: flex;
@ -94,7 +127,7 @@
} }
.status-content { .status-content {
margin: 10px 10px 20px 5px; margin: 10px 10px 10px 5px;
grid-area: status-content; grid-area: status-content;
word-wrap: break-word; word-wrap: break-word;
overflow: hidden; overflow: hidden;
@ -160,6 +193,12 @@
originalStatus: (status) => status.reblog ? status.reblog : status, originalStatus: (status) => status.reblog ? status.reblog : status,
originalAccount: (originalStatus) => originalStatus.account, originalAccount: (originalStatus) => originalStatus.account,
originalMediaAttachments: (originalStatus) => originalStatus.media_attachments, originalMediaAttachments: (originalStatus) => originalStatus.media_attachments,
},
methods: {
onClickSpoilerButton() {
this.set({spoilerShown: !this.get('spoilerShown')})
this.fire('recalculateHeight')
}
} }
} }
</script> </script>

View file

@ -1,4 +1,4 @@
<Status status="{{virtualProps}}" index="{{virtualIndex}}" length="{{virtualLength}}"/> <Status status="{{virtualProps}}" index="{{virtualIndex}}" length="{{virtualLength}}" on:recalculateHeight />
<script> <script>
import Status from './Status.html' import Status from './Status.html'

View file

@ -8,6 +8,7 @@
src="{{src}}" src="{{src}}"
width="{{width}}" width="{{width}}"
height="{{height}}" height="{{height}}"
aria-label="Video: {{description || ''}}"
controls controls
/> />
</div> </div>

View file

@ -1,10 +1,9 @@
<div class="virtual-list-item {{shown ? 'shown' : ''}}" <div class="virtual-list-item {{shown ? 'shown' : ''}}"
virtual-list-key="{{key}}" virtual-list-key="{{key}}"
ref:node ref:node
style="transform: translateY({{offset}}px style="transform: translateY({{offset}}px);" >
);" <:Component {component} virtualProps="{{props}}" virtualIndex="{{index}}" virtualLength="{{$numItems}}"
> on:recalculateHeight="doRecalculateHeight()"/>
<:Component {component} virtualProps="{{props}}" virtualIndex="{{index}}" virtualLength="{{$numItems}}"/>
</div> </div>
<style> <style>
.virtual-list-item { .virtual-list-item {
@ -23,7 +22,8 @@
import { virtualListStore } from '../_utils/virtualListStore' import { virtualListStore } from '../_utils/virtualListStore'
import { AsyncLayout } from '../_utils/AsyncLayout' import { AsyncLayout } from '../_utils/AsyncLayout'
const asyncLayout = new AsyncLayout(node => node.getAttribute('virtual-list-key')) const keyGetter = node => node.getAttribute('virtual-list-key')
const asyncLayout = new AsyncLayout(keyGetter)
export default { export default {
oncreate() { oncreate() {
@ -40,6 +40,17 @@
store: () => virtualListStore, store: () => virtualListStore,
computed: { computed: {
'shown': ($itemHeights, key) => $itemHeights[key] > 0 'shown': ($itemHeights, key) => $itemHeights[key] > 0
},
methods: {
doRecalculateHeight() {
let tempAsyncLayout = new AsyncLayout(keyGetter)
let key = this.get('key')
tempAsyncLayout.observe(key, this.refs.node, (rect) => {
tempAsyncLayout.disconnect()
// update all item heights in one microtask batch for better perf
this.store.batchUpdate('itemHeights', key, rect.height)
})
}
} }
} }
</script> </script>

View file

@ -26,6 +26,11 @@ class AsyncLayout {
this._intersectionObserver.unobserve(node) this._intersectionObserver.unobserve(node)
delete this._onIntersectionCallbacks[key] delete this._onIntersectionCallbacks[key]
} }
disconnect() {
this._intersectionObserver.disconnect()
this._intersectionObserver = null
}
} }
export { AsyncLayout } export { AsyncLayout }

View file

@ -1,6 +1,6 @@
import VideoDialog from '../_components/VideoDialog.html' import VideoDialog from '../_components/VideoDialog.html'
export function showVideoDialog(poster, src, width, height) { export function showVideoDialog(poster, src, width, height, description) {
let dialog = document.createElement('dialog') let dialog = document.createElement('dialog')
dialog.classList.add('video-dialog') dialog.classList.add('video-dialog')
dialog.setAttribute('aria-label', 'Video dialog') dialog.setAttribute('aria-label', 'Video dialog')
@ -12,7 +12,8 @@ export function showVideoDialog(poster, src, width, height) {
src: src, src: src,
dialog: dialog, dialog: dialog,
width: width, width: width,
height: height height: height,
description: description
} }
}) })
videoDialog.showModal() videoDialog.showModal()

View file

@ -9,10 +9,10 @@
{{#if instanceUserAccount}} {{#if instanceUserAccount}}
<h2>Logged in as:</h2> <h2>Logged in as:</h2>
<div class="acct-current-user"> <div class="acct-current-user">
<img alt="Profile picture for @{{instanceUserAccount.acct}}" <img alt="Profile picture for {{'@' + instanceUserAccount.acct}}"
class="acct-avatar" src="{{instanceUserAccount.avatar}}" /> class="acct-avatar" src="{{instanceUserAccount.avatar}}" />
<a class="acct-handle" rel="noopener" target="_blank" <a class="acct-handle" rel="noopener" target="_blank"
href="{{instanceUserAccount.url}}">@{{instanceUserAccount.acct}}</a> href="{{instanceUserAccount.url}}">{{'@' + instanceUserAccount.acct}}</a>
<span class="acct-display-name">{{instanceUserAccount.display_name}}</span> <span class="acct-display-name">{{instanceUserAccount.display_name}}</span>
</div> </div>
<h2>Theme:</h2> <h2>Theme:</h2>