feat: add an option to disable emoji reactions (#9878)
Closes: #9865 Co-authored-by: naskya <m@naskya.net> Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9878 Co-authored-by: naskya <naskya@noreply.codeberg.org> Co-committed-by: naskya <naskya@noreply.codeberg.org>
This commit is contained in:
parent
8588979db9
commit
0a173a3c1c
|
@ -114,6 +114,7 @@ clickToShow: "Click to show"
|
||||||
sensitive: "NSFW"
|
sensitive: "NSFW"
|
||||||
add: "Add"
|
add: "Add"
|
||||||
reaction: "Reactions"
|
reaction: "Reactions"
|
||||||
|
enableEmojiReactions: "Enable emoji reactions"
|
||||||
reactionSetting: "Reactions to show in the reaction picker"
|
reactionSetting: "Reactions to show in the reaction picker"
|
||||||
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
|
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
|
||||||
rememberNoteVisibility: "Remember post visibility settings"
|
rememberNoteVisibility: "Remember post visibility settings"
|
||||||
|
|
|
@ -109,6 +109,7 @@ clickToShow: "クリックして表示"
|
||||||
sensitive: "閲覧注意"
|
sensitive: "閲覧注意"
|
||||||
add: "追加"
|
add: "追加"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
|
enableEmojiReactions: "絵文字リアクションを有効にする"
|
||||||
reactionSetting: "ピッカーに表示するリアクション"
|
reactionSetting: "ピッカーに表示するリアクション"
|
||||||
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
||||||
rememberNoteVisibility: "公開範囲を記憶する"
|
rememberNoteVisibility: "公開範囲を記憶する"
|
||||||
|
|
|
@ -107,6 +107,7 @@ clickToShow: "点击以显示"
|
||||||
sensitive: "敏感内容"
|
sensitive: "敏感内容"
|
||||||
add: "添加"
|
add: "添加"
|
||||||
reaction: "回应"
|
reaction: "回应"
|
||||||
|
enableEmojiReaction: "启用表情符号回应"
|
||||||
reactionSetting: "在选择器中显示的回应"
|
reactionSetting: "在选择器中显示的回应"
|
||||||
reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。"
|
reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。"
|
||||||
rememberNoteVisibility: "保存上次设置的可见性"
|
rememberNoteVisibility: "保存上次设置的可见性"
|
||||||
|
|
|
@ -107,6 +107,7 @@ clickToShow: "按一下以顯示"
|
||||||
sensitive: "敏感內容"
|
sensitive: "敏感內容"
|
||||||
add: "新增"
|
add: "新增"
|
||||||
reaction: "情感"
|
reaction: "情感"
|
||||||
|
enableEmojiReaction: "啟用表情符號反應"
|
||||||
reactionSetting: "在選擇器中顯示反應"
|
reactionSetting: "在選擇器中顯示反應"
|
||||||
reactionSettingDescription2: "拖動以重新列序,點擊以刪除,按下 + 添加。"
|
reactionSettingDescription2: "拖動以重新列序,點擊以刪除,按下 + 添加。"
|
||||||
rememberNoteVisibility: "記住貼文可見性"
|
rememberNoteVisibility: "記住貼文可見性"
|
||||||
|
|
|
@ -176,6 +176,7 @@
|
||||||
</div>
|
</div>
|
||||||
<footer ref="el" class="footer" @click.stop>
|
<footer ref="el" class="footer" @click.stop>
|
||||||
<XReactionsViewer
|
<XReactionsViewer
|
||||||
|
v-if="enableEmojiReactions"
|
||||||
ref="reactionsViewer"
|
ref="reactionsViewer"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
|
@ -195,14 +196,32 @@
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="appearNote.renoteCount"
|
:count="appearNote.renoteCount"
|
||||||
/>
|
/>
|
||||||
|
<XStarButtonNoEmoji
|
||||||
|
v-if="!enableEmojiReactions"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
:count="
|
||||||
|
Object.values(appearNote.reactions).reduce(
|
||||||
|
(partialSum, val) => partialSum + val,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:reacted="appearNote.myReaction != null"
|
||||||
|
/>
|
||||||
<XStarButton
|
<XStarButton
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="starButton"
|
ref="starButton"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||||
class="button _button"
|
class="button _button"
|
||||||
|
@ -211,7 +230,10 @@
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction != null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction != null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click="undoReact(appearNote)"
|
@click="undoReact(appearNote)"
|
||||||
|
@ -263,6 +285,7 @@ import XPoll from "@/components/MkPoll.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
|
@ -333,6 +356,7 @@ const translating = ref(false);
|
||||||
const urls = appearNote.text
|
const urls = appearNote.text
|
||||||
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5)
|
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5)
|
||||||
: null;
|
: null;
|
||||||
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
r: () => reply(true),
|
r: () => reply(true),
|
||||||
|
|
|
@ -179,6 +179,7 @@
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
<XReactionsViewer
|
<XReactionsViewer
|
||||||
|
v-if="enableEmojiReactions"
|
||||||
ref="reactionsViewer"
|
ref="reactionsViewer"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
|
@ -203,14 +204,32 @@
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="appearNote.renoteCount"
|
:count="appearNote.renoteCount"
|
||||||
/>
|
/>
|
||||||
|
<XStarButtonNoEmoji
|
||||||
|
v-if="!enableEmojiReactions"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
:count="
|
||||||
|
Object.values(appearNote.reactions).reduce(
|
||||||
|
(partialSum, val) => partialSum + val,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:reacted="appearNote.myReaction != null"
|
||||||
|
/>
|
||||||
<XStarButton
|
<XStarButton
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="starButton"
|
ref="starButton"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||||
class="button _button"
|
class="button _button"
|
||||||
|
@ -219,7 +238,10 @@
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction != null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction != null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click="undoReact(appearNote)"
|
@click="undoReact(appearNote)"
|
||||||
|
@ -283,6 +305,7 @@ import XMediaList from "@/components/MkMediaList.vue";
|
||||||
import XCwButton from "@/components/MkCwButton.vue";
|
import XCwButton from "@/components/MkCwButton.vue";
|
||||||
import XPoll from "@/components/MkPoll.vue";
|
import XPoll from "@/components/MkPoll.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||||
|
@ -316,6 +339,8 @@ const inChannel = inject("inChannel", null);
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = $ref(deepClone(props.note));
|
||||||
|
|
||||||
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
|
||||||
// plugin
|
// plugin
|
||||||
if (noteViewInterruptors.length > 0) {
|
if (noteViewInterruptors.length > 0) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
</div>
|
</div>
|
||||||
<footer class="footer" @click.stop>
|
<footer class="footer" @click.stop>
|
||||||
<XReactionsViewer
|
<XReactionsViewer
|
||||||
|
v-if="enableEmojiReactions"
|
||||||
ref="reactionsViewer"
|
ref="reactionsViewer"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
|
@ -106,14 +107,32 @@
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="appearNote.renoteCount"
|
:count="appearNote.renoteCount"
|
||||||
/>
|
/>
|
||||||
|
<XStarButtonNoEmoji
|
||||||
|
v-if="!enableEmojiReactions"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
:count="
|
||||||
|
Object.values(appearNote.reactions).reduce(
|
||||||
|
(partialSum, val) => partialSum + val,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:reacted="appearNote.myReaction != null"
|
||||||
|
/>
|
||||||
<XStarButton
|
<XStarButton
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="starButton"
|
ref="starButton"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction == null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction == null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||||
class="button _button"
|
class="button _button"
|
||||||
|
@ -122,7 +141,10 @@
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="appearNote.myReaction != null"
|
v-if="
|
||||||
|
enableEmojiReactions &&
|
||||||
|
appearNote.myReaction != null
|
||||||
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click="undoReact(appearNote)"
|
@click="undoReact(appearNote)"
|
||||||
|
@ -187,6 +209,7 @@ import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
import XCwButton from "@/components/MkCwButton.vue";
|
import XCwButton from "@/components/MkCwButton.vue";
|
||||||
|
@ -199,6 +222,7 @@ import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { deepClone } from "@/scripts/clone";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -247,6 +271,7 @@ const replies: misskey.entities.Note[] =
|
||||||
item.renoteId === props.note.id
|
item.renoteId === props.note.id
|
||||||
)
|
)
|
||||||
.reverse() ?? [];
|
.reverse() ?? [];
|
||||||
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
|
|
|
@ -65,7 +65,10 @@
|
||||||
></i>
|
></i>
|
||||||
<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
|
<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
|
||||||
<XReactionIcon
|
<XReactionIcon
|
||||||
v-else-if="notification.type === 'reaction'"
|
v-else-if="
|
||||||
|
notification.type === 'reaction' &&
|
||||||
|
defaultStore.state.enableEmojiReactions
|
||||||
|
"
|
||||||
ref="reactionRef"
|
ref="reactionRef"
|
||||||
:reaction="
|
:reaction="
|
||||||
notification.reaction
|
notification.reaction
|
||||||
|
@ -78,6 +81,14 @@
|
||||||
:custom-emojis="notification.note.emojis"
|
:custom-emojis="notification.note.emojis"
|
||||||
:no-style="true"
|
:no-style="true"
|
||||||
/>
|
/>
|
||||||
|
<XReactionIcon
|
||||||
|
v-else-if="
|
||||||
|
notification.type === 'reaction' &&
|
||||||
|
!defaultStore.state.enableEmojiReactions
|
||||||
|
"
|
||||||
|
:reaction="defaultReaction"
|
||||||
|
:no-style="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tail">
|
<div class="tail">
|
||||||
|
@ -272,6 +283,8 @@ import { i18n } from "@/i18n";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import { instance } from "@/instance";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -288,6 +301,10 @@ const props = withDefaults(
|
||||||
const elRef = ref<HTMLElement>(null);
|
const elRef = ref<HTMLElement>(null);
|
||||||
const reactionRef = ref(null);
|
const reactionRef = ref(null);
|
||||||
|
|
||||||
|
const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReaction)
|
||||||
|
? instance.defaultReaction
|
||||||
|
: "⭐";
|
||||||
|
|
||||||
let readObserver: IntersectionObserver | undefined;
|
let readObserver: IntersectionObserver | undefined;
|
||||||
let connection;
|
let connection;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
@close="dialog.close()"
|
@close="dialog.close()"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.reactions }}</template>
|
<template #header>{{ i18n.ts.reaction }}</template>
|
||||||
|
|
||||||
<MkSpacer :margin-min="20" :margin-max="28">
|
<MkSpacer :margin-min="20" :margin-max="28">
|
||||||
<div v-if="note" class="_gaps">
|
<div v-if="note" class="_gaps">
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
|
||||||
|
class="_button"
|
||||||
|
:class="$style.root"
|
||||||
|
ref="buttonRef"
|
||||||
|
@click="toggleStar($event)"
|
||||||
|
>
|
||||||
|
<span v-if="!reacted">
|
||||||
|
<i
|
||||||
|
v-if="instance.defaultReaction === '👍'"
|
||||||
|
class="ph-thumbs-up ph-bold ph-lg"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
v-else-if="instance.defaultReaction === '❤️'"
|
||||||
|
class="ph-heart ph-bold ph-lg"
|
||||||
|
></i>
|
||||||
|
<i v-else class="ph-star ph-bold ph-lg"></i>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<i
|
||||||
|
v-if="instance.defaultReaction === '👍'"
|
||||||
|
class="ph-thumbs-up ph-bold ph-lg ph-fill"
|
||||||
|
:class="$style.yellow"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
v-else-if="instance.defaultReaction === '❤️'"
|
||||||
|
class="ph-heart ph-bold ph-lg ph-fill"
|
||||||
|
:class="$style.red"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
v-else
|
||||||
|
class="ph-star ph-bold ph-lg ph-fill"
|
||||||
|
:class="$style.yellow"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
<template v-if="count > 0"
|
||||||
|
><p :class="$style.count">{{ count }}</p></template
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import type { Note } from "calckey-js/built/entities";
|
||||||
|
import Ripple from "@/components/MkRipple.vue";
|
||||||
|
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||||
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { instance } from "@/instance";
|
||||||
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
note: Note;
|
||||||
|
count: number;
|
||||||
|
reacted: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const buttonRef = ref<HTMLElement>();
|
||||||
|
|
||||||
|
function toggleStar(ev?: MouseEvent): void {
|
||||||
|
pleaseLogin();
|
||||||
|
|
||||||
|
if (!props.reacted) {
|
||||||
|
os.api("notes/reactions/create", {
|
||||||
|
noteId: props.note.id,
|
||||||
|
reaction: instance.defaultReaction,
|
||||||
|
});
|
||||||
|
const el =
|
||||||
|
ev &&
|
||||||
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
|
if (el) {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
const y = rect.top + el.offsetHeight / 2;
|
||||||
|
os.popup(Ripple, { x, y }, {}, "end");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.api("notes/reactions/delete", {
|
||||||
|
noteId: props.note.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useTooltip(buttonRef, async (showing) => {
|
||||||
|
const reactions = await os.apiGet("notes/reactions", {
|
||||||
|
noteId: props.note.id,
|
||||||
|
limit: 11,
|
||||||
|
_cacheKey_: props.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = reactions.map((x) => x.user);
|
||||||
|
|
||||||
|
if (users.length < 1) return;
|
||||||
|
|
||||||
|
os.popup(
|
||||||
|
XDetails,
|
||||||
|
{
|
||||||
|
showing,
|
||||||
|
users,
|
||||||
|
count: props.count,
|
||||||
|
targetElement: buttonRef.value,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
"closed"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
display: inline-block;
|
||||||
|
height: 32px;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
color: var(--warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 0 0 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -114,6 +114,7 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
||||||
"swipeOnDesktop",
|
"swipeOnDesktop",
|
||||||
"showAdminUpdates",
|
"showAdminUpdates",
|
||||||
"enableCustomKaTeXMacro",
|
"enableCustomKaTeXMacro",
|
||||||
|
"enableEmojiReactions",
|
||||||
];
|
];
|
||||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||||
"lightTheme",
|
"lightTheme",
|
||||||
|
|
|
@ -1,81 +1,92 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="_formRoot">
|
<div class="_formRoot">
|
||||||
<FromSlot class="_formBlock">
|
<FormSwitch v-model="enableEmojiReactions" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.reactionSettingDescription }}</template>
|
{{ i18n.ts.enableEmojiReactions }}
|
||||||
<div v-panel style="border-radius: 6px">
|
|
||||||
<XDraggable
|
|
||||||
v-model="reactions"
|
|
||||||
class="zoaiodol"
|
|
||||||
:item-key="(item) => item"
|
|
||||||
animation="150"
|
|
||||||
delay="100"
|
|
||||||
delay-on-touch-only="true"
|
|
||||||
>
|
|
||||||
<template #item="{ element }">
|
|
||||||
<button
|
|
||||||
class="_button item"
|
|
||||||
@click="remove(element, $event)"
|
|
||||||
>
|
|
||||||
<MkEmoji :emoji="element" :normal="true" />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<button class="_button add" @click="chooseEmoji">
|
|
||||||
<i class="ph-plus ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</XDraggable>
|
|
||||||
</div>
|
|
||||||
<template #caption
|
|
||||||
>{{ i18n.ts.reactionSettingDescription2 }}
|
|
||||||
<button class="_textButton" @click="preview">
|
|
||||||
{{ i18n.ts.preview }}
|
|
||||||
</button></template
|
|
||||||
>
|
|
||||||
</FromSlot>
|
|
||||||
|
|
||||||
<FormRadios v-model="reactionPickerSize" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts.size }}</template>
|
|
||||||
<option :value="1">{{ i18n.ts.small }}</option>
|
|
||||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
|
||||||
<option :value="3">{{ i18n.ts.large }}</option>
|
|
||||||
</FormRadios>
|
|
||||||
<FormRadios v-model="reactionPickerWidth" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts.numberOfColumn }}</template>
|
|
||||||
<option :value="1">5</option>
|
|
||||||
<option :value="2">6</option>
|
|
||||||
<option :value="3">7</option>
|
|
||||||
<option :value="4">8</option>
|
|
||||||
<option :value="5">9</option>
|
|
||||||
</FormRadios>
|
|
||||||
<FormRadios v-model="reactionPickerHeight" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts.height }}</template>
|
|
||||||
<option :value="1">{{ i18n.ts.small }}</option>
|
|
||||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
|
||||||
<option :value="3">{{ i18n.ts.large }}</option>
|
|
||||||
<option :value="4">{{ i18n.ts.large }}+</option>
|
|
||||||
</FormRadios>
|
|
||||||
|
|
||||||
<FormSwitch
|
|
||||||
v-model="reactionPickerUseDrawerForMobile"
|
|
||||||
class="_formBlock"
|
|
||||||
>
|
|
||||||
{{ i18n.ts.useDrawerReactionPickerForMobile }}
|
|
||||||
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
|
|
||||||
<FormSection>
|
<div v-if="enableEmojiReactions">
|
||||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap">
|
<FromSlot class="_formBlock">
|
||||||
<FormButton inline @click="preview"
|
<template #label>{{
|
||||||
><i class="ph-eye ph-bold ph-lg"></i>
|
i18n.ts.reactionSettingDescription
|
||||||
{{ i18n.ts.preview }}</FormButton
|
}}</template>
|
||||||
|
<div v-panel style="border-radius: 6px">
|
||||||
|
<XDraggable
|
||||||
|
v-model="reactions"
|
||||||
|
class="zoaiodol"
|
||||||
|
:item-key="(item) => item"
|
||||||
|
animation="150"
|
||||||
|
delay="100"
|
||||||
|
delay-on-touch-only="true"
|
||||||
|
>
|
||||||
|
<template #item="{ element }">
|
||||||
|
<button
|
||||||
|
class="_button item"
|
||||||
|
@click="remove(element, $event)"
|
||||||
|
>
|
||||||
|
<MkEmoji :emoji="element" :normal="true" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<button class="_button add" @click="chooseEmoji">
|
||||||
|
<i class="ph-plus ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</XDraggable>
|
||||||
|
</div>
|
||||||
|
<template #caption
|
||||||
|
>{{ i18n.ts.reactionSettingDescription2 }}
|
||||||
|
<button class="_textButton" @click="preview">
|
||||||
|
{{ i18n.ts.preview }}
|
||||||
|
</button></template
|
||||||
>
|
>
|
||||||
<FormButton inline danger @click="setDefault"
|
</FromSlot>
|
||||||
><i class="ph-arrow-counter-clockwise ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.default }}</FormButton
|
<FormRadios v-model="reactionPickerSize" class="_formBlock">
|
||||||
>
|
<template #label>{{ i18n.ts.size }}</template>
|
||||||
</div>
|
<option :value="1">{{ i18n.ts.small }}</option>
|
||||||
</FormSection>
|
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||||
|
<option :value="3">{{ i18n.ts.large }}</option>
|
||||||
|
</FormRadios>
|
||||||
|
<FormRadios v-model="reactionPickerWidth" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts.numberOfColumn }}</template>
|
||||||
|
<option :value="1">5</option>
|
||||||
|
<option :value="2">6</option>
|
||||||
|
<option :value="3">7</option>
|
||||||
|
<option :value="4">8</option>
|
||||||
|
<option :value="5">9</option>
|
||||||
|
</FormRadios>
|
||||||
|
<FormRadios v-model="reactionPickerHeight" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts.height }}</template>
|
||||||
|
<option :value="1">{{ i18n.ts.small }}</option>
|
||||||
|
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||||
|
<option :value="3">{{ i18n.ts.large }}</option>
|
||||||
|
<option :value="4">{{ i18n.ts.large }}+</option>
|
||||||
|
</FormRadios>
|
||||||
|
|
||||||
|
<FormSwitch
|
||||||
|
v-model="reactionPickerUseDrawerForMobile"
|
||||||
|
class="_formBlock"
|
||||||
|
>
|
||||||
|
{{ i18n.ts.useDrawerReactionPickerForMobile }}
|
||||||
|
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
||||||
|
</FormSwitch>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<div style="display: flex; gap: var(--margin); flex-wrap: wrap">
|
||||||
|
<FormButton inline @click="preview"
|
||||||
|
><i class="ph-eye ph-bold ph-lg"></i>
|
||||||
|
{{ i18n.ts.preview }}</FormButton
|
||||||
|
>
|
||||||
|
<FormButton inline danger @click="setDefault"
|
||||||
|
><i
|
||||||
|
class="ph-arrow-counter-clockwise ph-bold ph-lg"
|
||||||
|
></i>
|
||||||
|
{{ i18n.ts.default }}</FormButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -108,6 +119,9 @@ const reactionPickerHeight = $computed(
|
||||||
const reactionPickerUseDrawerForMobile = $computed(
|
const reactionPickerUseDrawerForMobile = $computed(
|
||||||
defaultStore.makeGetterSetter("reactionPickerUseDrawerForMobile")
|
defaultStore.makeGetterSetter("reactionPickerUseDrawerForMobile")
|
||||||
);
|
);
|
||||||
|
const enableEmojiReactions = $computed(
|
||||||
|
defaultStore.makeGetterSetter("enableEmojiReactions")
|
||||||
|
);
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
defaultStore.set("reactions", reactions);
|
defaultStore.set("reactions", reactions);
|
||||||
|
|
|
@ -294,6 +294,10 @@ export const defaultStore = markRaw(
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
enableEmojiReactions: {
|
||||||
|
where: "account",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue