From 03b072b894b6be5ca8b94bb0ab73134705b45145 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 14 Nov 2020 14:32:01 +0900
Subject: [PATCH] Resolve #6704

---
 locales/ja-JP.yml                      |  1 +
 src/client/components/emoji-picker.vue |  9 ++--
 src/client/os.ts                       |  5 +-
 src/client/pages/settings/reaction.vue | 72 +++++++++++++++++++-------
 4 files changed, 62 insertions(+), 25 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6dcae50182..62da82b91c 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -214,6 +214,7 @@ imageUrl: "画像URL"
 remove: "削除"
 removed: "削除しました"
 removeAreYouSure: "「{x}」を削除しますか?"
+resetAreYouSure: "リセットしますか?"
 saved: "保存しました"
 messaging: "チャット"
 upload: "アップロード"
diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue
index 5d60f2eb51..5cfd97e374 100644
--- a/src/client/components/emoji-picker.vue
+++ b/src/client/components/emoji-picker.vue
@@ -30,7 +30,7 @@
 			</section>
 
 			<div class="index">
-				<section>
+				<section v-if="showPinned">
 					<div>
 						<button v-for="emoji in pinned"
 							class="_button"
@@ -109,8 +109,9 @@ export default defineComponent({
 		src: {
 			required: false
 		},
-		overridePinned: {
-			required: false
+		showPinned: {
+			required: false,
+			default: true
 		},
 		compact: {
 			required: false
@@ -123,7 +124,7 @@ export default defineComponent({
 		return {
 			emojilist: markRaw(emojilist),
 			getStaticImageUrl,
-			pinned: this.overridePinned || this.$store.state.settings.reactions,
+			pinned: this.$store.state.settings.reactions,
 			customEmojiCategories: this.$store.getters['instance/emojiCategories'],
 			customEmojis: this.$store.state.instance.meta.emojis,
 			visibleCategories: {},
diff --git a/src/client/os.ts b/src/client/os.ts
index e917f8a4a3..88d445ebac 100644
--- a/src/client/os.ts
+++ b/src/client/os.ts
@@ -275,10 +275,11 @@ export async function selectDriveFolder(multiple: boolean) {
 	});
 }
 
-export async function pickEmoji(src?: HTMLElement) {
+export async function pickEmoji(src?: HTMLElement, opts) {
 	return new Promise((resolve, reject) => {
 		popup(import('@/components/emoji-picker.vue'), {
-			src
+			src,
+			...opts
 		}, {
 			done: emoji => {
 				resolve(emoji);
diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/settings/reaction.vue
index 52b9f3260b..ad8430c2f7 100644
--- a/src/client/pages/settings/reaction.vue
+++ b/src/client/pages/settings/reaction.vue
@@ -3,15 +3,20 @@
 	<div class="_card">
 		<div class="_title"><Fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
 		<div class="_content">
-			<MkInput v-model:value="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif">
-				{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template>
-			</MkInput>
-			<MkButton inline @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</MkButton>
+			<XDraggable class="zoaiodol" :list="reactions" animation="150" delay="100" delay-on-touch-only="true">
+				<button class="_button item" v-for="reaction in reactions" :key="reaction" @click="remove(reaction, $event)">
+					<MkEmoji :emoji="reaction" :normal="true"/>
+				</button>
+				<template #footer>
+					<button>a</button>
+				</template>
+			</XDraggable>
+			<div class="_caption" style="padding: 8px;">{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div>
 			<MkSwitch v-model:value="useFullReactionPicker">{{ $t('useFullReactionPicker') }}</MkSwitch>
 		</div>
 		<div class="_footer">
-			<MkButton @click="save()" primary inline :disabled="!changed"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
 			<MkButton inline @click="preview"><Fa :icon="faEye"/> {{ $t('preview') }}</MkButton>
+			<MkButton inline @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</MkButton>
 		</div>
 	</div>
 </div>
@@ -21,6 +26,7 @@
 import { defineComponent } from 'vue';
 import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
 import { faUndo } from '@fortawesome/free-solid-svg-icons';
+import { VueDraggableNext } from 'vue-draggable-next';
 import MkInput from '@/components/ui/input.vue';
 import MkButton from '@/components/ui/button.vue';
 import MkSwitch from '@/components/ui/switch.vue';
@@ -33,6 +39,7 @@ export default defineComponent({
 		MkInput,
 		MkButton,
 		MkSwitch,
+		XDraggable: VueDraggableNext,
 	},
 
 	emits: ['info'],
@@ -43,17 +50,12 @@ export default defineComponent({
 				title: this.$t('reaction'),
 				icon: faLaugh
 			},
-			reactions: this.$store.state.settings.reactions.join(''),
-			changed: false,
+			reactions: JSON.parse(JSON.stringify(this.$store.state.settings.reactions)),
 			faLaugh, faSave, faEye, faUndo
 		}
 	},
 
 	computed: {
-		splited(): any {
-			return this.reactions.match(emojiRegexWithCustom);
-		},
-
 		useFullReactionPicker: {
 			get() { return this.$store.state.device.useFullReactionPicker; },
 			set(value) { this.$store.commit('device/set', { key: 'useFullReactionPicker', value: value }); }
@@ -63,7 +65,7 @@ export default defineComponent({
 	watch: {
 		reactions: {
 			handler() {
-				this.changed = true;
+				this.save();
 			},
 			deep: true
 		}
@@ -75,27 +77,59 @@ export default defineComponent({
 
 	methods: {
 		save() {
-			this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited });
-			this.changed = false;
+			this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions });
+		},
+
+		remove(reaction, ev) {
+			os.modalMenu([{
+				text: this.$t('remove'),
+				action: () => {
+					this.reactions = this.reactions.filter(x => x !== reaction)
+				}
+			}], ev.currentTarget || ev.target);
 		},
 
 		preview(ev) {
 			os.popup(import('@/components/emoji-picker.vue'), {
-				overridePinned: this.splited,
 				compact: !this.$store.state.device.useFullReactionPicker,
 				src: ev.currentTarget || ev.target,
 			}, {}, 'closed');
 		},
 
-		setDefault() {
-			this.reactions = defaultSettings.reactions.join('');
+		async setDefault() {
+			const { canceled } = await os.dialog({
+				type: 'warning',
+				text: this.$t('resetAreYouSure'),
+				showCancelButton: true
+			});
+			if (canceled) return;
+
+			this.reactions = JSON.parse(JSON.stringify(defaultSettings.reactions));
 		},
 
 		chooseEmoji(ev) {
-			os.pickEmoji(ev.currentTarget || ev.target).then(emoji => {
-				this.reactions += emoji;
+			os.pickEmoji(ev.currentTarget || ev.target, {
+				showPinned: false
+			}).then(emoji => {
+				if (!this.reactions.includes(emoji)) {
+					this.reactions.push(emoji);
+				}
 			});
 		}
 	}
 });
 </script>
+
+<style lang="scss" scoped>
+.zoaiodol {
+	border: solid 1px var(--divider);
+	border-radius: var(--radius);
+	padding: 16px;
+
+	> .item {
+		display: inline-block;
+		padding: 8px;
+		cursor: move;
+	}
+}
+</style>