summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-03-08 08:56:47 +0900
committerGitHub <noreply@github.com>2023-03-08 08:56:47 +0900
commitdd6569a1bb025f2e295c9d19d870febcde712ea1 (patch)
tree7ac4c4b1674c7aab27db9cf4b657bb2966e1e37d /packages/frontend/src
parentfeat: Per-user renote mute (#10249) (diff)
downloadsharkey-dd6569a1bb025f2e295c9d19d870febcde712ea1.tar.gz
sharkey-dd6569a1bb025f2e295c9d19d870febcde712ea1.tar.bz2
sharkey-dd6569a1bb025f2e295c9d19d870febcde712ea1.zip
feat: Reaction acceptance (#10256)
* wip * wip * デフォルト設定
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/MkNote.vue33
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue33
-rw-r--r--packages/frontend/src/components/MkPostForm.vue27
-rw-r--r--packages/frontend/src/pages/settings/profile.vue12
-rw-r--r--packages/frontend/src/store.ts4
5 files changed, 89 insertions, 20 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index bb1269562d..af81051a54 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -103,7 +103,8 @@
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
- <i class="ti ti-plus"></i>
+ <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+ <i v-else class="ti ti-plus"></i>
</button>
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
@@ -329,18 +330,32 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
- blur();
- reactionPicker.show(reactButton.value, reaction => {
+ if (appearNote.reactionAcceptance === 'likeOnly') {
os.api('notes/reactions/create', {
noteId: appearNote.id,
- reaction: reaction,
+ reaction: '❤️',
});
- if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
- claimAchievement('reactWithoutRead');
+ const el = reactButton.value 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(MkRippleEffect, { x, y }, {}, 'end');
}
- }, () => {
- focus();
- });
+ } else {
+ blur();
+ reactionPicker.show(reactButton.value, reaction => {
+ os.api('notes/reactions/create', {
+ noteId: appearNote.id,
+ reaction: reaction,
+ });
+ if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+ claimAchievement('reactWithoutRead');
+ }
+ }, () => {
+ focus();
+ });
+ }
}
function undoReact(note): void {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index f5f4a2afc1..ea72e1b517 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -108,7 +108,8 @@
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
- <i class="ti ti-plus"></i>
+ <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+ <i v-else class="ti ti-plus"></i>
</button>
<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
@@ -323,18 +324,32 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
- blur();
- reactionPicker.show(reactButton.value, reaction => {
+ if (appearNote.reactionAcceptance === 'likeOnly') {
os.api('notes/reactions/create', {
noteId: appearNote.id,
- reaction: reaction,
+ reaction: '❤️',
});
- if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
- claimAchievement('reactWithoutRead');
+ const el = reactButton.value 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(MkRippleEffect, { x, y }, {}, 'end');
}
- }, () => {
- focus();
- });
+ } else {
+ blur();
+ reactionPicker.show(reactButton.value, reaction => {
+ os.api('notes/reactions/create', {
+ noteId: appearNote.id,
+ reaction: reaction,
+ });
+ if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+ claimAchievement('reactWithoutRead');
+ }
+ }, () => {
+ focus();
+ });
+ }
}
function undoReact(note): void {
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index f610569f6d..ee5a9e1810 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -53,14 +53,23 @@
<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<XNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
+ <div v-if="showingOptions" style="padding: 0 16px;">
+ <MkSelect v-model="reactionAcceptance" small>
+ <template #label>{{ i18n.ts.reactionAcceptance }}</template>
+ <option :value="null">{{ i18n.ts.all }}</option>
+ <option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
+ <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+ </MkSelect>
+ </div>
+ <button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.emojiButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
<footer :class="$style.footer">
<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
- <button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.footerButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
+ <button v-tooltip="i18n.ts.more" class="_button" :class="$style.footerButton" @click="showingOptions = !showingOptions"><i class="ti ti-dots"></i></button>
</footer>
<datalist id="hashtags">
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
@@ -76,6 +85,7 @@ import * as misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode/';
import * as Acct from 'misskey-js/built/acct';
+import MkSelect from './MkSelect.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
import XNotePreview from '@/components/MkNotePreview.vue';
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
@@ -151,12 +161,14 @@ let visibleUsers = $ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
+let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
let autocomplete = $ref(null);
let draghover = $ref(false);
let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
let imeText = $ref('');
+let showingOptions = $ref(false);
const draftKey = $computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -614,6 +626,7 @@ async function post(ev?: MouseEvent) {
localOnly: localOnly,
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
+ reactionAcceptance,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
@@ -1030,6 +1043,18 @@ defineExpose({
}
}
+.emojiButton {
+ position: absolute;
+ top: 55px;
+ right: 13px;
+ display: inline-block;
+ padding: 0;
+ margin: 0;
+ font-size: 1em;
+ width: 32px;
+ height: 32px;
+}
+
@container (max-width: 500px) {
.header {
height: 50px;
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 41563c441f..4776a87d5b 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -64,12 +64,19 @@
</div>
</MkFolder>
+ <MkSelect v-model="reactionAcceptance">
+ <template #label>{{ i18n.ts.reactionAcceptance }}</template>
+ <option :value="null">{{ i18n.ts.all }}</option>
+ <option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
+ <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+ </MkSelect>
+
<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
</div>
</template>
<script lang="ts" setup>
-import { reactive, watch } from 'vue';
+import { computed, reactive, watch } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -85,6 +92,9 @@ import { $i } from '@/account';
import { langmap } from '@/scripts/langmap';
import { definePageMetadata } from '@/scripts/page-metadata';
import { claimAchievement } from '@/scripts/achievements';
+import { defaultStore } from '@/store';
+
+const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
const profile = reactive({
name: $i.name,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index d84c056538..a68386fa4f 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -81,6 +81,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
+ reactionAcceptance: {
+ where: 'account',
+ default: null,
+ },
mutedWords: {
where: 'account',
default: [],