<article class="status-article" tabindex="0" aria-posinset="{{index}}" aria-setsize="{{length}}" on:recalculateHeight> {{#if status.reblog}} <div class="status-boosted"> <svg> <use xlink:href="#fa-retweet" /> </svg> <span> <a href="/accounts/{{status.account.id}}"> {{status.account.username}} </a> boosted </span> </div> {{/if}} <div class="status-author"> <a class="status-author-name" href="/accounts/{{originalAccount.id}}"> {{originalAccount.display_name || originalAccount.username}} </a> <span class="status-author-handle"> {{'@' + originalAccount.acct}} </span> <a class="status-author-date" rel="noopener" target="_blank" href="{{originalStatus.uri}}"> <time datetime={{createdAtDate}} title="{{relativeDate}}">{{relativeDate}}</time> </a> </div> <Avatar account={{originalAccount}} className="status-sidebar"/> {{#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()">{{spoilerShown ? 'Show less' : 'Show more'}}</button> </div> {{/if}} {{#if !status.spoiler_text || spoilerShown}} <div class="status-content"> {{{hydratedContent}}} </div> {{/if}} {{#if originalMediaAttachments && originalMediaAttachments.length}} {{#if status.sensitive}} <div class="status-sensitive-media-container {{sensitiveShown ? 'status-sensitive-media-shown' : 'status-sensitive-media-hidden'}}" > <button type="button" class="status-sensitive-media-button" aria-label="{{sensitiveShown ? 'Hide sensitive media: ' : 'Show sensitive media'}}" on:click="onClickSensitiveMediaButton()" > {{#if !sensitiveShown}} <div class="status-sensitive-media-warning"> <span> Sensitive content. Click to show. </span> </div> {{/if}} <div class="svg-wrapper"> <svg> <use xlink:href="{{sensitiveShown ? '#fa-eye-slash' : '#fa-eye'}}" /> </svg> </div> </button> {{#if sensitiveShown}} <Media mediaAttachments="{{originalMediaAttachments}}" sensitive="{{status.sensitive}}"/> {{/if}} </div> {{else}} <Media mediaAttachments="{{originalMediaAttachments}}" sensitive="{{status.sensitive}}"/> {{/if}} {{/if}} <Toolbar :status /> </article> <style> .status-article { width: 560px; max-width: calc(100vw - 40px); padding: 10px 20px; display: grid; grid-template-areas: ".............. status-boosted" "status-sidebar status-author" "status-sidebar status-spoiler" "status-sidebar status-spoiler-button" "status-sidebar status-content" "status-media status-media" ".............. status-toolbar"; grid-template-columns: 58px 1fr; border-bottom: 1px solid var(--main-border); } :global(.status-sidebar) { grid-area: status-sidebar; 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 { grid-area: status-author; display: flex; align-items: center; margin: 0 10px 0 5px; font-size: 1.1em; min-width: 0; } .status-author a, .status-author a:visited, .status-author a:hover, .status-author .status-author-handle { color: var(--deemphasized-text-color); } .status-author .status-author-name { min-width: 0; flex-shrink: 1; color: var(--body-text-color); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .status-author .status-author-name:hover { color: var(--body-text-color); } .status-author-handle { min-width: 0; flex: 1; margin-left: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .status-author-date { flex-shrink: 1; text-align: right; margin-left: 5px; white-space: nowrap; } .status-content { margin: 10px 10px 10px 5px; grid-area: status-content; word-wrap: break-word; overflow: hidden; white-space: pre-wrap; font-size: 0.9em; } :global(.status-content .status-emoji) { width: 20px; height: 20px; margin: -3px 0; } :global(.status-content p) { margin: 0 0 20px; } :global(.status-content p:first-child) { margin: 0 0 20px; } :global(.status-content p:last-child) { margin: 0; } .status-boosted span { margin-left: 5px; } .status-boosted span, .status-boosted a, .status-boosted a:visited, .status-boosted a:hover { color: var(--deemphasized-text-color); } .status-boosted { grid-area: status-boosted; margin: 5px 10px 5px 5px; display: flex; align-items: center; } .status-boosted svg { width: 18px; height: 18px; fill: var(--deemphasized-text-color); } .status-sensitive-media-container { grid-area: status-media; margin: 10px 0; position: relative; border-radius: 0; border: none; background: none; } .status-sensitive-media-button { margin: 0; padding: 0; border: none; background: none; width: 100%; height: 100%; } .status-sensitive-media-button:hover { background: none; } .status-sensitive-media-button:active { background: none; } .status-sensitive-media-shown .status-sensitive-media-button { position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: 90; } .status-sensitive-media-container.status-sensitive-media-hidden { width: 100%; margin: 10px auto; height: 200px; } .status-sensitive-media-container .status-sensitive-media-warning { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; color: var(--deemphasized-text-color); z-index: 60; } .status-sensitive-media-container .svg-wrapper { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: flex-start; justify-content: flex-start; z-index: 40; pointer-events: none; background: var(--mask-bg); } .status-sensitive-media-container.status-sensitive-media-shown .svg-wrapper { background: none; } .status-sensitive-media-container svg { width: 24px; height: 24px; fill: var(--mask-svg-fill); border-radius: 2px; background: var(--mask-opaque-bg); margin: 1px; padding: 6px 10px; } .status-sensitive-media-container.status-sensitive-media-hidden svg { fill: var(--deemphasized-text-color); background: var(--mask-opaque-bg); } </style> <script> import Avatar from './Avatar.html' import Media from './Media.html' import Toolbar from './Toolbar.html' import { mark, stop } from '../_utils/marks' import IntlRelativeFormat from 'intl-relativeformat' import { replaceAll } from '../_utils/replaceAll' const relativeFormat = new IntlRelativeFormat('en-US'); export default { components: { Avatar, Media, Toolbar }, computed: { createdAtDate: (status) => status.created_at, relativeDate: (createdAtDate) => { mark('compute relativeDate') let res = relativeFormat.format(new Date(createdAtDate)) stop('compute relativeDate') return res }, originalStatus: (status) => status.reblog ? status.reblog : status, originalAccount: (originalStatus) => originalStatus.account, originalMediaAttachments: (originalStatus) => originalStatus.media_attachments, hydratedContent: (originalStatus) => { let status = originalStatus let content = status.content if (status.tags && status.tags.length) { for (let tag of status.tags) { let {name, url} = tag content = replaceAll(content, url, `/tags/${name}`) } } if (status.emojis && status.emojis.length) { for (let emoji of status.emojis) { let { shortcode, url } = emoji let shortcodeWithColons = `:${shortcode}:` content = replaceAll( content, shortcodeWithColons, `<img class="status-emoji" draggable="false" src="${url}" alt="${shortcodeWithColons}" title="${shortcodeWithColons}" />` ) } } return content } }, methods: { onClickSpoilerButton() { this.set({spoilerShown: !this.get('spoilerShown')}) this.fire('recalculateHeight') }, onClickSensitiveMediaButton() { this.set({sensitiveShown: !this.get('sensitiveShown')}) this.fire('recalculateHeight') } } } </script>