refactor: use ids instead of attrs for delegate/shortcut/focus (#1035)
* refactor: use ids instead of attrs for delegate/shortcut/focus fixes #1034 * console log on error * fix test
This commit is contained in:
parent
c9ca605cfe
commit
547ee14f88
|
@ -1,4 +1,3 @@
|
||||||
{#if delegateKey}
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
title={label}
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
|
@ -6,27 +5,12 @@
|
||||||
aria-hidden={ariaHidden}
|
aria-hidden={ariaHidden}
|
||||||
class={computedClass}
|
class={computedClass}
|
||||||
{disabled}
|
{disabled}
|
||||||
delegate-key={delegateKey}
|
ref:node
|
||||||
focus-key={focusKey || ''} >
|
>
|
||||||
<svg class="icon-button-svg {svgClassName || ''}" ref:svg>
|
<svg class="icon-button-svg {svgClassName || ''}" ref:svg>
|
||||||
<use xlink:href={href} />
|
<use xlink:href={href} />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
|
||||||
<button type="button"
|
|
||||||
title={label}
|
|
||||||
aria-label={label}
|
|
||||||
aria-pressed={pressable ? !!pressed : void 0}
|
|
||||||
aria-hidden={ariaHidden}
|
|
||||||
class={computedClass}
|
|
||||||
focus-key={focusKey || ''}
|
|
||||||
{disabled}
|
|
||||||
on:click >
|
|
||||||
<svg class="icon-button-svg {svgClassName || ''}" ref:svg>
|
|
||||||
<use xlink:href={href} />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<style>
|
<style>
|
||||||
.icon-button {
|
.icon-button {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
@ -110,18 +94,34 @@
|
||||||
import { animate } from '../_utils/animate'
|
import { animate } from '../_utils/animate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
oncreate () {
|
||||||
|
let { clickListener, elementId } = this.get()
|
||||||
|
if (clickListener) {
|
||||||
|
this.onClick = this.onClick.bind(this)
|
||||||
|
this.refs.node.addEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
if (elementId) {
|
||||||
|
this.refs.node.setAttribute('id', elementId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ondestroy () {
|
||||||
|
let { clickListener } = this.get()
|
||||||
|
if (clickListener) {
|
||||||
|
this.refs.node.removeEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
big: false,
|
big: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
svgClassName: void 0,
|
svgClassName: void 0,
|
||||||
focusKey: void 0,
|
elementId: void 0,
|
||||||
pressable: false,
|
pressable: false,
|
||||||
pressed: false,
|
pressed: false,
|
||||||
className: void 0,
|
className: void 0,
|
||||||
delegateKey: void 0,
|
|
||||||
sameColorWhenPressed: false,
|
sameColorWhenPressed: false,
|
||||||
ariaHidden: false
|
ariaHidden: false,
|
||||||
|
clickListener: true
|
||||||
}),
|
}),
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -145,6 +145,9 @@
|
||||||
}
|
}
|
||||||
let svg = this.refs.svg
|
let svg = this.refs.svg
|
||||||
animate(svg, animation)
|
animate(svg, animation)
|
||||||
|
},
|
||||||
|
onClick (e) {
|
||||||
|
this.fire('click', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
|
|
||||||
const VISIBILITY_CHECK_DELAY_MS = 600
|
const VISIBILITY_CHECK_DELAY_MS = 600
|
||||||
|
|
||||||
const keyToElement = key => document.querySelector(`[shortcut-key=${JSON.stringify(key)}]`)
|
const keyToElement = key => document.getElementById(key)
|
||||||
const elementToKey = element => element.getAttribute('shortcut-key')
|
const elementToKey = element => element.getAttribute('id')
|
||||||
const scope = 'global'
|
const scope = 'global'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
if (!activeElement) {
|
if (!activeElement) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let activeItem = activeElement.getAttribute('shortcut-key')
|
let activeItem = activeElement.getAttribute('id')
|
||||||
if (!activeItem) {
|
if (!activeItem) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
preventScroll: true
|
preventScroll: true
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error('Ignored focus error', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{#if type === 'video'}
|
{#if type === 'video'}
|
||||||
<button type="button"
|
<button id={elementId}
|
||||||
|
type="button"
|
||||||
class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'}"
|
class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'}"
|
||||||
aria-label="Play video: {description}"
|
aria-label="Play video: {description}"
|
||||||
delegate-key={delegateKey}
|
|
||||||
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
||||||
<PlayVideoIcon />
|
<PlayVideoIcon />
|
||||||
<LazyImage
|
<LazyImage
|
||||||
|
@ -17,11 +17,11 @@
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button"
|
<button id={elementId}
|
||||||
|
type="button"
|
||||||
class="show-image-button {$largeInlineMedia ? '' : 'fixed-size'}"
|
class="show-image-button {$largeInlineMedia ? '' : 'fixed-size'}"
|
||||||
aria-label="Show image: {description}"
|
aria-label="Show image: {description}"
|
||||||
title={description}
|
title={description}
|
||||||
delegate-key={delegateKey}
|
|
||||||
on:mouseover="set({mouseover: event})"
|
on:mouseover="set({mouseover: event})"
|
||||||
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
||||||
{#if type === 'gifv' && $autoplayGifs}
|
{#if type === 'gifv' && $autoplayGifs}
|
||||||
|
@ -90,8 +90,8 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
let { delegateKey } = this.get()
|
let { elementId } = this.get()
|
||||||
registerClickDelegate(this, delegateKey, () => this.onClick())
|
registerClickDelegate(this, elementId, () => this.onClick())
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
focus: ({ meta }) => meta && meta.focus,
|
focus: ({ meta }) => meta && meta.focus,
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
originalWidth: ({ original }) => original && original.width,
|
originalWidth: ({ original }) => original && original.width,
|
||||||
originalHeight: ({ original }) => original && original.height,
|
originalHeight: ({ original }) => original && original.height,
|
||||||
noNativeWidthHeight: ({ smallWidth, smallHeight }) => typeof smallWidth !== 'number' || typeof smallHeight !== 'number',
|
noNativeWidthHeight: ({ smallWidth, smallHeight }) => typeof smallWidth !== 'number' || typeof smallHeight !== 'number',
|
||||||
delegateKey: ({ media, uuid }) => `media-${uuid}-${media.id}`,
|
elementId: ({ media, uuid }) => `media-${uuid}-${media.id}`,
|
||||||
description: ({ media }) => media.description || '',
|
description: ({ media }) => media.description || '',
|
||||||
previewUrl: ({ media }) => media.preview_url,
|
previewUrl: ({ media }) => media.preview_url,
|
||||||
url: ({ media }) => media.url,
|
url: ({ media }) => media.url,
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
{status} {notification} {enableShortcuts} on:recalculateHeight
|
{status} {notification} {enableShortcuts} on:recalculateHeight
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<article class={className}
|
<article id={elementId}
|
||||||
|
class={className}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-posinset={index}
|
aria-posinset={index}
|
||||||
aria-setsize={length}
|
aria-setsize={length}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
focus-key={uuid}
|
|
||||||
shortcut-key={uuid}
|
|
||||||
on:focus="onFocus()"
|
on:focus="onFocus()"
|
||||||
on:blur="onBlur()"
|
on:blur="onBlur()"
|
||||||
ref:article
|
ref:article
|
||||||
|
@ -17,8 +16,8 @@
|
||||||
<StatusHeader {notification} {notificationId} {status} {statusId} {timelineType}
|
<StatusHeader {notification} {notificationId} {status} {statusId} {timelineType}
|
||||||
{account} {accountId} {uuid} isStatusInNotification="true" />
|
{account} {accountId} {uuid} isStatusInNotification="true" />
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
<Shortcut scope={uuid} key="p" on:pressed="openAuthorProfile()" />
|
<Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" />
|
||||||
<Shortcut scope={uuid} key="m" on:pressed="mentionAuthor()" />
|
<Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" />
|
||||||
{/if}
|
{/if}
|
||||||
</article>
|
</article>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -69,6 +68,8 @@
|
||||||
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => {
|
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => {
|
||||||
return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}`
|
return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}`
|
||||||
},
|
},
|
||||||
|
elementId: ({ uuid }) => `notification-${uuid}`,
|
||||||
|
shortcutScope: ({ elementId }) => elementId,
|
||||||
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
|
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
|
||||||
!status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}`
|
!status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}`
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<article class={className}
|
<article id={elementId}
|
||||||
|
class={className}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
delegate-key={uuid}
|
|
||||||
focus-key={uuid}
|
|
||||||
shortcut-key={uuid}
|
|
||||||
aria-posinset={index}
|
aria-posinset={index}
|
||||||
aria-setsize={length}
|
aria-setsize={length}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
|
@ -41,9 +39,9 @@
|
||||||
{/if}
|
{/if}
|
||||||
</article>
|
</article>
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
<Shortcut scope={uuid} key="o" on:pressed="open()" />
|
<Shortcut scope={shortcutScope} key="o" on:pressed="open()" />
|
||||||
<Shortcut scope={uuid} key="p" on:pressed="openAuthorProfile()" />
|
<Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" />
|
||||||
<Shortcut scope={uuid} key="m" on:pressed="mentionAuthor()" />
|
<Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -146,11 +144,11 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
let { uuid, isStatusInOwnThread, showContent } = this.get()
|
let { elementId, isStatusInOwnThread, showContent } = this.get()
|
||||||
let { disableTapOnStatus } = this.store.get()
|
let { disableTapOnStatus } = this.store.get()
|
||||||
if (!isStatusInOwnThread && !disableTapOnStatus) {
|
if (!isStatusInOwnThread && !disableTapOnStatus) {
|
||||||
// the whole <article> is clickable in this case
|
// the whole <article> is clickable in this case
|
||||||
registerClickDelegate(this, uuid, (e) => this.onClickOrKeydown(e))
|
registerClickDelegate(this, elementId, (e) => this.onClickOrKeydown(e))
|
||||||
}
|
}
|
||||||
if (!showContent) {
|
if (!showContent) {
|
||||||
scheduleIdleTask(() => {
|
scheduleIdleTask(() => {
|
||||||
|
@ -248,6 +246,8 @@
|
||||||
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => (
|
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => (
|
||||||
`${$currentInstance}/${timelineType}/${timelineValue}/${notificationId || ''}/${statusId}`
|
`${$currentInstance}/${timelineType}/${timelineValue}/${notificationId || ''}/${statusId}`
|
||||||
),
|
),
|
||||||
|
elementId: ({ uuid }) => `status-${uuid}`,
|
||||||
|
shortcutScope: ({ elementId }) => elementId,
|
||||||
isStatusInOwnThread: ({ timelineType, timelineValue, originalStatusId }) => (
|
isStatusInOwnThread: ({ timelineType, timelineValue, originalStatusId }) => (
|
||||||
(timelineType === 'status' || timelineType === 'reply') && timelineValue === originalStatusId
|
(timelineType === 'status' || timelineType === 'reply') && timelineValue === originalStatusId
|
||||||
),
|
),
|
||||||
|
@ -297,7 +297,7 @@
|
||||||
account, accountId, uuid, isStatusInNotification, isStatusInOwnThread,
|
account, accountId, uuid, isStatusInNotification, isStatusInOwnThread,
|
||||||
originalAccount, originalAccountId, spoilerShown, visibility, replyShown,
|
originalAccount, originalAccountId, spoilerShown, visibility, replyShown,
|
||||||
replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId,
|
replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId,
|
||||||
createdAtDate, timeagoFormattedDate, enableShortcuts, absoluteFormattedDate }) => ({
|
createdAtDate, timeagoFormattedDate, enableShortcuts, absoluteFormattedDate, shortcutScope }) => ({
|
||||||
notification,
|
notification,
|
||||||
notificationId,
|
notificationId,
|
||||||
status,
|
status,
|
||||||
|
@ -321,7 +321,8 @@
|
||||||
createdAtDate,
|
createdAtDate,
|
||||||
timeagoFormattedDate,
|
timeagoFormattedDate,
|
||||||
enableShortcuts,
|
enableShortcuts,
|
||||||
absoluteFormattedDate
|
absoluteFormattedDate,
|
||||||
|
shortcutScope
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<a class="status-author-name {isStatusInNotification ? 'status-in-notification' : '' } {isStatusInOwnThread ? 'status-in-own-thread' : ''}"
|
<a id={elementId}
|
||||||
|
class="status-author-name {isStatusInNotification ? 'status-in-notification' : '' } {isStatusInOwnThread ? 'status-in-own-thread' : ''}"
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
href="/accounts/{originalAccountId}"
|
href="/accounts/{originalAccountId}"
|
||||||
title="{'@' + originalAccount.acct}"
|
title="{'@' + originalAccount.acct}"
|
||||||
focus-key={focusKey}
|
|
||||||
>
|
>
|
||||||
<AccountDisplayName account={originalAccount} />
|
<AccountDisplayName account={originalAccount} />
|
||||||
</a>
|
</a>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
focusKey: ({ uuid }) => `status-author-name-${uuid}`
|
elementId: ({ uuid }) => `status-author-name-${uuid}`
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AccountDisplayName
|
AccountDisplayName
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
for (let tag of tags) {
|
for (let tag of tags) {
|
||||||
if (anchor.getAttribute('href').endsWith(`/tags/${tag.name}`)) {
|
if (anchor.getAttribute('href').endsWith(`/tags/${tag.name}`)) {
|
||||||
anchor.setAttribute('href', `/tags/${tag.name}`)
|
anchor.setAttribute('href', `/tags/${tag.name}`)
|
||||||
anchor.setAttribute('focus-key', `status-content-link-${uuid}-${++count}`)
|
anchor.setAttribute('id', `status-content-link-${uuid}-${++count}`)
|
||||||
anchor.removeAttribute('target')
|
anchor.removeAttribute('target')
|
||||||
anchor.removeAttribute('rel')
|
anchor.removeAttribute('rel')
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
if (anchor.getAttribute('href') === mention.url) {
|
if (anchor.getAttribute('href') === mention.url) {
|
||||||
anchor.setAttribute('href', `/accounts/${mention.id}`)
|
anchor.setAttribute('href', `/accounts/${mention.id}`)
|
||||||
anchor.setAttribute('title', `@${mention.acct}`)
|
anchor.setAttribute('title', `@${mention.acct}`)
|
||||||
anchor.setAttribute('focus-key', `status-content-link-${uuid}-${++count}`)
|
anchor.setAttribute('id', `status-content-link-${uuid}-${++count}`)
|
||||||
anchor.removeAttribute('target')
|
anchor.removeAttribute('target')
|
||||||
anchor.removeAttribute('rel')
|
anchor.removeAttribute('rel')
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
Pinned toot
|
Pinned toot
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/accounts/{accountId}"
|
<a id={elementId}
|
||||||
|
href="/accounts/{accountId}"
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
class="status-header-author"
|
class="status-header-author"
|
||||||
title="{'@' + account.acct}"
|
title="{'@' + account.acct}"
|
||||||
focus-key={focusKey} >
|
>
|
||||||
<AccountDisplayName {account} />
|
<AccountDisplayName {account} />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -112,7 +113,7 @@
|
||||||
AccountDisplayName
|
AccountDisplayName
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
focusKey: ({ uuid }) => `status-header-${uuid}`,
|
elementId: ({ uuid }) => `status-header-${uuid}`,
|
||||||
icon: ({ notification, status, timelineType }) => {
|
icon: ({ notification, status, timelineType }) => {
|
||||||
if (timelineType === 'pinned') {
|
if (timelineType === 'pinned') {
|
||||||
return '#fa-thumb-tack'
|
return '#fa-thumb-tack'
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<div class={computedClass} style={customSize}>
|
<div class={computedClass} style={customSize}>
|
||||||
<div class="status-sensitive-inner-div">
|
<div class="status-sensitive-inner-div">
|
||||||
{#if sensitiveShown}
|
{#if sensitiveShown}
|
||||||
<button type="button"
|
<button id={elementId}
|
||||||
|
type="button"
|
||||||
class="status-sensitive-media-button"
|
class="status-sensitive-media-button"
|
||||||
aria-label="Hide sensitive media"
|
aria-label="Hide sensitive media" >
|
||||||
delegate-key={delegateKey} >
|
|
||||||
<div class="svg-wrapper">
|
<div class="svg-wrapper">
|
||||||
<svg class="status-sensitive-media-svg">
|
<svg class="status-sensitive-media-svg">
|
||||||
<use xlink:href="#fa-eye-slash" />
|
<use xlink:href="#fa-eye-slash" />
|
||||||
|
@ -14,10 +14,10 @@
|
||||||
</button>
|
</button>
|
||||||
<MediaAttachments {mediaAttachments} {sensitive} {uuid} />
|
<MediaAttachments {mediaAttachments} {sensitive} {uuid} />
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button"
|
<button id={elementId}
|
||||||
|
type="button"
|
||||||
class="status-sensitive-media-button"
|
class="status-sensitive-media-button"
|
||||||
aria-label="Show sensitive media"
|
aria-label="Show sensitive media" >
|
||||||
delegate-key={delegateKey} >
|
|
||||||
|
|
||||||
<div class="status-sensitive-media-warning">
|
<div class="status-sensitive-media-warning">
|
||||||
Sensitive content. Click to show.
|
Sensitive content. Click to show.
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
<Shortcut scope={uuid} key="y" on:pressed="toggleSensitiveMedia()"/>
|
<Shortcut scope={shortcutScope} key="y" on:pressed="toggleSensitiveMedia()"/>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<MediaAttachments {mediaAttachments} {sensitive} {uuid} />
|
<MediaAttachments {mediaAttachments} {sensitive} {uuid} />
|
||||||
|
@ -158,8 +158,8 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
let { delegateKey } = this.get()
|
let { elementId } = this.get()
|
||||||
registerClickDelegate(this, delegateKey, () => this.toggleSensitiveMedia())
|
registerClickDelegate(this, elementId, () => this.toggleSensitiveMedia())
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MediaAttachments,
|
MediaAttachments,
|
||||||
|
@ -177,7 +177,7 @@
|
||||||
sensitive: ({ originalStatus, $markMediaAsSensitive, $neverMarkMediaAsSensitive }) => (
|
sensitive: ({ originalStatus, $markMediaAsSensitive, $neverMarkMediaAsSensitive }) => (
|
||||||
!$neverMarkMediaAsSensitive && ($markMediaAsSensitive || originalStatus.sensitive)
|
!$neverMarkMediaAsSensitive && ($markMediaAsSensitive || originalStatus.sensitive)
|
||||||
),
|
),
|
||||||
delegateKey: ({ uuid }) => `sensitive-${uuid}`,
|
elementId: ({ uuid }) => `sensitive-${uuid}`,
|
||||||
customSize: ({ $largeInlineMedia, mediaAttachments }) => {
|
customSize: ({ $largeInlineMedia, mediaAttachments }) => {
|
||||||
if ($largeInlineMedia || !mediaAttachments || mediaAttachments.length < 5) {
|
if ($largeInlineMedia || !mediaAttachments || mediaAttachments.length < 5) {
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
<!-- empty space -->
|
<!-- empty space -->
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/accounts/{mention.id}"
|
<a id="status-mention-link-{uuid}-{mention.id}"
|
||||||
|
href="/accounts/{mention.id}"
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
title="@{mention.acct}"
|
title="@{mention.acct}"
|
||||||
focus-key="status-mention-link-{uuid}-{mention.id}">
|
>
|
||||||
@{mention.username}
|
@{mention.username}
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<a class="status-relative-date {isStatusInNotification ? 'status-in-notification' : '' }"
|
<a id={elementId}
|
||||||
|
class="status-relative-date {isStatusInNotification ? 'status-in-notification' : '' }"
|
||||||
href="/statuses/{originalStatusId}"
|
href="/statuses/{originalStatusId}"
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
focus-key={focusKey}
|
|
||||||
>
|
>
|
||||||
<time datetime={createdAtDate} title={absoluteFormattedDate}
|
<time datetime={createdAtDate} title={absoluteFormattedDate}
|
||||||
aria-label="{timeagoFormattedDate} – click to show thread">
|
aria-label="{timeagoFormattedDate} – click to show thread">
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
focusKey: ({ uuid }) => `status-relative-date-${uuid}`
|
elementId: ({ uuid }) => `status-relative-date-${uuid}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<a class="status-sidebar size-{size}"
|
<a id={elementId}
|
||||||
|
class="status-sidebar size-{size}"
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
href="/accounts/{originalAccountId}"
|
href="/accounts/{originalAccountId}"
|
||||||
focus-key={focusKey}
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<Avatar account={originalAccount}
|
<Avatar account={originalAccount}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
Avatar
|
Avatar
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
focusKey: ({ uuid }) => `status-author-avatar-${uuid}`,
|
elementId: ({ uuid }) => `status-author-avatar-${uuid}`,
|
||||||
size: ({ isStatusInOwnThread }) => isStatusInOwnThread ? 'medium' : 'small'
|
size: ({ isStatusInOwnThread }) => isStatusInOwnThread ? 'medium' : 'small'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
<p>{@html massagedSpoilerText}</p>
|
<p>{@html 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 id={elementId} type="button" >
|
||||||
{spoilerShown ? 'Show less' : 'Show more'}
|
{spoilerShown ? 'Show less' : 'Show more'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
<Shortcut scope={uuid} key="x" on:pressed="toggleSpoilers()"/>
|
<Shortcut scope={shortcutScope} key="x" on:pressed="toggleSpoilers()"/>
|
||||||
{/if}
|
{/if}
|
||||||
<style>
|
<style>
|
||||||
.status-spoiler {
|
.status-spoiler {
|
||||||
|
@ -56,8 +56,8 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
let { delegateKey } = this.get()
|
let { elementId } = this.get()
|
||||||
registerClickDelegate(this, delegateKey, () => this.toggleSpoilers())
|
registerClickDelegate(this, elementId, () => this.toggleSpoilers())
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
components: {
|
components: {
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
spoilerText = escapeHtml(spoilerText)
|
spoilerText = escapeHtml(spoilerText)
|
||||||
return emojifyText(spoilerText, emojis, $autoplayGifs)
|
return emojifyText(spoilerText, emojis, $autoplayGifs)
|
||||||
},
|
},
|
||||||
delegateKey: ({ uuid }) => `spoiler-${uuid}`
|
elementId: ({ uuid }) => `spoiler-${uuid}`
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleSpoilers () {
|
toggleSpoilers () {
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
pressable="true"
|
pressable="true"
|
||||||
pressed={replyShown}
|
pressed={replyShown}
|
||||||
href={replyIcon}
|
href={replyIcon}
|
||||||
delegateKey={replyKey}
|
clickListener={false}
|
||||||
focusKey={replyKey}
|
elementId={replyKey}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label={reblogLabel}
|
label={reblogLabel}
|
||||||
|
@ -14,7 +14,8 @@
|
||||||
pressed={reblogged}
|
pressed={reblogged}
|
||||||
disabled={reblogDisabled}
|
disabled={reblogDisabled}
|
||||||
href={reblogIcon}
|
href={reblogIcon}
|
||||||
delegateKey={reblogKey}
|
clickListener={false}
|
||||||
|
elementId={reblogKey}
|
||||||
ref:reblogIcon
|
ref:reblogIcon
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -22,19 +23,21 @@
|
||||||
pressable="true"
|
pressable="true"
|
||||||
pressed={favorited}
|
pressed={favorited}
|
||||||
href="#fa-star"
|
href="#fa-star"
|
||||||
delegateKey={favoriteKey}
|
clickListener={false}
|
||||||
|
elementId={favoriteKey}
|
||||||
ref:favoriteIcon
|
ref:favoriteIcon
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Show more options"
|
label="Show more options"
|
||||||
href="#fa-ellipsis-h"
|
href="#fa-ellipsis-h"
|
||||||
delegateKey={optionsKey}
|
clickListener={false}
|
||||||
|
elementId={optionsKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
<Shortcut scope={uuid} key="f" on:pressed="toggleFavorite()"/>
|
<Shortcut scope={shortcutScope} key="f" on:pressed="toggleFavorite()"/>
|
||||||
<Shortcut scope={uuid} key="r" on:pressed="reply()"/>
|
<Shortcut scope={shortcutScope} key="r" on:pressed="reply()"/>
|
||||||
<Shortcut scope={uuid} key="b" on:pressed="reblog()"/>
|
<Shortcut scope={shortcutScope} key="b" on:pressed="reblog()"/>
|
||||||
{/if}
|
{/if}
|
||||||
<style>
|
<style>
|
||||||
.status-toolbar {
|
.status-toolbar {
|
||||||
|
|
|
@ -241,17 +241,14 @@
|
||||||
try {
|
try {
|
||||||
let { currentInstance } = this.store.get()
|
let { currentInstance } = this.store.get()
|
||||||
let { timeline } = this.get()
|
let { timeline } = this.get()
|
||||||
let lastFocusedElementSelector
|
let lastFocusedElementId
|
||||||
let activeElement = e.target
|
let activeElement = e.target
|
||||||
if (activeElement) {
|
if (activeElement) {
|
||||||
let focusKey = activeElement.getAttribute('focus-key')
|
lastFocusedElementId = activeElement.getAttribute('id')
|
||||||
if (focusKey) {
|
|
||||||
lastFocusedElementSelector = `[focus-key=${JSON.stringify(focusKey)}]`
|
|
||||||
}
|
}
|
||||||
}
|
console.log('saving focus to ', lastFocusedElementId)
|
||||||
console.log('saving focus to ', lastFocusedElementSelector)
|
|
||||||
this.store.setForTimeline(currentInstance, timeline, {
|
this.store.setForTimeline(currentInstance, timeline, {
|
||||||
lastFocusedElementSelector
|
lastFocusedElementId: lastFocusedElementId
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('unable to save focus', err)
|
console.error('unable to save focus', err)
|
||||||
|
@ -267,21 +264,21 @@
|
||||||
let { currentInstance } = this.store.get()
|
let { currentInstance } = this.store.get()
|
||||||
let { timeline } = this.get()
|
let { timeline } = this.get()
|
||||||
this.store.setForTimeline(currentInstance, timeline, {
|
this.store.setForTimeline(currentInstance, timeline, {
|
||||||
lastFocusedElementSelector: null
|
lastFocusedElementId: null
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('unable to clear focus', err)
|
console.error('unable to clear focus', err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
restoreFocus () {
|
restoreFocus () {
|
||||||
let { lastFocusedElementSelector } = this.store.get()
|
let { lastFocusedElementId } = this.store.get()
|
||||||
if (!lastFocusedElementSelector) {
|
if (!lastFocusedElementId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('restoreFocus', lastFocusedElementSelector)
|
console.log('restoreFocus', lastFocusedElementId)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
let element = document.querySelector(lastFocusedElementSelector)
|
let element = document.getElementById(lastFocusedElementId)
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus()
|
element.focus()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ function computeForTimeline (store, key, defaultValue) {
|
||||||
export function timelineComputations (store) {
|
export function timelineComputations (store) {
|
||||||
computeForTimeline(store, 'timelineItemIds', null)
|
computeForTimeline(store, 'timelineItemIds', null)
|
||||||
computeForTimeline(store, 'runningUpdate', false)
|
computeForTimeline(store, 'runningUpdate', false)
|
||||||
computeForTimeline(store, 'lastFocusedElementSelector', null)
|
computeForTimeline(store, 'lastFocusedElementId', null)
|
||||||
computeForTimeline(store, 'ignoreBlurEvents', false)
|
computeForTimeline(store, 'ignoreBlurEvents', false)
|
||||||
computeForTimeline(store, 'itemIdsToAdd', null)
|
computeForTimeline(store, 'itemIdsToAdd', null)
|
||||||
computeForTimeline(store, 'showHeader', false)
|
computeForTimeline(store, 'showHeader', false)
|
||||||
|
|
|
@ -15,7 +15,7 @@ function onEvent (e) {
|
||||||
let key
|
let key
|
||||||
let element = target
|
let element = target
|
||||||
while (element) {
|
while (element) {
|
||||||
if ((key = element.getAttribute('delegate-key'))) {
|
if ((key = element.getAttribute('id'))) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
element = element.parentElement
|
element = element.parentElement
|
||||||
|
|
|
@ -163,8 +163,12 @@ export const isNthStatusActive = (idx) => (exec(() => {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const isActiveStatusPinned = exec(() => {
|
export const isActiveStatusPinned = exec(() => {
|
||||||
return document.activeElement &&
|
let el = document.activeElement
|
||||||
document.activeElement.getAttribute('delegate-key').includes('pinned')
|
return el &&
|
||||||
|
(
|
||||||
|
(el.parentElement.getAttribute('class') || '').includes('pinned') ||
|
||||||
|
(el.parentElement.parentElement.getAttribute('class') || '').includes('pinned')
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const scrollToBottom = exec(() => {
|
export const scrollToBottom = exec(() => {
|
||||||
|
|
Loading…
Reference in a new issue