parent
							
								
									f2e071baaa
								
							
						
					
					
						commit
						f29d417b30
					
				
					 13 changed files with 563 additions and 113 deletions
				
			
		
							
								
								
									
										191
									
								
								src/client/components/emoji-picker-dialog.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								src/client/components/emoji-picker-dialog.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | |||
| <template> | ||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | ||||
| 	<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> | ||||
| </MkModal> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import MkModal from '@/components/ui/modal.vue'; | ||||
| import MkEmojiPicker from '@/components/emoji-picker.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkModal, | ||||
| 		MkEmojiPicker, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		showPinned: { | ||||
| 			required: false, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		asReactionPicker: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['done', 'closed'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 
 | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		chosen(emoji: any) { | ||||
| 			this.$emit('done', emoji); | ||||
| 			this.$refs.modal.close(); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .omfetrab { | ||||
| 	$pad: 8px; | ||||
| 	--eachSize: 40px; | ||||
| 
 | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	contain: content; | ||||
| 
 | ||||
| 	&.big { | ||||
| 		--eachSize: 44px; | ||||
| 	} | ||||
| 
 | ||||
| 	&.w1 { | ||||
| 		width: calc((var(--eachSize) * 5) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.w2 { | ||||
| 		width: calc((var(--eachSize) * 6) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.w3 { | ||||
| 		width: calc((var(--eachSize) * 7) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h1 { | ||||
| 		--height: calc((var(--eachSize) * 4) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h2 { | ||||
| 		--height: calc((var(--eachSize) * 6) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h3 { | ||||
| 		--height: calc((var(--eachSize) * 8) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	> .search { | ||||
| 		width: 100%; | ||||
| 		padding: 12px; | ||||
| 		box-sizing: border-box; | ||||
| 		font-size: 1em; | ||||
| 		outline: none; | ||||
| 		border: none; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 
 | ||||
| 		&:not(.filled) { | ||||
| 			order: 1; | ||||
| 			z-index: 2; | ||||
| 			box-shadow: 0px -1px 0 0px var(--divider); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .emojis { | ||||
| 		height: var(--height); | ||||
| 		overflow-y: auto; | ||||
| 		overflow-x: hidden; | ||||
| 
 | ||||
| 		scrollbar-width: none; | ||||
| 
 | ||||
| 		&::-webkit-scrollbar { | ||||
| 			display: none; | ||||
| 		} | ||||
| 
 | ||||
| 		> .index { | ||||
| 			min-height: var(--height); | ||||
| 			position: relative; | ||||
| 			border-bottom: solid 1px var(--divider); | ||||
| 				 | ||||
| 			> .arrow { | ||||
| 				position: absolute; | ||||
| 				bottom: 0; | ||||
| 				left: 0; | ||||
| 				width: 100%; | ||||
| 				padding: 16px 0; | ||||
| 				text-align: center; | ||||
| 				opacity: 0.5; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		section { | ||||
| 			> header { | ||||
| 				position: sticky; | ||||
| 				top: 0; | ||||
| 				left: 0; | ||||
| 				z-index: 1; | ||||
| 				padding: 8px; | ||||
| 				font-size: 12px; | ||||
| 			} | ||||
| 
 | ||||
| 			> div { | ||||
| 				padding: $pad; | ||||
| 
 | ||||
| 				> button { | ||||
| 					position: relative; | ||||
| 					padding: 0; | ||||
| 					width: var(--eachSize); | ||||
| 					height: var(--eachSize); | ||||
| 					border-radius: 4px; | ||||
| 
 | ||||
| 					&:focus { | ||||
| 						outline: solid 2px var(--focus); | ||||
| 						z-index: 1; | ||||
| 					} | ||||
| 
 | ||||
| 					&:hover { | ||||
| 						background: rgba(0, 0, 0, 0.05); | ||||
| 					} | ||||
| 
 | ||||
| 					&:active { | ||||
| 						background: var(--accent); | ||||
| 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); | ||||
| 					} | ||||
| 
 | ||||
| 					> * { | ||||
| 						font-size: 24px; | ||||
| 						height: 1.25em; | ||||
| 						vertical-align: -.25em; | ||||
| 						pointer-events: none; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			&.result { | ||||
| 				border-bottom: solid 1px var(--divider); | ||||
| 
 | ||||
| 				&:empty { | ||||
| 					display: none; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			&.unicode { | ||||
| 				min-height: 384px; | ||||
| 			} | ||||
| 
 | ||||
| 			&.custom { | ||||
| 				min-height: 64px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										197
									
								
								src/client/components/emoji-picker-window.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/client/components/emoji-picker-window.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,197 @@ | |||
| <template> | ||||
| <MkWindow ref="window" | ||||
| 	:initial-width="null" | ||||
| 	:initial-height="null" | ||||
| 	:can-resize="false" | ||||
| 	:mini="true" | ||||
| 	:front="true" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> | ||||
| </MkWindow> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import MkWindow from '@/components/ui/window.vue'; | ||||
| import MkEmojiPicker from '@/components/emoji-picker.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkWindow, | ||||
| 		MkEmojiPicker, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		showPinned: { | ||||
| 			required: false, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		asReactionPicker: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['chosen', 'closed'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 
 | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		chosen(emoji: any) { | ||||
| 			this.$emit('chosen', emoji); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .omfetrab { | ||||
| 	$pad: 8px; | ||||
| 	--eachSize: 40px; | ||||
| 
 | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	contain: content; | ||||
| 
 | ||||
| 	&.big { | ||||
| 		--eachSize: 44px; | ||||
| 	} | ||||
| 
 | ||||
| 	&.w1 { | ||||
| 		width: calc((var(--eachSize) * 5) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.w2 { | ||||
| 		width: calc((var(--eachSize) * 6) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.w3 { | ||||
| 		width: calc((var(--eachSize) * 7) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h1 { | ||||
| 		--height: calc((var(--eachSize) * 4) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h2 { | ||||
| 		--height: calc((var(--eachSize) * 6) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	&.h3 { | ||||
| 		--height: calc((var(--eachSize) * 8) + (#{$pad} * 2)); | ||||
| 	} | ||||
| 
 | ||||
| 	> .search { | ||||
| 		width: 100%; | ||||
| 		padding: 12px; | ||||
| 		box-sizing: border-box; | ||||
| 		font-size: 1em; | ||||
| 		outline: none; | ||||
| 		border: none; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 
 | ||||
| 		&:not(.filled) { | ||||
| 			order: 1; | ||||
| 			z-index: 2; | ||||
| 			box-shadow: 0px -1px 0 0px var(--divider); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .emojis { | ||||
| 		height: var(--height); | ||||
| 		overflow-y: auto; | ||||
| 		overflow-x: hidden; | ||||
| 
 | ||||
| 		scrollbar-width: none; | ||||
| 
 | ||||
| 		&::-webkit-scrollbar { | ||||
| 			display: none; | ||||
| 		} | ||||
| 
 | ||||
| 		> .index { | ||||
| 			min-height: var(--height); | ||||
| 			position: relative; | ||||
| 			border-bottom: solid 1px var(--divider); | ||||
| 				 | ||||
| 			> .arrow { | ||||
| 				position: absolute; | ||||
| 				bottom: 0; | ||||
| 				left: 0; | ||||
| 				width: 100%; | ||||
| 				padding: 16px 0; | ||||
| 				text-align: center; | ||||
| 				opacity: 0.5; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		section { | ||||
| 			> header { | ||||
| 				position: sticky; | ||||
| 				top: 0; | ||||
| 				left: 0; | ||||
| 				z-index: 1; | ||||
| 				padding: 8px; | ||||
| 				font-size: 12px; | ||||
| 			} | ||||
| 
 | ||||
| 			> div { | ||||
| 				padding: $pad; | ||||
| 
 | ||||
| 				> button { | ||||
| 					position: relative; | ||||
| 					padding: 0; | ||||
| 					width: var(--eachSize); | ||||
| 					height: var(--eachSize); | ||||
| 					border-radius: 4px; | ||||
| 
 | ||||
| 					&:focus { | ||||
| 						outline: solid 2px var(--focus); | ||||
| 						z-index: 1; | ||||
| 					} | ||||
| 
 | ||||
| 					&:hover { | ||||
| 						background: rgba(0, 0, 0, 0.05); | ||||
| 					} | ||||
| 
 | ||||
| 					&:active { | ||||
| 						background: var(--accent); | ||||
| 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); | ||||
| 					} | ||||
| 
 | ||||
| 					> * { | ||||
| 						font-size: 24px; | ||||
| 						height: 1.25em; | ||||
| 						vertical-align: -.25em; | ||||
| 						pointer-events: none; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			&.result { | ||||
| 				border-bottom: solid 1px var(--divider); | ||||
| 
 | ||||
| 				&:empty { | ||||
| 					display: none; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			&.unicode { | ||||
| 				min-height: 384px; | ||||
| 			} | ||||
| 
 | ||||
| 			&.custom { | ||||
| 				min-height: 64px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -1,93 +1,91 @@ | |||
| <template> | ||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | ||||
| 	<div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]"> | ||||
| 		<input ref="search" class="search" :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> | ||||
| 		<div class="emojis" ref="emojis"> | ||||
| 			<section class="result"> | ||||
| 				<div v-if="searchResultCustom.length > 0"> | ||||
| 					<button v-for="emoji in searchResultCustom" | ||||
| <div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]"> | ||||
| 	<input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> | ||||
| 	<div class="emojis" ref="emojis"> | ||||
| 		<section class="result"> | ||||
| 			<div v-if="searchResultCustom.length > 0"> | ||||
| 				<button v-for="emoji in searchResultCustom" | ||||
| 					class="_button" | ||||
| 					:title="emoji.name" | ||||
| 					@click="chosen(emoji, $event)" | ||||
| 					:key="emoji" | ||||
| 					tabindex="0" | ||||
| 				> | ||||
| 					<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> | ||||
| 					<img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 			<div v-if="searchResultUnicode.length > 0"> | ||||
| 				<button v-for="emoji in searchResultUnicode" | ||||
| 					class="_button" | ||||
| 					:title="emoji.name" | ||||
| 					@click="chosen(emoji, $event)" | ||||
| 					:key="emoji.name" | ||||
| 					tabindex="0" | ||||
| 				> | ||||
| 					<MkEmoji :emoji="emoji.char"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</section> | ||||
| 
 | ||||
| 		<div class="index"> | ||||
| 			<section v-if="showPinned"> | ||||
| 				<div> | ||||
| 					<button v-for="emoji in pinned" | ||||
| 						class="_button" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 
 | ||||
| 			<section> | ||||
| 				<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $ts.recentUsed }}</header> | ||||
| 				<div> | ||||
| 					<button v-for="emoji in $store.state.recentlyUsedEmojis" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> | ||||
| 						<img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 				<div v-if="searchResultUnicode.length > 0"> | ||||
| 					<button v-for="emoji in searchResultUnicode" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji.char"/> | ||||
| 						<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 
 | ||||
| 			<div class="index"> | ||||
| 				<section v-if="showPinned"> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in pinned" | ||||
| 							class="_button" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							tabindex="0" | ||||
| 						> | ||||
| 							<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</section> | ||||
| 
 | ||||
| 				<section> | ||||
| 					<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $ts.recentUsed }}</header> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in $store.state.recentlyUsedEmojis" | ||||
| 							class="_button" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							:key="emoji" | ||||
| 						> | ||||
| 							<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						</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 || $ts.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.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> | ||||
| 			</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 || $ts.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.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> | ||||
| 		</section> | ||||
| 	</div> | ||||
| </MkModal> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|  | @ -96,7 +94,6 @@ import { emojilist } from '../../misc/emojilist'; | |||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons'; | ||||
| import MkModal from '@/components/ui/modal.vue'; | ||||
| import Particle from '@/components/particle.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { isDeviceTouch } from '@/scripts/is-device-touch'; | ||||
|  | @ -104,14 +101,7 @@ import { isMobile } from '@/scripts/is-mobile'; | |||
| import { emojiCategories } from '@/instance'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkModal, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		showPinned: { | ||||
| 			required: false, | ||||
| 			default: true | ||||
|  | @ -121,7 +111,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['done', 'closed'], | ||||
| 	emits: ['chosen'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
|  | @ -345,8 +335,7 @@ export default defineComponent({ | |||
| 			} | ||||
| 
 | ||||
| 			const key = this.getKey(emoji); | ||||
| 			this.$emit('done', key); | ||||
| 			this.$refs.modal.close(); | ||||
| 			this.$emit('chosen', key); | ||||
| 
 | ||||
| 			// 最近使った絵文字更新 | ||||
| 			if (!this.pinned.includes(key)) { | ||||
|  |  | |||
|  | @ -523,7 +523,7 @@ export default defineComponent({ | |||
| 		react(viaKeyboard = false) { | ||||
| 			pleaseLogin(); | ||||
| 			this.blur(); | ||||
| 			os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 			os.popup(import('@/components/emoji-picker-dialog.vue'), { | ||||
| 				src: this.$refs.reactButton, | ||||
| 				asReactionPicker: true | ||||
| 			}, { | ||||
|  |  | |||
|  | @ -498,7 +498,7 @@ export default defineComponent({ | |||
| 		react(viaKeyboard = false) { | ||||
| 			pleaseLogin(); | ||||
| 			this.blur(); | ||||
| 			os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 			os.popup(import('@/components/emoji-picker-dialog.vue'), { | ||||
| 				src: this.$refs.reactButton, | ||||
| 				asReactionPicker: true | ||||
| 			}, { | ||||
|  |  | |||
|  | @ -606,9 +606,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		async insertEmoji(ev) { | ||||
| 			os.pickEmoji(ev.currentTarget || ev.target).then(emoji => { | ||||
| 				insertTextAtCursor(this.$refs.text, emoji); | ||||
| 			}); | ||||
| 			os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); | ||||
| 		}, | ||||
| 
 | ||||
| 		showActions(ev) { | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| <template> | ||||
| <transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')"> | ||||
| 	<div class="ebkgocck" v-if="showing"> | ||||
| 	<div class="ebkgocck" :class="{ front }" v-if="showing"> | ||||
| 		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> | ||||
| 			<div class="header" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 				<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | ||||
| 				<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 
 | ||||
|  | @ -92,6 +92,16 @@ export default defineComponent({ | |||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		mini: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		front: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		contextmenu: { | ||||
| 			type: Array, | ||||
| 			required: false, | ||||
|  | @ -387,6 +397,10 @@ export default defineComponent({ | |||
| 	left: 0; | ||||
| 	z-index: 5000; | ||||
| 
 | ||||
| 	&.front { | ||||
| 		z-index: 11000; // front指定の時は、mk-modalのよりも大きくなければならない | ||||
| 	} | ||||
| 
 | ||||
| 	> .body { | ||||
| 		overflow: hidden; // overflow: clip; をSafariが対応したら消す | ||||
| 		overflow: clip; | ||||
|  | @ -397,17 +411,22 @@ export default defineComponent({ | |||
|     height: 100%; | ||||
| 
 | ||||
| 		> .header { | ||||
| 			$height: 50px; | ||||
| 			--height: 50px; | ||||
| 
 | ||||
| 			&.mini { | ||||
| 				--height: 38px; | ||||
| 			} | ||||
| 
 | ||||
| 			display: flex; | ||||
| 			position: relative; | ||||
| 			z-index: 1; | ||||
| 			flex-shrink: 0; | ||||
| 			user-select: none; | ||||
| 			height: $height; | ||||
| 			height: var(--height); | ||||
| 
 | ||||
| 			> ::v-deep(button) { | ||||
| 				height: $height; | ||||
| 				width: $height; | ||||
| 				height: var(--height); | ||||
| 				width: var(--height); | ||||
| 
 | ||||
| 				&:hover { | ||||
| 					color: var(--fgHighlighted); | ||||
|  | @ -417,7 +436,7 @@ export default defineComponent({ | |||
| 			> .title { | ||||
| 				flex: 1; | ||||
| 				position: relative; | ||||
| 				line-height: $height; | ||||
| 				line-height: var(--height); | ||||
| 				white-space: nowrap; | ||||
| 				overflow: hidden; // overflow: clip; をSafariが対応したら消す | ||||
| 				overflow: clip; | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
 | ||||
| 
 | ||||
| import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue'; | ||||
| import { EventEmitter } from 'eventemitter3'; | ||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | ||||
| import * as Sentry from '@sentry/browser'; | ||||
| import Stream from '@/scripts/stream'; | ||||
| import { apiUrl, debug } from '@/config'; | ||||
|  | @ -289,7 +292,7 @@ export async function selectDriveFolder(multiple: boolean) { | |||
| 
 | ||||
| export async function pickEmoji(src?: HTMLElement, opts) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(import('@/components/emoji-picker.vue'), { | ||||
| 		popup(import('@/components/emoji-picker-dialog.vue'), { | ||||
| 			src, | ||||
| 			...opts | ||||
| 		}, { | ||||
|  | @ -300,6 +303,63 @@ export async function pickEmoji(src?: HTMLElement, opts) { | |||
| 	}); | ||||
| } | ||||
| 
 | ||||
| type AwaitType<T> = | ||||
| 	T extends Promise<infer U> ? U : | ||||
| 	T extends (...args: Array<any>) => Promise<infer V> ? V : | ||||
| 	T; | ||||
| let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null; | ||||
| let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null; | ||||
| export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: typeof activeTextarea) { | ||||
| 	if (openingEmojiPicker) return; | ||||
| 
 | ||||
| 	activeTextarea = initialTextarea; | ||||
| 
 | ||||
| 	const textareas = document.querySelectorAll('textarea, input'); | ||||
| 	for (const textarea of Array.from(textareas)) { | ||||
| 		textarea.addEventListener('focus', () => { | ||||
| 			activeTextarea = textarea; | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	const observer = new MutationObserver(records => { | ||||
| 		for (const record of records) { | ||||
| 			for (const node of Array.from(record.addedNodes)) { | ||||
| 				if (node instanceof HTMLElement) { | ||||
| 					const textareas = node.querySelectorAll('textarea, input'); | ||||
| 					for (const textarea of Array.from(textareas)) { | ||||
| 						if (textarea.dataset.preventEmojiInsert != null) return; | ||||
| 						if (document.activeElement === textarea) activeTextarea = textarea; | ||||
| 						textarea.addEventListener('focus', () => { | ||||
| 							activeTextarea = textarea; | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	observer.observe(document.body, { | ||||
| 		childList: true, | ||||
| 		subtree: true, | ||||
| 		attributes: false, | ||||
| 		characterData: false, | ||||
| 	}); | ||||
| 
 | ||||
| 	openingEmojiPicker = await popup(import('@/components/emoji-picker-window.vue'), { | ||||
| 		src, | ||||
| 		...opts | ||||
| 	}, { | ||||
| 		chosen: emoji => { | ||||
| 			insertTextAtCursor(activeTextarea, emoji); | ||||
| 		}, | ||||
| 		closed: () => { | ||||
| 			openingEmojiPicker!.dispose(); | ||||
| 			openingEmojiPicker = null; | ||||
| 			observer.disconnect(); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		let dispose; | ||||
|  |  | |||
|  | @ -223,9 +223,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		async insertEmoji(ev) { | ||||
| 			os.pickEmoji(ev.currentTarget || ev.target).then(emoji => { | ||||
| 				insertTextAtCursor(this.$refs.text, emoji); | ||||
| 			}); | ||||
| 			os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -105,7 +105,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		preview(ev) { | ||||
| 			os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 			os.popup(import('@/components/emoji-picker-dialog.vue'), { | ||||
| 				asReactionPicker: true, | ||||
| 				src: ev.currentTarget || ev.target, | ||||
| 			}, {}, 'closed'); | ||||
|  |  | |||
|  | @ -391,8 +391,8 @@ hr { | |||
| 
 | ||||
| ._acrylic { | ||||
| 	background: var(--acrylicPanel); | ||||
| 	-webkit-backdrop-filter: blur(10px); | ||||
| 	backdrop-filter: blur(10px); | ||||
| 	-webkit-backdrop-filter: blur(15px); | ||||
| 	backdrop-filter: blur(15px); | ||||
| } | ||||
| 
 | ||||
| ._vMargin { | ||||
|  |  | |||
|  | @ -504,7 +504,7 @@ export default defineComponent({ | |||
| 			pleaseLogin(); | ||||
| 			this.operating = true; | ||||
| 			this.blur(); | ||||
| 			const { dispose } = await os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 			const { dispose } = await os.popup(import('@/components/emoji-picker-dialog.vue'), { | ||||
| 				src: this.$refs.reactButton, | ||||
| 				asReactionPicker: true | ||||
| 			}, { | ||||
|  |  | |||
|  | @ -593,9 +593,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		async insertEmoji(ev) { | ||||
| 			os.pickEmoji(ev.currentTarget || ev.target).then(emoji => { | ||||
| 				insertTextAtCursor(this.$refs.text, emoji); | ||||
| 			}); | ||||
| 			os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); | ||||
| 		}, | ||||
| 
 | ||||
| 		showActions(ev) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue