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