parent
							
								
									888dcd2559
								
							
						
					
					
						commit
						bef2534fa8
					
				
					 5 changed files with 369 additions and 154 deletions
				
			
		|  | @ -542,6 +542,7 @@ pluginInstallWarn: "信頼できないプラグインはインストールしな | |||
| deck: "デッキ" | ||||
| undeck: "デッキ解除" | ||||
| useBlurEffectForModal: "モーダルにぼかし効果を使用" | ||||
| useFullReactionPicker: "フル機能リアクションピッカーを使用" | ||||
| generateAccessToken: "アクセストークンの発行" | ||||
| permission: "権限" | ||||
| enableAll: "全て有効にする" | ||||
|  |  | |||
|  | @ -1,62 +1,94 @@ | |||
| <template> | ||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | ||||
| 	<div class="omfetrab _popup"> | ||||
| 		<header> | ||||
| 			<button v-for="(category, i) in categories" | ||||
| 				class="_button" | ||||
| 				@click="go(category)" | ||||
| 				:class="{ active: category.isActive }" | ||||
| 				:key="i" | ||||
| 			> | ||||
| 				<Fa :icon="category.icon" fixed-width/> | ||||
| 			</button> | ||||
| 		</header> | ||||
| 
 | ||||
| 		<input ref="search" class="search" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()" autofocus> | ||||
| 		<div class="emojis"> | ||||
| 			<template v-if="categories[0].isActive"> | ||||
| 				<header class="category"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header> | ||||
| 				<div class="list"> | ||||
| 					<button v-for="emoji in ($store.state.device.recentEmojis || [])" | ||||
| 			<section class="result"> | ||||
| 				<div v-if="searchResultCustom.length > 0"> | ||||
| 					<button v-for="emoji in searchResultCustom" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji)" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> | ||||
| 						<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<header class="category"><Fa :icon="faAsterisk" fixed-width/> {{ $t('customEmojis') }}</header> | ||||
| 			</template> | ||||
| 
 | ||||
| 			<template v-if="categories.find(x => x.isActive).name"> | ||||
| 				<div class="list"> | ||||
| 					<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)" | ||||
| 				<div v-if="searchResultUnicode.length > 0"> | ||||
| 					<button v-for="emoji in searchResultUnicode" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji)" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji.char"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 
 | ||||
| 			<div class="index"> | ||||
| 				<section> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in reactions || $store.state.settings.reactions" | ||||
| 							class="_button" | ||||
| 							:title="emoji.name" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							:key="emoji" | ||||
| 							tabindex="0" | ||||
| 						> | ||||
| 							<MkEmoji :emoji="emoji.startsWith(':') ? null : emoji" :name="emoji.startsWith(':') ? emoji.substr(1, emoji.length - 2) : null" :normal="true"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</section> | ||||
| 
 | ||||
| 				<section> | ||||
| 					<header class="_acrylic"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in ($store.state.device.recentEmojis || [])" | ||||
| 							class="_button" | ||||
| 							:title="emoji.name" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							:key="emoji" | ||||
| 						> | ||||
| 							<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> | ||||
| 							<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</section> | ||||
| 
 | ||||
| 				<div class="arrow"><Fa :icon="faChevronDown"/></div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom"> | ||||
| 				<header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $t('other') }}</header> | ||||
| 				<div v-if="visibleCategories[category]"> | ||||
| 					<button v-for="emoji in customEmojis.filter(e => e.category === category)" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 					> | ||||
| 						<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 
 | ||||
| 			<section v-for="category in categories" :key="category.name" class="unicode"> | ||||
| 				<header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header> | ||||
| 				<div v-if="category.isActive"> | ||||
| 					<button v-for="emoji in emojilist.filter(e => e.category === category.name)" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji.char"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 			<template v-else> | ||||
| 				<div v-for="(key, i) in Object.keys(customEmojis)" :key="i"> | ||||
| 					<header class="sub" v-if="key">{{ key }}</header> | ||||
| 					<div class="list"> | ||||
| 						<button v-for="emoji in customEmojis[key]" | ||||
| 							class="_button" | ||||
| 							:title="emoji.name" | ||||
| 							@click="chosen(emoji)" | ||||
| 							:key="emoji.name" | ||||
| 						> | ||||
| 							<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 			</section> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </MkModal> | ||||
|  | @ -66,10 +98,11 @@ | |||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { emojilist } from '../../misc/emojilist'; | ||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { groupByX } from '../../prelude/array'; | ||||
| import MkModal from '@/components/ui/modal.vue'; | ||||
| import Particle from '@/components/particle.vue'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -80,6 +113,9 @@ export default defineComponent({ | |||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		reactions: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['done', 'closed'], | ||||
|  | @ -88,12 +124,14 @@ export default defineComponent({ | |||
| 		return { | ||||
| 			emojilist: markRaw(emojilist), | ||||
| 			getStaticImageUrl, | ||||
| 			customEmojis: {}, | ||||
| 			faGlobe, faHistory, | ||||
| 			customEmojiCategories: this.$store.getters['instance/emojiCategories'], | ||||
| 			customEmojis: this.$store.state.instance.meta.emojis, | ||||
| 			visibleCategories: {}, | ||||
| 			q: null, | ||||
| 			searchResultCustom: [], | ||||
| 			searchResultUnicode: [], | ||||
| 			faGlobe, faHistory, faChevronDown, | ||||
| 			categories: [{ | ||||
| 				icon: faAsterisk, | ||||
| 				isActive: true | ||||
| 			}, { | ||||
| 				name: 'face', | ||||
| 				icon: faLaugh, | ||||
| 				isActive: false | ||||
|  | @ -134,38 +172,149 @@ export default defineComponent({ | |||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		let local = this.$store.state.instance.meta.emojis; | ||||
| 		local = groupByX(local, (x: any) => x.category || ''); | ||||
| 		this.customEmojis = markRaw(local); | ||||
| 	watch: { | ||||
| 		q() { | ||||
| 			if (this.q == null || this.q === '') { | ||||
| 				this.searchResultCustom = []; | ||||
| 				this.searchResultUnicode = []; | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			const q = this.q.replace(/:/g, ''); | ||||
| 
 | ||||
| 			const searchCustom = () => { | ||||
| 				const max = 8; | ||||
| 				const emojis = this.customEmojis; | ||||
| 				const matches = new Set(); | ||||
| 
 | ||||
| 				const exactMatch = emojis.find(e => e.name === q); | ||||
| 				if (exactMatch) matches.add(exactMatch); | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.name.startsWith(q)) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.aliases.some(alias => alias.startsWith(q))) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.name.includes(q)) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.aliases.some(alias => alias.includes(q))) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				return matches; | ||||
| 			}; | ||||
| 
 | ||||
| 			const searchUnicode = () => { | ||||
| 				const max = 8; | ||||
| 				const emojis = this.emojilist; | ||||
| 				const matches = new Set(); | ||||
| 
 | ||||
| 				const exactMatch = emojis.find(e => e.name === q); | ||||
| 				if (exactMatch) matches.add(exactMatch); | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.name.startsWith(q)) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.keywords.some(keyword => keyword.startsWith(q))) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.name.includes(q)) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				if (matches.size >= max) return matches; | ||||
| 
 | ||||
| 				for (const emoji of emojis) { | ||||
| 					if (emoji.keywords.some(keyword => keyword.includes(q))) { | ||||
| 						matches.add(emoji); | ||||
| 						if (matches.size >= max) break; | ||||
| 					} | ||||
| 				} | ||||
| 				return matches; | ||||
| 			}; | ||||
| 
 | ||||
| 			this.searchResultCustom = Array.from(searchCustom()); | ||||
| 			this.searchResultUnicode = Array.from(searchUnicode()); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.$refs.search.focus(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		go(category: any) { | ||||
| 			this.goCategory(category.name); | ||||
| 		}, | ||||
| 
 | ||||
| 		goCategory(name: string) { | ||||
| 			let matched = false; | ||||
| 			for (const c of this.categories) { | ||||
| 				c.isActive = c.name === name; | ||||
| 				if (c.isActive) { | ||||
| 					matched = true; | ||||
| 				} | ||||
| 		chosen(emoji: any, ev) { | ||||
| 			if (ev) { | ||||
| 				const el = ev.currentTarget || ev.target; | ||||
| 				const rect = el.getBoundingClientRect(); | ||||
| 				const x = rect.left + (el.clientWidth / 2); | ||||
| 				const y = rect.top + (el.clientHeight / 2); | ||||
| 				os.popup(Particle, { x, y }, {}, 'end'); | ||||
| 			} | ||||
| 			if (!matched) { | ||||
| 				this.categories[0].isActive = true; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		chosen(emoji: any) { | ||||
| 			const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`; | ||||
| 			const getKey = (emoji: any) => typeof emoji === 'string' ? emoji : emoji.char || `:${emoji.name}:`; | ||||
| 			this.$emit('done', getKey(emoji)); | ||||
| 			this.$refs.modal.close(); | ||||
| 
 | ||||
| 			// 最近使った絵文字更新 | ||||
| 			let recents = this.$store.state.device.recentEmojis || []; | ||||
| 			recents = recents.filter((e: any) => getKey(e) !== getKey(emoji)); | ||||
| 			recents.unshift(emoji) | ||||
| 			this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) }); | ||||
| 			this.$emit('done', getKey(emoji)); | ||||
| 			this.$refs.modal.close(); | ||||
| 		}, | ||||
| 
 | ||||
| 		paste(event) { | ||||
| 			const paste = (event.clipboardData || window.clipboardData).getData('text'); | ||||
| 			if (this.done(paste)) { | ||||
| 				event.preventDefault(); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		done(query) { | ||||
| 			if (query == null) query = this.q; | ||||
| 			if (query == null) return; | ||||
| 			const q = query.replace(/:/g, ''); | ||||
| 			const exactMatchCustom = this.customEmojis.find(e => e.name === q); | ||||
| 			if (exactMatchCustom) { | ||||
| 				this.chosen(exactMatchCustom); | ||||
| 				return true; | ||||
| 			} | ||||
| 			const exactMatchUnicode = this.emojilist.find(e => e.name === q); | ||||
| 			if (exactMatchUnicode) { | ||||
| 				this.chosen(exactMatchUnicode); | ||||
| 				return true; | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
|  | @ -174,85 +323,108 @@ export default defineComponent({ | |||
| <style lang="scss" scoped> | ||||
| .omfetrab { | ||||
| 	width: 350px; | ||||
| 	contain: content; | ||||
| 
 | ||||
| 	> header { | ||||
| 		display: flex; | ||||
| 
 | ||||
| 		> button { | ||||
| 			flex: 1; | ||||
| 			padding: 10px 0; | ||||
| 			font-size: 16px; | ||||
| 			transition: color 0.2s ease; | ||||
| 
 | ||||
| 			&:hover { | ||||
| 				color: var(--fgHighlighted); | ||||
| 				transition: color 0s; | ||||
| 			} | ||||
| 
 | ||||
| 			&.active { | ||||
| 				color: var(--accent); | ||||
| 				transition: color 0s; | ||||
| 			} | ||||
| 		} | ||||
| 	> .search { | ||||
| 		width: 100%; | ||||
| 		padding: 12px; | ||||
| 		box-sizing: border-box; | ||||
| 		font-size: 1em; | ||||
| 		outline: none; | ||||
| 		border: none; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 	} | ||||
| 
 | ||||
| 	> .emojis { | ||||
| 		height: 300px; | ||||
| 		$height: 300px; | ||||
| 
 | ||||
| 		height: $height; | ||||
| 		overflow-y: auto; | ||||
| 		overflow-x: hidden; | ||||
| 
 | ||||
| 		> header.category { | ||||
| 			position: sticky; | ||||
| 			top: 0; | ||||
| 			left: 0; | ||||
| 			z-index: 1; | ||||
| 			padding: 8px; | ||||
| 			background: var(--panel); | ||||
| 			font-size: 12px; | ||||
| 		} | ||||
| 		> .index { | ||||
| 			min-height: $height; | ||||
| 			position: relative; | ||||
| 			border-bottom: solid 1px var(--divider); | ||||
| 				 | ||||
| 		header.sub { | ||||
| 			padding: 4px 8px; | ||||
| 			font-size: 12px; | ||||
| 		} | ||||
| 
 | ||||
| 		div.list { | ||||
| 			display: grid; | ||||
| 			grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; | ||||
| 			gap: 4px; | ||||
| 			padding: 8px; | ||||
| 
 | ||||
| 			> button { | ||||
| 				position: relative; | ||||
| 				padding: 0; | ||||
| 			> .arrow { | ||||
| 				position: absolute; | ||||
| 				bottom: 0; | ||||
| 				left: 0; | ||||
| 				width: 100%; | ||||
| 				padding: 16px 0; | ||||
| 				text-align: center; | ||||
| 				opacity: 0.5; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 				&:before { | ||||
| 					content: ''; | ||||
| 					display: block; | ||||
| 					width: 1px; | ||||
| 					height: 0; | ||||
| 					padding-bottom: 100%; | ||||
| 				} | ||||
| 		section { | ||||
| 			> header { | ||||
| 				position: sticky; | ||||
| 				top: 0; | ||||
| 				left: 0; | ||||
| 				z-index: 1; | ||||
| 				padding: 8px; | ||||
| 				font-size: 12px; | ||||
| 			} | ||||
| 
 | ||||
| 			> div { | ||||
| 				display: grid; | ||||
| 				grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; | ||||
| 				gap: 4px; | ||||
| 				padding: 8px; | ||||
| 
 | ||||
| 				> button { | ||||
| 					position: relative; | ||||
| 					padding: 0; | ||||
| 					width: 100%; | ||||
| 
 | ||||
| 					&:focus { | ||||
| 						outline: solid 2px var(--focus); | ||||
| 						z-index: 1; | ||||
| 					} | ||||
| 
 | ||||
| 					&:before { | ||||
| 						content: ''; | ||||
| 						display: block; | ||||
| 						width: 1px; | ||||
| 						height: 0; | ||||
| 						padding-bottom: 100%; | ||||
| 					} | ||||
| 
 | ||||
| 					&:hover { | ||||
| 						> * { | ||||
| 							transform: scale(1.2); | ||||
| 							transition: transform 0s; | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 				&:hover { | ||||
| 					> * { | ||||
| 						transform: scale(1.2); | ||||
| 						transition: transform 0s; | ||||
| 						position: absolute; | ||||
| 						top: 0; | ||||
| 						left: 0; | ||||
| 						width: 100%; | ||||
| 						height: 100%; | ||||
| 						object-fit: contain; | ||||
| 						font-size: 28px; | ||||
| 						transition: transform 0.2s ease; | ||||
| 						pointer-events: none; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 				> * { | ||||
| 					position: absolute; | ||||
| 					top: 0; | ||||
| 					left: 0; | ||||
| 					width: 100%; | ||||
| 					height: 100%; | ||||
| 					object-fit: contain; | ||||
| 					font-size: 28px; | ||||
| 					transition: transform 0.2s ease; | ||||
| 					pointer-events: none; | ||||
| 				} | ||||
| 			&.result { | ||||
| 				border-bottom: solid 1px var(--divider); | ||||
| 			} | ||||
| 
 | ||||
| 			&.unicode { | ||||
| 				min-height: 384px; | ||||
| 			} | ||||
| 
 | ||||
| 			&.custom { | ||||
| 				min-height: 64px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -498,20 +498,36 @@ export default defineComponent({ | |||
| 		react(viaKeyboard = false) { | ||||
| 			pleaseLogin(); | ||||
| 			this.blur(); | ||||
| 			os.popup(import('@/components/reaction-picker.vue'), { | ||||
| 				showFocus: viaKeyboard, | ||||
| 				src: this.$refs.reactButton, | ||||
| 			}, { | ||||
| 				done: reaction => { | ||||
| 					if (reaction) { | ||||
| 						os.api('notes/reactions/create', { | ||||
| 							noteId: this.appearNote.id, | ||||
| 							reaction: reaction | ||||
| 						}); | ||||
| 					} | ||||
| 					this.focus(); | ||||
| 				}, | ||||
| 			}, 'closed'); | ||||
| 			if (this.$store.state.device.useFullReactionPicker) { | ||||
| 				os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 					src: this.$refs.reactButton, | ||||
| 				}, { | ||||
| 					done: reaction => { | ||||
| 						if (reaction) { | ||||
| 							os.api('notes/reactions/create', { | ||||
| 								noteId: this.appearNote.id, | ||||
| 								reaction: reaction | ||||
| 							}); | ||||
| 						} | ||||
| 						this.focus(); | ||||
| 					}, | ||||
| 				}, 'closed'); | ||||
| 			} else { | ||||
| 				os.popup(import('@/components/reaction-picker.vue'), { | ||||
| 					showFocus: viaKeyboard, | ||||
| 					src: this.$refs.reactButton, | ||||
| 				}, { | ||||
| 					done: reaction => { | ||||
| 						if (reaction) { | ||||
| 							os.api('notes/reactions/create', { | ||||
| 								noteId: this.appearNote.id, | ||||
| 								reaction: reaction | ||||
| 							}); | ||||
| 						} | ||||
| 						this.focus(); | ||||
| 					}, | ||||
| 				}, 'closed'); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		reactDirectly(reaction) { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| 				{{ $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> | ||||
| 			<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> | ||||
|  | @ -22,6 +23,7 @@ import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons'; | |||
| import { faUndo } from '@fortawesome/free-solid-svg-icons'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkSwitch from '@/components/ui/switch.vue'; | ||||
| import { emojiRegexWithCustom } from '../../../misc/emoji-regex'; | ||||
| import { defaultSettings } from '@/store'; | ||||
| import * as os from '@/os'; | ||||
|  | @ -30,6 +32,7 @@ export default defineComponent({ | |||
| 	components: { | ||||
| 		MkInput, | ||||
| 		MkButton, | ||||
| 		MkSwitch, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['info'], | ||||
|  | @ -50,6 +53,11 @@ export default defineComponent({ | |||
| 		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 }); } | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	watch: { | ||||
|  | @ -72,11 +80,18 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		preview(ev) { | ||||
| 			os.popup(import('@/components/reaction-picker.vue'), { | ||||
| 				reactions: this.splited, | ||||
| 				showFocus: false, | ||||
| 				src: ev.currentTarget || ev.target, | ||||
| 			}, {}, 'closed'); | ||||
| 			if (this.$store.state.device.useFullReactionPicker) { | ||||
| 				os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 					reactions: this.splited, | ||||
| 					src: ev.currentTarget || ev.target, | ||||
| 				}, {}, 'closed'); | ||||
| 			} else { | ||||
| 				os.popup(import('@/components/reaction-picker.vue'), { | ||||
| 					reactions: this.splited, | ||||
| 					showFocus: false, | ||||
| 					src: ev.currentTarget || ev.target, | ||||
| 				}, {}, 'closed'); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		setDefault() { | ||||
|  |  | |||
|  | @ -76,6 +76,7 @@ export const defaultDeviceSettings = { | |||
| 	disablePagesScript: false, | ||||
| 	enableInfiniteScroll: true, | ||||
| 	useBlurEffectForModal: true, | ||||
| 	useFullReactionPicker: false, | ||||
| 	sidebarDisplay: 'full', // full, icon, hide
 | ||||
| 	instanceTicker: 'remote', // none, remote, always
 | ||||
| 	roomGraphicsQuality: 'medium', | ||||
|  | @ -182,6 +183,16 @@ export const store = createStore({ | |||
| 				meta: null | ||||
| 			}, | ||||
| 
 | ||||
| 			getters: { | ||||
| 				emojiCategories: state => { | ||||
| 					const categories = new Set(); | ||||
| 					for (const emoji of state.meta.emojis) { | ||||
| 						categories.add(emoji.category); | ||||
| 					} | ||||
| 					return Array.from(categories); | ||||
| 				}, | ||||
| 			}, | ||||
| 
 | ||||
| 			mutations: { | ||||
| 				set(state, meta) { | ||||
| 					state.meta = meta; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue