more work + dropdown items overhaul

This commit is contained in:
Henry Jameson 2025-01-12 01:46:10 +02:00
parent eb7406c663
commit 96fd7f91c4
9 changed files with 340 additions and 227 deletions

View file

@ -0,0 +1,105 @@
.popover-trigger-button {
display: inline-block;
}
.popover {
z-index: var(--ZI_popover_override, var(--ZI_popovers));
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
box-shadow: var(--shadow);
}
.popover-default {
&::after {
content: "";
position: absolute;
top: -1px;
bottom: -1px;
left: -1px;
right: -1px;
z-index: -1px;
box-shadow: var(--shadow);
pointer-events: none;
}
border-radius: var(--roundness);
border-color: var(--border);
border-style: solid;
border-width: 1px;
background-color: var(--background);
}
.dropdown-menu {
display: block;
padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
background-color: var(--background);
.dropdown-divider {
height: 0;
margin: 0.5rem 0;
overflow: hidden;
border-top: 1px solid var(--border);
}
.dropdown-item:not(button, a) {
padding: 0;
}
a.dropdown-item,
button.dropdown-item,
.dropdown-item:not(button, a) > button:first-child,
.dropdown-item:not(button, a) > a:first-child {
box-sizing: border-box;
padding: var(--__horizontal-gap) var(--__horizontal-gap);
grid-gap: var(--__horizontal-gap);
display: grid;
border: none;
align-items: center;
grid-template-columns: 1fr var(--__line-height);
grid-auto-flow: column;
grid-auto-columns: auto;
cursor: pointer;
.menu-checkbox {
display: inline-block;
vertical-align: middle;
min-width: calc(var(--__line-height) + 1px);
max-width: calc(var(--__line-height) + 1px);
min-height: calc(var(--__line-height) + 1px);
max-height: calc(var(--__line-height) + 1px);
line-height: var(--__line-height);
text-align: center;
border-radius: 0;
box-shadow: var(--shadow);
margin-right: var(--__horizontal-gap);
&.menu-checkbox-checked::after {
font-size: 1.25em;
content: "";
}
&.-radio {
border-radius: 9999px;
&.menu-checkbox-checked::after {
font-size: 2em;
content: "";
}
}
}
}
a.dropdown-item-icon,
button.dropdown-item-icon,
.dropdown-item-icon:not(button, a) > button:first-child,
.dropdown-item-icon:not(button, a) > a:first-child {
grid-template-columns: var(--__line-height) 1fr;
}
}

View file

@ -41,101 +41,4 @@
<script src="./popover.js" />
<style lang="scss">
.popover-trigger-button {
display: inline-block;
}
.popover {
z-index: var(--ZI_popover_override, var(--ZI_popovers));
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
box-shadow: var(--shadow);
}
.popover-default {
&::after {
content: "";
position: absolute;
top: -1px;
bottom: -1px;
left: -1px;
right: -1px;
z-index: -1px;
box-shadow: var(--shadow);
pointer-events: none;
}
border-radius: var(--roundness);
border-color: var(--border);
border-style: solid;
border-width: 1px;
background-color: var(--background);
}
.dropdown-menu {
display: block;
padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
background-color: var(--background);
.dropdown-divider {
height: 0;
margin: 0.5rem 0;
overflow: hidden;
border-top: 1px solid var(--border);
}
.dropdown-item {
border: none;
&-icon {
svg {
width: var(--__line-height);
margin-right: var(--__horizontal-gap);
}
}
&.-has-submenu {
.chevron-icon {
margin-right: 0.25rem;
margin-left: 2rem;
}
}
.menu-checkbox {
display: inline-block;
vertical-align: middle;
min-width: calc(var(--__line-height) + 1px);
max-width: calc(var(--__line-height) + 1px);
min-height: calc(var(--__line-height) + 1px);
max-height: calc(var(--__line-height) + 1px);
line-height: var(--__line-height);
text-align: center;
border-radius: 0;
box-shadow: var(--shadow);
margin-right: var(--__horizontal-gap);
&.menu-checkbox-checked::after {
font-size: 1.25em;
content: "✓";
}
&.-radio {
border-radius: 9999px;
&.menu-checkbox-checked::after {
font-size: 2em;
content: "•";
}
}
}
}
}
</style>
<style src="./popover.scss" lang="scss"></style>

View file

@ -337,7 +337,7 @@
>
<button
v-if="!disableDraft"
class="menu-item dropdown-item dropdown-item-icon"
class="menu-item dropdown-item"
role="menu"
:disabled="!safeToSaveDraft"
:class="{ disabled: !safeToSaveDraft }"

View file

@ -113,11 +113,11 @@ const BUTTONS = [{
counter: ({ status }) => status.fave_num,
anonLink: true,
toggleable: true,
action ({ status, store }) {
action ({ status, dispatch }) {
if (!status.favorited) {
return store.dispatch('favorite', { id: status.id })
return dispatch('favorite', { id: status.id })
} else {
return store.dispatch('unfavorite', { id: status.id })
return dispatch('unfavorite', { id: status.id })
}
}
}, {
@ -125,7 +125,7 @@ const BUTTONS = [{
// EMOJI REACTIONS
// =========
name: 'emoji',
label: 'tool_lip.add_reaction',
label: 'tool_tip.add_reaction',
icon: ['far', 'smile-beam'],
anonLink: true,
popover: 'emoji-picker'
@ -279,6 +279,7 @@ const StatusActionButtons = {
emits: ['toggleReplying'],
data () {
return {
showPin: true,
showingConfirmDialog: false,
currentConfirmTitle: '',
currentConfirmOkText: '',
@ -304,6 +305,9 @@ const StatusActionButtons = {
extraButtons () {
return this.buttons.filter(x => !this.pinnedItems.has(x.name))
},
currentUser () {
return this.$store.state.users.currentUser
},
funcArg () {
return {
status: this.status,
@ -313,8 +317,8 @@ const StatusActionButtons = {
state: this.$store.state,
getters: this.$store.getters,
router: this.$router,
currentUser: this.$store.state.users.currentUser,
loggedIn: !!this.$store.state.users.currentUser
currentUser: this.currentUser,
loggedIn: !!this.currentUser
}
},
triggerAttrs () {
@ -336,6 +340,18 @@ const StatusActionButtons = {
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
isPinned (button) {
console.log(this.pinnedItems, button.name)
return this.pinnedItems.has(button.name)
},
unpin (button) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage')
},
pin (button) {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedStatusActions', value: button.name })
this.$store.dispatch('pushServerSideStorage')
},
component (button) {
if (!this.$store.state.users.currentUser && button.anonLink) {
return 'a'
@ -348,6 +364,8 @@ const StatusActionButtons = {
getClass (button) {
return {
[button.name + '-button']: true,
'-pin-edit': this.showPin,
'-dropdown': button.dropdown?.(),
'-active': button.active?.(this.funcArg),
'-interactive': !!this.$store.state.users.currentUser
}

View file

@ -0,0 +1,112 @@
@import "../../mixins";
.StatusActionButtons {
.quick-action-buttons {
display: grid;
grid-template-columns: 1fr;
grid-auto-flow: column;
grid-auto-columns: 1fr;
grid-gap: 1em;
margin-top: var(--status-margin);
.quick-action {
display: grid;
grid-template-columns: 1fr auto;
.action-button {
display: grid;
grid-template-columns: max-content auto;
grid-gap: 1em;
align-items: center;
}
&.-pin {
margin: calc(-2px - 0.25em);
padding: 0.25em;
border: 2px dashed var(--icon);
border-radius: var(--roundness);
}
&.-pin,
&.-dropdown {
grid-template-columns: 1fr max-content;
}
.reply-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cBlue);
}
}
}
.retweet-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cGreen);
}
}
}
.favorite-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cOrange);
}
}
}
> button,
> a {
padding: 0.5em;
margin: -0.5em;
@include unfocused-style {
.focus-marker {
visibility: hidden;
}
.active-marker {
visibility: visible;
}
}
@include focused-style {
.focus-marker {
visibility: visible;
}
.active-marker {
visibility: hidden;
}
}
}
}
}
}
// popover
.extra-action-buttons {
.extra-action {
display: grid;
grid-template-columns: 1fr;
grid-auto-flow: column;
grid-auto-columns: auto;
grid-gap: 1em;
.pin-action-button {
margin: 0;
padding: var(--__horizontal-gap) var(--__horizontal-gap);
&::before {
content: "";
height: 1em;
width: 1px;
border-left: 1px solid var(--icon);
margin-right: 0.5em;
}
}
}
}

View file

@ -3,12 +3,13 @@
<span class="quick-action-buttons">
<span
class="quick-action"
:class="{ '-pin': showPin, '-toggle': button.dropdown?.() }"
v-for="button in quickButtons"
:key="button.name"
>
<component
:is="component(button)"
class="button-unstyled"
class="button-unstyled action-button"
:class="getClass(button)"
role="button"
:tabindex="0"
@ -16,7 +17,7 @@
@click.stop="component(button) === 'button' && doAction(button)"
:href="component(button) == 'a' ? button.link?.(funcArg) || getRemoteInteractionLink : undefined"
>
<FALayers class="fa-old-padding">
<FALayers>
<FAIcon
class="fa-scale-110"
:icon="button.icon(funcArg)"
@ -42,13 +43,28 @@
/>
</template>
</FALayers>
<span
class="action-counter"
v-if="button.counter?.(funcArg) > 0"
>
{{ button.counter?.(funcArg) }}
</span>
</component>
<span
class="action-counter"
v-if="button.counter?.(funcArg) > 0"
<button
v-if="showPin && currentUser"
type="button"
class="button-unstyled pin-action-button"
:title="$t('general.unpin')"
:aria-pressed="true"
@click.stop.prevent="unpin(button)"
>
{{ button.counter?.(funcArg) }}
</span>
<FAIcon
v-if="showPin && currentUser"
fixed-width
class="fa-scale-110"
icon="thumbtack"
/>
</button>
</span>
<Popover
trigger="click"
@ -56,44 +72,61 @@
:tabindex="0"
placement="top"
:offset="{ y: 5 }"
:bound-to="{ x: 'container2' }"
:bound-to="{ x: 'container' }"
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>
<FAIcon
class="fa-scale-110 "
icon="ellipsis-h"
/>
</template>
<template #content="{close}">
<div
:id="`popup-menu-${randomSeed}`"
class="dropdown-menu"
class="dropdown-menu extra-action-buttons"
role="menu"
>
<component
<div
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"
class="menu-item dropdown-item extra-action dropdown-item-icon"
>
<FAIcon
class="fa-scale-110"
:icon="button.icon(funcArg)"
/><span>{{ $t(button.label(funcArg)) }}</span>
</component>
<component
:is="component(button)"
class="main-button"
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"
fixed-width
:icon="button.icon(funcArg)"
/><span>{{ $t(button.label(funcArg)) }}</span>
</component>
<button
v-if="showPin && currentUser"
type="button"
class="button-unstyled pin-action-button"
:title="$t('general.pin' )"
:aria-pressed="false"
@click.stop.prevent="pin(button)"
>
<FAIcon
v-if="showPin && currentUser"
fixed-width
class="fa-scale-110 veryfaint"
transform="rotate-45"
icon="thumbtack"
/>
</button>
</div>
</div>
</template>
</Popover>
@ -116,78 +149,4 @@
<script src="./status_action_buttons.js"></script>
<style lang="scss">
@import "../../mixins";
.StatusActionButtons {
.quick-action-buttons {
display: grid;
grid-template-columns: 1fr;
grid-auto-flow: column;
grid-auto-columns: 1fr;
grid-gap: 1em;
margin-top: var(--status-margin);
.quick-action {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 0.5em;
max-width: 4em;
.reply-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cBlue);
}
}
}
.retweet-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cGreen);
}
}
}
.favorite-button {
&:hover,
&.-active {
.svg-inline--fa {
color: var(--cOrange);
}
}
}
> button,
> a {
padding: 0.5em;
margin: -0.5em;
}
@include unfocused-style {
.focus-marker {
visibility: hidden;
}
.active-marker {
visibility: visible;
}
}
@include focused-style {
.focus-marker {
visibility: visible;
}
.active-marker {
visibility: hidden;
}
}
}
}
}
</style>
<style lang="scss" src="./status_action_buttons.scss"></style>

View file

@ -34,6 +34,11 @@ const UserListMenu = {
...list,
inList: this.inListsSet.has(list.id)
}))
},
triggerAttrs () {
return {
class: 'menu-item dropdown-item -has-submenu'
}
}
},
methods: {

View file

@ -3,6 +3,7 @@
<Popover
trigger="hover"
placement="left"
:trigger-attrs="triggerAttrs"
remove-padding
>
<template #content>
@ -10,7 +11,7 @@
<button
v-for="list in lists"
:key="list.id"
class="menu-item dropdown-item"
class="menu-item dropdown-item dropdown-item-icon"
@click="toggleList(list.id)"
>
<span
@ -22,14 +23,14 @@
</div>
</template>
<template #trigger>
<button class="menu-item dropdown-item -has-submenu">
<span>
{{ $t('lists.manage_lists') }}
<FAIcon
class="chevron-icon"
size="lg"
icon="chevron-right"
/>
</button>
</span>
<FAIcon
class="chevron-icon"
size="lg"
icon="chevron-right"
/>
</template>
</Popover>
</div>

View file

@ -9,8 +9,7 @@ import {
groupBy,
findLastIndex,
takeRight,
uniqWith,
merge
uniqWith
} from 'lodash'
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
@ -124,10 +123,21 @@ export const _getRecentData = (cache, live) => {
result.needUpload = true
}
result.recent = merge(defaultState, result.recent)
result.stale = merge(defaultState, result.stale)
const merge = (a, b) => ({
needUpload: b.needUpload ?? a.needUpload,
prefsStorage: {
...a.prefsStorage,
...b.prefsStorage
},
flagStorage: {
...a.flagStorage,
...b.flagStorage
}
})
result.recent = result.recent && merge(defaultState, result.recent)
result.stale = result.stale && merge(defaultState, result.stale)
return merge(defaultState, result)
return result
}
export const _getAllFlags = (recent, stale) => {
@ -309,7 +319,7 @@ export const mutations = {
cache = _doMigrations(cache)
let { recent, stale, needsUpload } = _getRecentData(cache, live)
let { recent, stale, needUpload } = _getRecentData(cache, live)
const userNew = userData.created_at > NEW_USER_DATE
const flagsTemplate = userNew ? newUserFlags : defaultState.flagStorage
@ -323,7 +333,7 @@ export const mutations = {
})
}
if (!needsUpload && recent && stale) {
if (!needUpload && recent && stale) {
console.debug('Checking if data needs merging...')
// discarding timestamps and versions
const { _timestamp: _0, _version: _1, ...recentData } = recent
@ -352,7 +362,7 @@ export const mutations = {
recent.flagStorage = { ...flagsTemplate, ...totalFlags }
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
state.dirty = dirty || needsUpload
state.dirty = dirty || needUpload
state.cache = recent
// set local timestamp to smaller one if we don't have any changes
if (stale && recent && !state.dirty) {