commit: 96fd7f91c43035eed918d08fe887f145751f7570
parent eb7406c6635e476f633163bbfbcff26e6cb1964d
Author: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2025 01:46:10 +0200
more work + dropdown items overhaul
Diffstat:
9 files changed, 340 insertions(+), 227 deletions(-)
diff --git a/src/components/popover/popover.scss b/src/components/popover/popover.scss
@@ -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;
+ }
+}
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
@@ -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>
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -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 }"
diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js
@@ -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
}
diff --git a/src/components/status_action_buttons/status_action_buttons.scss b/src/components/status_action_buttons/status_action_buttons.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/src/components/status_action_buttons/status_action_buttons.vue b/src/components/status_action_buttons/status_action_buttons.vue
@@ -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>
diff --git a/src/components/user_list_menu/user_list_menu.js b/src/components/user_list_menu/user_list_menu.js
@@ -34,6 +34,11 @@ const UserListMenu = {
...list,
inList: this.inListsSet.has(list.id)
}))
+ },
+ triggerAttrs () {
+ return {
+ class: 'menu-item dropdown-item -has-submenu'
+ }
}
},
methods: {
diff --git a/src/components/user_list_menu/user_list_menu.vue b/src/components/user_list_menu/user_list_menu.vue
@@ -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>
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
@@ -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) {