commit: eb7406c6635e476f633163bbfbcff26e6cb1964d
parent 08f8b975b68212dc2ff7616c1b3c163402327a6a
Author: Henry Jameson <me@hjkos.com>
Date: Sat, 11 Jan 2025 20:02:53 +0200
extraButtons implementation
Diffstat:
5 files changed, 181 insertions(+), 50 deletions(-)
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
@@ -166,26 +166,24 @@
</div>
</template>
<template #trigger>
- <span class="button-unstyled popover-trigger">
- <FALayers class="fa-old-padding-layer">
- <FAIcon
- class="fa-scale-110 "
- icon="ellipsis-h"
- />
- <FAIcon
- v-show="!expanded"
- class="focus-marker"
- transform="shrink-6 up-8 right-16"
- icon="plus"
- />
- <FAIcon
- v-show="expanded"
- class="focus-marker"
- transform="shrink-6 up-8 right-16"
- icon="times"
- />
- </FALayers>
- </span>
+ <FALayers class="fa-old-padding-layer">
+ <FAIcon
+ class="fa-scale-110 "
+ icon="ellipsis-h"
+ />
+ <FAIcon
+ v-show="!expanded"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-16"
+ icon="plus"
+ />
+ <FAIcon
+ v-show="expanded"
+ class="focus-marker"
+ transform="shrink-6 up-8 right-16"
+ icon="times"
+ />
+ </FALayers>
<teleport to="#modal">
<ConfirmModal
v-if="showingDeleteDialog"
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
@@ -264,13 +264,11 @@
.status-actions {
position: relative;
width: 100%;
- display: flex;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
margin-top: var(--status-margin);
-
- > * {
- max-width: 4em;
- flex: 1;
- }
}
.muted {
diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js
@@ -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 {
- faRetweet,
faPlus,
faMinus,
- faCheck
+ faCheck,
+ faTimes,
+
+ faReply,
+ faRetweet,
+ faStar,
+ faSmileBeam,
+
+ faEllipsisH,
+ faBookmark,
+ faEyeSlash,
+ faThumbtack,
+ faShareAlt,
+ faExternalLinkAlt,
+ faHistory
} from '@fortawesome/free-solid-svg-icons'
+import {
+ faStar as faStarRegular
+} from '@fortawesome/free-regular-svg-icons'
library.add(
- faRetweet,
faPlus,
faMinus,
- faCheck
+ faCheck,
+ faTimes,
+
+ faReply,
+ faRetweet,
+ faStar,
+ faStarRegular,
+ faSmileBeam,
+
+ faEllipsisH,
+ faBookmark,
+ faEyeSlash,
+ faThumbtack,
+ faShareAlt,
+ faExternalLinkAlt,
+ faHistory
)
const PRIVATE_SCOPES = new Set(['private', 'direct'])
const PUBLIC_SCOPES = new Set(['public', 'unlisted'])
@@ -27,6 +62,8 @@ const BUTTONS = [{
anon: true,
anonLink: true,
toggleable: true,
+ closeIndicator: 'times',
+ activeIndicator: 'none',
action ({ emit }) {
emit('toggleReplying')
return Promise.resolve()
@@ -230,7 +267,10 @@ const BUTTONS = [{
}
}].map(button => {
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: '',
currentConfirmOkText: '',
currentConfirmCancelText: '',
- currentConfirmAction: () => {}
+ currentConfirmAction: () => {},
+ randomSeed: genRandomSeed()
}
},
components: {
+ Popover,
ConfirmModal
},
computed: {
+ ...mapState({
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedStatusActions)
+ }),
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 () {
return {
@@ -265,6 +316,15 @@ const StatusActionButtons = {
currentUser: 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: {
@@ -272,7 +332,7 @@ const StatusActionButtons = {
this.doActionReal(button)
},
doActionReal (button) {
- button.action(this.funcArg(button))
+ button.action(this.funcArg)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
@@ -287,8 +347,8 @@ const StatusActionButtons = {
},
getClass (button) {
return {
- [button.name() + '-button']: true,
- '-active': button.active?.(this.funcArg()),
+ [button.name + '-button']: true,
+ '-active': button.active?.(this.funcArg),
'-interactive': !!this.$store.state.users.currentUser
}
},
diff --git a/src/components/status_action_buttons/status_action_buttons.vue b/src/components/status_action_buttons/status_action_buttons.vue
@@ -3,7 +3,7 @@
<span class="quick-action-buttons">
<span
class="quick-action"
- v-for="button in buttons"
+ v-for="button in quickButtons"
:key="button.name"
>
<component
@@ -23,16 +23,22 @@
/>
<template v-if="button.toggleable?.(funcArg) && button.active">
<FAIcon
- v-show="!button.active(funcArg)"
+ v-if="button.active(funcArg)"
+ class="active-marker"
+ transform="shrink-6 up-9 right-12"
+ :icon="button.activeIndicator?.(funcArg) || 'check'"
+ />
+ <FAIcon
+ v-if="!button.active(funcArg)"
class="focus-marker"
- transform="shrink-6 up-9 right-17"
- icon="plus"
+ transform="shrink-6 up-9 right-12"
+ :icon="button.openIndicator?.(funcArg) || 'plus'"
/>
<FAIcon
- v-show="button.active(funcArg)"
+ v-else
class="focus-marker"
- transform="shrink-6 up-9 right-17"
- icon="times"
+ transform="shrink-6 up-9 right-12"
+ :icon="button.closeIndicator?.(funcArg) || 'minus'"
/>
</template>
</FALayers>
@@ -44,7 +50,55 @@
{{ button.counter?.(funcArg) }}
</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>
+
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmDialog"
@@ -66,11 +120,7 @@
@import "../../mixins";
.StatusActionButtons {
- width: 100%;
-
.quick-action-buttons {
- position: relative;
- width: 100%;
display: grid;
grid-template-columns: 1fr;
grid-auto-flow: column;
@@ -121,12 +171,20 @@
.focus-marker {
visibility: hidden;
}
+
+ .active-marker {
+ visibility: visible;
+ }
}
@include focused-style {
.focus-marker {
visibility: visible;
}
+
+ .active-marker {
+ visibility: hidden;
+ }
}
}
}
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
@@ -1,5 +1,17 @@
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'
export const VERSION = 1
@@ -26,6 +38,7 @@ export const defaultState = {
collapseNav: false
},
collections: {
+ pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'],
pinnedNavItems: ['home', 'dms', 'chats']
}
},
@@ -110,7 +123,11 @@ export const _getRecentData = (cache, live) => {
console.debug('Both sources are invalid, start from scratch')
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) => {