commit: fe84a52dcc0a1cfe5088363edd5116bd0a61f36e
parent 35409ad9ebb366202bfe4f685c62bd05d433df99
Author: Henry Jameson <me@hjkos.com>
Date: Thu, 9 Jan 2025 17:43:48 +0200
initial work on quick actions
Diffstat:
5 files changed, 205 insertions(+), 53 deletions(-)
diff --git a/src/components/status/status.js b/src/components/status/status.js
@@ -16,6 +16,7 @@ import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
import UserLink from '../user_link/user_link.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue'
+import StatusActionButtons from 'src/components/status_action_buttons/status_action_buttons.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js'
@@ -119,7 +120,8 @@ const Status = {
MentionLink,
MentionsLine,
UserPopover,
- UserLink
+ UserLink,
+ StatusActionButtons
},
props: [
'statusoid',
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -65,7 +65,6 @@
v-if="retweet"
class="left-side repeater-avatar"
:show-actor-type-indicator="showActorTypeIndicator"
- :better-shadow="betterShadow"
:user="statusoid.user"
/>
<div class="right-side faint">
@@ -120,7 +119,6 @@
class="post-avatar"
:show-actor-type-indicator="showActorTypeIndicator"
:compact="compact"
- :better-shadow="betterShadow"
:user="status.user"
/>
</UserPopover>
@@ -537,6 +535,12 @@
:status="status"
/>
+ <StatusActionButtons
+ v-if="!noHeading && !isPreview"
+ :status="status"
+ :replying="replying"
+ @toggleReplying="toggleReplying"
+ />
<div
v-if="!noHeading && !isPreview"
class="status-actions"
@@ -600,12 +604,14 @@
<PostStatusForm
ref="postStatusForm"
class="reply-body"
+ :closeable="true"
:reply-to="status.id"
:attentions="status.attentions"
:replied-user="status.user"
:copy-message-scope="status.visibility"
:subject="replySubject"
@posted="doToggleReplying"
+ @draft-done="doToggleReplying"
@can-close="doToggleReplying"
/>
</div>
diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js
@@ -28,7 +28,8 @@ const BUTTONS = [{
anonLink: true,
toggleable: true,
action ({ emit }) {
- emit('toggle')
+ emit('toggleReplying')
+ return Promise.resolve()
}
}, {
// =========
@@ -44,11 +45,16 @@ const BUTTONS = [{
},
animated: true,
active: ({ status }) => status.repeated,
- counter: ({ status }) => status.replies_count,
+ counter: ({ status }) => status.repeat_num,
anonLink: true,
interactive: ({ status }) => !PRIVATE_SCOPES.has(status.visibility),
toggleable: true,
confirm: ({ status, getters }) => !status.repeated && getters.mergedConfig.modalOnRepeat,
+ confirmStrings: {
+ title: 'status.repeat_confirm_title',
+ confirm: 'status.repeat_confirm_accept_button',
+ cancel: 'status.repeat_confirm_cancel_button'
+ },
action ({ status, store }) {
if (!status.repeated) {
return store.dispatch('retweet', { id: status.id })
@@ -62,10 +68,12 @@ const BUTTONS = [{
// =========
name: 'favorite',
label: 'tool_tip.favorite',
- icon: 'star',
+ icon: ({ status }) => status.favorited
+ ? ['fas', 'star']
+ : ['far', 'star'],
animated: true,
active: ({ status }) => status.favorited,
- counter: ({ status }) => status.fave_count,
+ counter: ({ status }) => status.fave_num,
anonLink: true,
toggleable: true,
action ({ status, store }) {
@@ -81,11 +89,9 @@ const BUTTONS = [{
// =========
name: 'emoji',
label: 'tool_lip.add_reaction',
- icon: 'smile-beam',
+ icon: ['far', 'smile-beam'],
anonLink: true,
- action ({ emojiPicker }) {
- emojiPicker.show()
- }
+ popover: 'emoji-picker'
}, {
// =========
// MUTE CONVERSATION, my beloved
@@ -141,7 +147,8 @@ const BUTTONS = [{
} else {
return dispatch('bookmark', { id: status.id })
}
- }
+ },
+ popover: 'bookmark-folders'
}, {
// =========
// EDIT
@@ -180,8 +187,13 @@ const BUTTONS = [{
currentUser.privileges.includes('messages_delete')
)
},
+ confirmStrings: {
+ title: 'status.delete_confirm_title',
+ confirm: 'status.delete_confirm_cancel_button',
+ cancel: 'status.delete_confirm_accept_button'
+ },
action ({ dispatch, status }) {
- dispatch('deleteStatus', { id: status.id })
+ return dispatch('deleteStatus', { id: status.id })
}
}, {
// =========
@@ -195,6 +207,7 @@ const BUTTONS = [{
state.instance.server,
router.resolve({ name: 'conversation', params: { id: status.id } }).href
].join(''))
+ return Promise.resolve()
}
}, {
// =========
@@ -210,58 +223,74 @@ const BUTTONS = [{
// =========
name: 'report',
icon: 'flag',
- label: 'status.report',
+ label: 'user_card.report',
if: ({ loggedIn }) => loggedIn,
action ({ dispatch, status }) {
dispatch('openUserReportingModal', { userId: status.user.id, statusIds: [status.id] })
}
-}]
+}].map(button => {
+ return Object.fromEntries(
+ Object.entries(button).map(([k, v]) => [k, typeof v === 'function' ? v : () => v])
+ )
+})
+console.log(BUTTONS)
const StatusActionButtons = {
- props: ['status'],
- components: {
- ConfirmModal
- },
+ props: ['status', 'replying'],
+ emits: ['toggleReplying'],
data () {
return {
+ buttons: BUTTONS,
+ showingConfirmDialog: false,
+ currentConfirmTitle: '',
+ currentConfirmOkText: '',
+ currentConfirmCancelText: '',
+ currentConfirmAction: () => {}
}
},
+ components: {
+ ConfirmModal
+ },
methods: {
- retweet () {
- if (!this.status.repeated && this.shouldConfirmRepeat) {
- this.showConfirmDialog()
- } else {
- this.doRetweet()
- }
+ doAction (button) {
+ this.doActionReal(button)
+ },
+ doActionReal (button) {
+ button.action(this.funcArg(button))
+ .then(() => this.$emit('onSuccess'))
+ .catch(err => this.$emit('onError', err.error.error))
},
- doRetweet () {
- if (!this.status.repeated) {
- this.$store.dispatch('retweet', { id: this.status.id })
+ component (button) {
+ if (!this.$store.state.users.currentUser && button.anonLink) {
+ return 'a'
+ } else if (button.action == null && button.link != null) {
+ return 'a'
} else {
- this.$store.dispatch('unretweet', { id: this.status.id })
+ return 'button'
}
- this.animated = true
- setTimeout(() => {
- this.animated = false
- }, 500)
- this.hideConfirmDialog()
},
- showConfirmDialog () {
- this.showingConfirmDialog = true
+ funcArg () {
+ return {
+ status: this.status,
+ replying: this.replying,
+ emit: this.$emit,
+ dispatch: this.$store.dispatch,
+ state: this.$store.state,
+ getters: this.$store.getters,
+ router: this.$router,
+ currentUser: this.$store.state.users.currentUser,
+ loggedIn: !!this.$store.state.users.currentUser
+ }
},
- hideConfirmDialog () {
- this.showingConfirmDialog = false
- }
- },
- computed: {
- mergedConfig () {
- return this.$store.getters.mergedConfig
+ getClass (button) {
+ return {
+ [button.name() + '-button']: true,
+ '-active': button.active?.(this.funcArg()),
+ '-interactive': !!this.$store.state.users.currentUser
+ }
},
- remoteInteractionLink () {
+ getRemoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
- },
- shouldConfirmRepeat () {
- return this.mergedConfig.modalOnRepeat
}
}
}
diff --git a/src/components/status_action_buttons/status_action_buttons.vue b/src/components/status_action_buttons/status_action_buttons.vue
@@ -1,21 +1,135 @@
<template>
+ <div class="StatusActionButtons">
+ <span class="quick-action-buttons">
+ <span
+ class="quick-action"
+ v-for="button in buttons"
+ :key="button.name()"
+ >
+ <component
+ :is="component(button)"
+ class="button-unstyled"
+ :class="getClass(button)"
+ role="button"
+ :tabindex="0"
+ :title="$t(button.label(funcArg()))"
+ @click.stop="component(button) === 'button' && doAction(button)"
+ :href="component(button) == 'a' ? button.link?.(funcArg()) || getRemoteInteractionLink : undefined"
+ >
+ <FALayers class="fa-old-padding">
+ <FAIcon
+ class="fa-scale-110"
+ :icon="button.icon(funcArg())"
+ />
+ <template v-if="button.toggleable?.(funcArg()) && button.active">
+ <FAIcon
+ v-show="!button.active(funcArg())"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-17"
+ icon="plus"
+ />
+ <FAIcon
+ v-show="button.active(funcArg())"
+ class="focus-marker"
+ transform="shrink-6 up-9 right-17"
+ icon="times"
+ />
+ </template>
+ </FALayers>
+ </component>
+ <span
+ class="action-counter"
+ v-if="button.counter?.(funcArg()) > 0"
+ >
+ {{ button.counter?.(funcArg()) }}
+ </span>
+ </span>
+ </span>
+ <teleport to="#modal">
+ <confirm-modal
+ v-if="showingConfirmDialog"
+ :title="currentConfirmTitle"
+ :confirm-text="currentConfirmOkText"
+ :cancel-text="currentConfirmCancelText"
+ @accepted="currentConfirmAction"
+ @cancelled="hideConfirmDialog"
+ >
+ {{ $t('status.repeat_confirm') }}
+ </confirm-modal>
+ </teleport>
+ </div>
</template>
-<script src="./status_buttons.js"></script>
+<script src="./status_action_buttons.js"></script>
<style lang="scss">
@import "../../mixins";
-.status-actions {
-position: relative;
+.StatusActionButtons {
+ width: 100%;
+
+ .quick-action-buttons {
+ position: relative;
width: 100%;
- display: flex;
+ 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;
- flex: 1;
+
+ .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;
+ }
+ }
+
+ @include focused-style {
+ .focus-marker {
+ visibility: visible;
+ }
+ }
}
}
+}
</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -1410,6 +1410,7 @@
"mentions": "Mentions",
"repeat": "Repeat",
"reply": "Reply",
+ "add_reaction": "Add reaction",
"favorite": "Favorite",
"add_reaction": "Add Reaction",
"user_settings": "User Settings",