extraButtons implementation

This commit is contained in:
Henry Jameson 2025-01-11 20:02:53 +02:00
parent 08f8b975b6
commit eb7406c663
5 changed files with 182 additions and 51 deletions

View file

@ -166,26 +166,24 @@
</div> </div>
</template> </template>
<template #trigger> <template #trigger>
<span class="button-unstyled popover-trigger"> <FALayers class="fa-old-padding-layer">
<FALayers class="fa-old-padding-layer"> <FAIcon
<FAIcon class="fa-scale-110 "
class="fa-scale-110 " icon="ellipsis-h"
icon="ellipsis-h" />
/> <FAIcon
<FAIcon v-show="!expanded"
v-show="!expanded" class="focus-marker"
class="focus-marker" transform="shrink-6 up-8 right-16"
transform="shrink-6 up-8 right-16" icon="plus"
icon="plus" />
/> <FAIcon
<FAIcon v-show="expanded"
v-show="expanded" class="focus-marker"
class="focus-marker" transform="shrink-6 up-8 right-16"
transform="shrink-6 up-8 right-16" icon="times"
icon="times" />
/> </FALayers>
</FALayers>
</span>
<teleport to="#modal"> <teleport to="#modal">
<ConfirmModal <ConfirmModal
v-if="showingDeleteDialog" v-if="showingDeleteDialog"

View file

@ -264,13 +264,11 @@
.status-actions { .status-actions {
position: relative; position: relative;
width: 100%; width: 100%;
display: flex; display: grid;
grid-template-columns: 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: column;
margin-top: var(--status-margin); margin-top: var(--status-margin);
> * {
max-width: 4em;
flex: 1;
}
} }
.muted { .muted {

View file

@ -1,17 +1,52 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { mapState } from 'vuex'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import Popover from 'src/components/popover/popover.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faRetweet,
faPlus, faPlus,
faMinus, faMinus,
faCheck faCheck,
faTimes,
faReply,
faRetweet,
faStar,
faSmileBeam,
faEllipsisH,
faBookmark,
faEyeSlash,
faThumbtack,
faShareAlt,
faExternalLinkAlt,
faHistory
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import {
faStar as faStarRegular
} from '@fortawesome/free-regular-svg-icons'
library.add( library.add(
faRetweet,
faPlus, faPlus,
faMinus, faMinus,
faCheck faCheck,
faTimes,
faReply,
faRetweet,
faStar,
faStarRegular,
faSmileBeam,
faEllipsisH,
faBookmark,
faEyeSlash,
faThumbtack,
faShareAlt,
faExternalLinkAlt,
faHistory
) )
const PRIVATE_SCOPES = new Set(['private', 'direct']) const PRIVATE_SCOPES = new Set(['private', 'direct'])
const PUBLIC_SCOPES = new Set(['public', 'unlisted']) const PUBLIC_SCOPES = new Set(['public', 'unlisted'])
@ -27,6 +62,8 @@ const BUTTONS = [{
anon: true, anon: true,
anonLink: true, anonLink: true,
toggleable: true, toggleable: true,
closeIndicator: 'times',
activeIndicator: 'none',
action ({ emit }) { action ({ emit }) {
emit('toggleReplying') emit('toggleReplying')
return Promise.resolve() return Promise.resolve()
@ -230,7 +267,10 @@ const BUTTONS = [{
} }
}].map(button => { }].map(button => {
return Object.fromEntries( return Object.fromEntries(
Object.entries(button).map(([k, v]) => [k, typeof v === 'function' ? v : () => v]) Object.entries(button).map(([k, v]) => [
k,
(typeof v === 'function' || k === 'name') ? v : () => v
])
) )
}) })
@ -243,15 +283,26 @@ const StatusActionButtons = {
currentConfirmTitle: '', currentConfirmTitle: '',
currentConfirmOkText: '', currentConfirmOkText: '',
currentConfirmCancelText: '', currentConfirmCancelText: '',
currentConfirmAction: () => {} currentConfirmAction: () => {},
randomSeed: genRandomSeed()
} }
}, },
components: { components: {
Popover,
ConfirmModal ConfirmModal
}, },
computed: { computed: {
...mapState({
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedStatusActions)
}),
buttons () { buttons () {
return BUTTONS.filter(x => x.if(this.funcArg)) return BUTTONS.filter(x => x.if ? x.if(this.funcArg) : true)
},
quickButtons () {
return this.buttons.filter(x => this.pinnedItems.has(x.name))
},
extraButtons () {
return this.buttons.filter(x => !this.pinnedItems.has(x.name))
}, },
funcArg () { funcArg () {
return { return {
@ -265,6 +316,15 @@ const StatusActionButtons = {
currentUser: this.$store.state.users.currentUser, currentUser: this.$store.state.users.currentUser,
loggedIn: !!this.$store.state.users.currentUser loggedIn: !!this.$store.state.users.currentUser
} }
},
triggerAttrs () {
return {
title: this.$t('status.more_actions'),
id: `popup-trigger-${this.randomSeed}`,
'aria-controls': `popup-menu-${this.randomSeed}`,
'aria-expanded': this.expanded,
'aria-haspopup': 'menu'
}
} }
}, },
methods: { methods: {
@ -272,7 +332,7 @@ const StatusActionButtons = {
this.doActionReal(button) this.doActionReal(button)
}, },
doActionReal (button) { doActionReal (button) {
button.action(this.funcArg(button)) button.action(this.funcArg)
.then(() => this.$emit('onSuccess')) .then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error)) .catch(err => this.$emit('onError', err.error.error))
}, },
@ -287,8 +347,8 @@ const StatusActionButtons = {
}, },
getClass (button) { getClass (button) {
return { return {
[button.name() + '-button']: true, [button.name + '-button']: true,
'-active': button.active?.(this.funcArg()), '-active': button.active?.(this.funcArg),
'-interactive': !!this.$store.state.users.currentUser '-interactive': !!this.$store.state.users.currentUser
} }
}, },

View file

@ -3,7 +3,7 @@
<span class="quick-action-buttons"> <span class="quick-action-buttons">
<span <span
class="quick-action" class="quick-action"
v-for="button in buttons" v-for="button in quickButtons"
:key="button.name" :key="button.name"
> >
<component <component
@ -23,16 +23,22 @@
/> />
<template v-if="button.toggleable?.(funcArg) && button.active"> <template v-if="button.toggleable?.(funcArg) && button.active">
<FAIcon <FAIcon
v-show="!button.active(funcArg)" v-if="button.active(funcArg)"
class="focus-marker" class="active-marker"
transform="shrink-6 up-9 right-17" transform="shrink-6 up-9 right-12"
icon="plus" :icon="button.activeIndicator?.(funcArg) || 'check'"
/> />
<FAIcon <FAIcon
v-show="button.active(funcArg)" v-if="!button.active(funcArg)"
class="focus-marker" class="focus-marker"
transform="shrink-6 up-9 right-17" transform="shrink-6 up-9 right-12"
icon="times" :icon="button.openIndicator?.(funcArg) || 'plus'"
/>
<FAIcon
v-else
class="focus-marker"
transform="shrink-6 up-9 right-12"
:icon="button.closeIndicator?.(funcArg) || 'minus'"
/> />
</template> </template>
</FALayers> </FALayers>
@ -44,7 +50,55 @@
{{ button.counter?.(funcArg) }} {{ button.counter?.(funcArg) }}
</span> </span>
</span> </span>
<Popover
trigger="click"
:trigger-attrs="triggerAttrs"
:tabindex="0"
placement="top"
:offset="{ y: 5 }"
:bound-to="{ x: 'container2' }"
remove-padding
@show="onShow"
@close="onClose"
>
<template #trigger>
<span class="popover-trigger">
<FALayers class="fa-old-padding-layer">
<FAIcon
class="fa-scale-110 "
icon="ellipsis-h"
/>
</FALayers>
</span>
</template>
<template #content="{close}">
<div
:id="`popup-menu-${randomSeed}`"
class="dropdown-menu"
role="menu"
>
<component
v-for="button in extraButtons"
:key="button.name"
:is="component(button)"
class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
:class="getClass(button)"
:tabindex="0"
@click.stop="component(button) === 'button' && doAction(button)"
@click="close"
:href="component(button) == 'a' ? button.link?.(funcArg) || getRemoteInteractionLink : undefined"
>
<FAIcon
class="fa-scale-110"
:icon="button.icon(funcArg)"
/><span>{{ $t(button.label(funcArg)) }}</span>
</component>
</div>
</template>
</Popover>
</span> </span>
<teleport to="#modal"> <teleport to="#modal">
<confirm-modal <confirm-modal
v-if="showingConfirmDialog" v-if="showingConfirmDialog"
@ -66,11 +120,7 @@
@import "../../mixins"; @import "../../mixins";
.StatusActionButtons { .StatusActionButtons {
width: 100%;
.quick-action-buttons { .quick-action-buttons {
position: relative;
width: 100%;
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-auto-flow: column; grid-auto-flow: column;
@ -121,12 +171,20 @@
.focus-marker { .focus-marker {
visibility: hidden; visibility: hidden;
} }
.active-marker {
visibility: visible;
}
} }
@include focused-style { @include focused-style {
.focus-marker { .focus-marker {
visibility: visible; visibility: visible;
} }
.active-marker {
visibility: hidden;
}
} }
} }
} }

View file

@ -1,5 +1,17 @@
import { toRaw } from 'vue' import { toRaw } from 'vue'
import { isEqual, cloneDeep, set, get, clamp, flatten, groupBy, findLastIndex, takeRight, uniqWith } from 'lodash' import {
isEqual,
cloneDeep,
set,
get,
clamp,
flatten,
groupBy,
findLastIndex,
takeRight,
uniqWith,
merge
} from 'lodash'
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
export const VERSION = 1 export const VERSION = 1
@ -26,6 +38,7 @@ export const defaultState = {
collapseNav: false collapseNav: false
}, },
collections: { collections: {
pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'],
pinnedNavItems: ['home', 'dms', 'chats'] pinnedNavItems: ['home', 'dms', 'chats']
} }
}, },
@ -110,7 +123,11 @@ export const _getRecentData = (cache, live) => {
console.debug('Both sources are invalid, start from scratch') console.debug('Both sources are invalid, start from scratch')
result.needUpload = true result.needUpload = true
} }
return result
result.recent = merge(defaultState, result.recent)
result.stale = merge(defaultState, result.stale)
return merge(defaultState, result)
} }
export const _getAllFlags = (recent, stale) => { export const _getAllFlags = (recent, stale) => {