feat: implement shortcut for opening/closing all CWs (#1973)

Fixes #1914
This commit is contained in:
Nolan Lawson 2021-02-27 18:31:53 -08:00 committed by GitHub
parent a21a889f5f
commit 8c09ede2d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 6 deletions

View file

@ -155,6 +155,7 @@ export default {
<li><kbd>p</kbd> das Profil des Verfassers öffnen</li> <li><kbd>p</kbd> das Profil des Verfassers öffnen</li>
<li><kbd>l</kbd> den Link des aktuellen Tröts in einem neuen Tab öffnen</li> <li><kbd>l</kbd> den Link des aktuellen Tröts in einem neuen Tab öffnen</li>
<li><kbd>x</kbd> den Text hinter der Inhaltswarnung anzeigen oder verbergen</li> <li><kbd>x</kbd> den Text hinter der Inhaltswarnung anzeigen oder verbergen</li>
<li><kbd>z</kbd> den Text hinter der Inhaltswarnung anzeigen oder verbergen (alle)</li>
`, `,
mediaHotkeys: ` mediaHotkeys: `
<li><kbd></kbd> / <kbd></kbd> zum nächsten oder vorherigen Inhalt gehen</li> <li><kbd></kbd> / <kbd></kbd> zum nächsten oder vorherigen Inhalt gehen</li>

View file

@ -156,6 +156,7 @@ export default {
<li><kbd>p</kbd> to open the author's profile</li> <li><kbd>p</kbd> to open the author's profile</li>
<li><kbd>l</kbd> to open the card's link in a new tab</li> <li><kbd>l</kbd> to open the card's link in a new tab</li>
<li><kbd>x</kbd> to show or hide text behind content warning</li> <li><kbd>x</kbd> to show or hide text behind content warning</li>
<li><kbd>z</kbd> to show or hide all content warnings in a thread</li>
`, `,
mediaHotkeys: ` mediaHotkeys: `
<li><kbd></kbd> / <kbd></kbd> to go to next or previous</li> <li><kbd></kbd> / <kbd></kbd> to go to next or previous</li>

View file

@ -156,6 +156,7 @@ export default {
<li><kbd>p</kbd> pour voir le profile de l'auteur</li> <li><kbd>p</kbd> pour voir le profile de l'auteur</li>
<li><kbd>l</kbd> pour ouvrir un lien de carte dans un nouvel onglet</li> <li><kbd>l</kbd> pour ouvrir un lien de carte dans un nouvel onglet</li>
<li><kbd>x</kbd> pour afficher ou cacher le texte caché derrière une avertissement</li> <li><kbd>x</kbd> pour afficher ou cacher le texte caché derrière une avertissement</li>
<li><kbd>z</kbd> pour afficher ou cacher toutes les avertissements</li>
`, `,
mediaHotkeys: ` mediaHotkeys: `
<li><kbd></kbd> / <kbd></kbd> pour voir la prochaine ou dernière image</li> <li><kbd></kbd> / <kbd></kbd> pour voir la prochaine ou dernière image</li>

View file

@ -11,6 +11,8 @@
} from '../../_utils/shortcuts' } from '../../_utils/shortcuts'
import { smoothScroll } from '../../_utils/smoothScroll' import { smoothScroll } from '../../_utils/smoothScroll'
import { getScrollContainer } from '../../_utils/scrollContainer' import { getScrollContainer } from '../../_utils/scrollContainer'
import { store } from '../../_store/store'
import { emit } from '../../_utils/eventBus'
const VISIBILITY_CHECK_DELAY_MS = 600 const VISIBILITY_CHECK_DELAY_MS = 600
@ -32,8 +34,10 @@
export default { export default {
data: () => ({ data: () => ({
activeItemChangeTime: 0, activeItemChangeTime: 0,
elements: document.getElementsByClassName('shortcut-list-item') elements: document.getElementsByClassName('shortcut-list-item'),
spoilersShown: false
}), }),
store: () => store,
oncreate () { oncreate () {
addShortcutFallback(scope, this) addShortcutFallback(scope, this)
}, },
@ -45,6 +49,15 @@
if (shouldIgnoreEvent(event)) { if (shouldIgnoreEvent(event)) {
return return
} }
if (event.key === 'z' && this.store.get().currentTimeline.startsWith('status/')) {
// if we're in a thread, toggle all content warnings on or off
event.stopPropagation()
event.preventDefault()
const { spoilersShown } = this.get()
emit('toggleAllSpoilers', !spoilersShown)
this.set({ spoilersShown: !spoilersShown })
return
}
if (event.key === 'j' || event.key === 'ArrowDown') { if (event.key === 'j' || event.key === 'ArrowDown') {
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()

View file

@ -52,12 +52,15 @@
import { registerClickDelegate } from '../../_utils/delegate' import { registerClickDelegate } from '../../_utils/delegate'
import { mark, stop } from '../../_utils/marks' import { mark, stop } from '../../_utils/marks'
import { emojifyText } from '../../_utils/emojifyText' import { emojifyText } from '../../_utils/emojifyText'
import { on } from '../../_utils/eventBus'
import escapeHtml from 'escape-html' import escapeHtml from 'escape-html'
export default { export default {
oncreate () { oncreate () {
this.toggleSpoilers = this.toggleSpoilers.bind(this)
const { elementId } = this.get() const { elementId } = this.get()
registerClickDelegate(this, elementId, () => this.toggleSpoilers()) registerClickDelegate(this, elementId, () => this.toggleSpoilers())
on('toggleAllSpoilers', this, this.toggleSpoilers)
}, },
store: () => store, store: () => store,
components: { components: {
@ -71,13 +74,13 @@
elementId: ({ uuid }) => `spoiler-${uuid}` elementId: ({ uuid }) => `spoiler-${uuid}`
}, },
methods: { methods: {
toggleSpoilers () { toggleSpoilers (shown) {
const { uuid } = this.get()
const { spoilersShown } = this.store.get()
spoilersShown[uuid] = typeof shown === 'undefined' ? !spoilersShown[uuid] : !!shown
this.store.set({ spoilersShown })
requestAnimationFrame(() => { requestAnimationFrame(() => {
mark('clickSpoilerButton') mark('clickSpoilerButton')
const { uuid } = this.get()
const { spoilersShown } = this.store.get()
spoilersShown[uuid] = !spoilersShown[uuid]
this.store.set({ spoilersShown })
this.fire('recalculateHeight') this.fire('recalculateHeight')
stop('clickSpoilerButton') stop('clickSpoilerButton')
}) })

View file

@ -54,6 +54,11 @@ export async function postReplyAs (username, text, inReplyTo) {
inReplyTo, null, false, null, 'public') inReplyTo, null, false, null, 'public')
} }
export async function postReplyWithSpoilerAs (username, text, inReplyTo, spoilerText) {
return postStatus(instanceName, users[username].accessToken, text,
inReplyTo, null, false, spoilerText, 'public')
}
export async function deleteAs (username, statusId) { export async function deleteAs (username, statusId) {
return deleteStatus(instanceName, users[username].accessToken, statusId) return deleteStatus(instanceName, users[username].accessToken, statusId)
} }

View file

@ -0,0 +1,41 @@
import {
getNthStatus, getNthStatusContent, getNthStatusSpoiler, getUrl, sleep
} from '../utils'
import { loginAsFoobar } from '../roles'
import { postReplyWithSpoilerAs, postWithSpoilerAndPrivacyAs } from '../serverActions'
fixture`137-shortcut-spoiler.js`
.page`http://localhost:4002`
test('Can toggle all spoilers with shortcut', async t => {
const { id: statusId1 } = await postWithSpoilerAndPrivacyAs('admin', 'the content', 'yolo', 'public')
const { id: statusId2 } = await postReplyWithSpoilerAs('admin', 'the content', statusId1, 'haha')
await postReplyWithSpoilerAs('admin', 'the content', statusId2, 'wheeee')
await sleep(500)
await loginAsFoobar(t)
await t
.expect(getNthStatusSpoiler(1).innerText).eql('wheeee')
.click(getNthStatus(1))
.expect(getUrl()).contains('/statuses')
.expect(getNthStatusSpoiler(1).innerText).eql('yolo')
.expect(getNthStatusSpoiler(2).innerText).eql('haha')
.expect(getNthStatusSpoiler(3).innerText).eql('wheeee')
.expect(getNthStatusContent(1).visible).notOk()
.expect(getNthStatusContent(2).visible).notOk()
.expect(getNthStatusContent(3).visible).notOk()
await sleep(500)
await t
.pressKey('z')
.expect(getNthStatusContent(1).innerText).contains('the content')
.expect(getNthStatusContent(2).innerText).contains('the content')
.expect(getNthStatusContent(3).innerText).contains('the content')
.expect(getNthStatusContent(1).visible).ok()
.expect(getNthStatusContent(2).visible).ok()
.expect(getNthStatusContent(3).visible).ok()
await sleep(500)
await t
.pressKey('z')
.expect(getNthStatusContent(1).visible).notOk()
.expect(getNthStatusContent(2).visible).notOk()
.expect(getNthStatusContent(3).visible).notOk()
})