diff --git a/locales/en-US.yml b/locales/en-US.yml index fa2a0b11ce..08fcc490ea 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2239,4 +2239,5 @@ autocorrectNoteLanguage: "Show a warning if the post language does not match the incorrectLanguageWarning: "It looks like your post is in {detected}, but you selected {current}.\nWould you like to set the language to {detected} instead?" noteEditHistory: "Post edit history" +slashQuote: "Chain quote" foldNotification: "Group similar notifications" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 7642fc80db..2b326c4066 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -2066,4 +2066,5 @@ autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候 incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?" noteEditHistory: "帖子编辑历史" media: 媒体 +slashQuote: "斜杠引用" foldNotification: "将通知按同类型分组" diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 434d10afb9..78cb6350f7 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -373,6 +373,11 @@ const props = withDefaults( autofocus?: boolean; showMfmCheatSheet?: boolean; editId?: entities.Note["id"]; + selectRange?: [ + start: number, + end: number, + direction?: "forward" | "backward" | "none", + ]; }>(), { initialVisibleUsers: () => [], @@ -692,10 +697,14 @@ function togglePoll() { function focus() { if (textareaEl.value) { textareaEl.value.focus(); - textareaEl.value.setSelectionRange( - textareaEl.value.value.length, - textareaEl.value.value.length, - ); + if (props.selectRange) { + textareaEl.value.setSelectionRange(...props.selectRange); + } else { + textareaEl.value.setSelectionRange( + textareaEl.value.value.length, + textareaEl.value.value.length, + ); + } } } diff --git a/packages/client/src/components/MkPostFormDialog.vue b/packages/client/src/components/MkPostFormDialog.vue index fafb4afdd4..a80daf0431 100644 --- a/packages/client/src/components/MkPostFormDialog.vue +++ b/packages/client/src/components/MkPostFormDialog.vue @@ -44,6 +44,11 @@ const props = defineProps<{ fixed?: boolean; autofocus?: boolean; editId?: entities.Note["id"]; + selectRange?: [ + start: number, + end: number, + direction?: "forward" | "backward" | "none", + ]; }>(); const emit = defineEmits<{ diff --git a/packages/client/src/components/MkQuoteButton.vue b/packages/client/src/components/MkQuoteButton.vue index f4004b0cdb..2617d3655e 100644 --- a/packages/client/src/components/MkQuoteButton.vue +++ b/packages/client/src/components/MkQuoteButton.vue @@ -1,5 +1,6 @@ <template> <button + ref="el" v-if="canRenote && defaultStore.state.seperateRenoteQuote" v-tooltip.noDelay.bottom="i18n.ts.quote" class="eddddedb _button" @@ -10,8 +11,8 @@ </template> <script lang="ts" setup> -import { computed } from "vue"; -import type { entities } from "firefish-js"; +import { computed, ref } from "vue"; +import { acct, type entities } from "firefish-js"; import { pleaseLogin } from "@/scripts/please-login"; import * as os from "@/os"; import { me } from "@/me"; @@ -23,6 +24,8 @@ const props = defineProps<{ note: entities.Note; }>(); +const el = ref<HTMLButtonElement>(); + const canRenote = computed( () => ["public", "home"].includes(props.note.visibility) || @@ -31,10 +34,57 @@ const canRenote = computed( function quote(): void { pleaseLogin(); + if ( + props.note.renote != null && + (props.note.text != null || + props.note.fileIds.length === 0 || + props.note.poll != null) + ) { + menu(); + } else { + normalQuote(); + } +} + +function normalQuote(): void { os.post({ renote: props.note, }); } + +function slashQuote(): void { + os.post({ + initialText: ` // @${acct.toString(props.note.user)}: ${props.note.text}`, + selectRange: [0, 0], + renote: props.note.renote, + channel: props.note.channel, + }); +} + +function menu(viaKeyboard = false): void { + os.popupMenu( + [ + { + text: i18n.ts.quote, + icon: `${icon("ph-quotes")}`, + action: normalQuote, + }, + { + text: i18n.ts.slashQuote, + icon: `${icon("ph-notches")}`, + action: slashQuote, + }, + ], + el.value, + { + viaKeyboard, + }, + ).then(focus); +} + +function focus(): void { + el.value!.focus(); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/MkRenoteButton.vue b/packages/client/src/components/MkRenoteButton.vue index 3925dc8c5a..53c1994e65 100644 --- a/packages/client/src/components/MkRenoteButton.vue +++ b/packages/client/src/components/MkRenoteButton.vue @@ -22,7 +22,7 @@ <script lang="ts" setup> import { computed, ref } from "vue"; -import type { entities } from "firefish-js"; +import { acct, type entities } from "firefish-js"; import Ripple from "@/components/MkRipple.vue"; import XDetails from "@/components/MkUsersTooltip.vue"; import { pleaseLogin } from "@/scripts/please-login"; @@ -223,6 +223,28 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => { }); }, }); + if ( + props.note.renote != null && + (props.note.text != null || + props.note.fileIds.length === 0 || + props.note.poll != null) + ) { + buttonActions.push({ + text: i18n.ts.slashQuote, + icon: `${icon("ph-notches")}`, + danger: false, + action: () => { + os.post({ + initialText: ` // @${acct.toString(props.note.user)}: ${ + props.note.text + }`, + selectRange: [0, 0], + renote: props.note.renote, + channel: props.note.channel, + }); + }, + }); + } } if (hasRenotedBefore.value) {