chore(client): rendering performance tweak a bit
This commit is contained in:
		
							parent
							
								
									f882e0b6b6
								
							
						
					
					
						commit
						bc73ad2e56
					
				|  | @ -1,15 +1,17 @@ | |||
| <template> | ||||
| <!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと --> | ||||
| <section> | ||||
| 	<header class="_acrylic" @click="shown = !shown"> | ||||
| 		<i class="toggle fa-fw" :class="shown ? 'fas fa-chevron-down' : 'fas fa-chevron-up'"></i> <slot></slot> ({{ emojis.length }}) | ||||
| 	</header> | ||||
| 	<div v-if="shown"> | ||||
| 		<button v-for="emoji in emojis" | ||||
| 	<div v-if="shown" class="body"> | ||||
| 		<button | ||||
| 			v-for="emoji in emojis" | ||||
| 			:key="emoji" | ||||
| 			class="_button" | ||||
| 			class="_button item" | ||||
| 			@click="emit('chosen', emoji, $event)" | ||||
| 		> | ||||
| 			<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 			<MkEmoji class="emoji" :emoji="emoji" :normal="true"/> | ||||
| 		</button> | ||||
| 	</div> | ||||
| </section> | ||||
|  |  | |||
|  | @ -3,63 +3,67 @@ | |||
| 	<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()"> | ||||
| 	<div ref="emojis" class="emojis"> | ||||
| 		<section class="result"> | ||||
| 			<div v-if="searchResultCustom.length > 0"> | ||||
| 				<button v-for="emoji in searchResultCustom" | ||||
| 			<div v-if="searchResultCustom.length > 0" class="body"> | ||||
| 				<button | ||||
| 					v-for="emoji in searchResultCustom" | ||||
| 					:key="emoji.id" | ||||
| 					class="_button" | ||||
| 					class="_button item" | ||||
| 					:title="emoji.name" | ||||
| 					tabindex="0" | ||||
| 					@click="chosen(emoji, $event)" | ||||
| 				> | ||||
| 					<!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> | ||||
| 					<img :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					<img class="emoji" :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 			<div v-if="searchResultUnicode.length > 0"> | ||||
| 				<button v-for="emoji in searchResultUnicode" | ||||
| 			<div v-if="searchResultUnicode.length > 0" class="body"> | ||||
| 				<button | ||||
| 					v-for="emoji in searchResultUnicode" | ||||
| 					:key="emoji.name" | ||||
| 					class="_button" | ||||
| 					class="_button item" | ||||
| 					:title="emoji.name" | ||||
| 					tabindex="0" | ||||
| 					@click="chosen(emoji, $event)" | ||||
| 				> | ||||
| 					<MkEmoji :emoji="emoji.char"/> | ||||
| 					<MkEmoji class="emoji" :emoji="emoji.char"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</section> | ||||
| 
 | ||||
| 		<div v-if="tab === 'index'" class="index"> | ||||
| 		<div v-if="tab === 'index'" class="group index"> | ||||
| 			<section v-if="showPinned"> | ||||
| 				<div> | ||||
| 					<button v-for="emoji in pinned" | ||||
| 				<div class="body"> | ||||
| 					<button | ||||
| 						v-for="emoji in pinned" | ||||
| 						:key="emoji" | ||||
| 						class="_button" | ||||
| 						class="_button item" | ||||
| 						tabindex="0" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						<MkEmoji class="emoji" :emoji="emoji" :normal="true"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 
 | ||||
| 			<section> | ||||
| 				<header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header> | ||||
| 				<div> | ||||
| 					<button v-for="emoji in recentlyUsedEmojis" | ||||
| 				<div class="body"> | ||||
| 					<button | ||||
| 						v-for="emoji in recentlyUsedEmojis" | ||||
| 						:key="emoji" | ||||
| 						class="_button" | ||||
| 						class="_button item" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						<MkEmoji class="emoji" :emoji="emoji" :normal="true"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 		<div class="group"> | ||||
| 			<header class="_acrylic">{{ i18n.ts.customEmojis }}</header> | ||||
| 			<XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 		<div class="group"> | ||||
| 			<header class="_acrylic">{{ i18n.ts.emoji }}</header> | ||||
| 			<XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> | ||||
| 		</div> | ||||
|  | @ -76,6 +80,7 @@ | |||
| <script lang="ts" setup> | ||||
| import { ref, computed, watch, onMounted } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import XSection from './emoji-picker.section.vue'; | ||||
| import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; | ||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import Ripple from '@/components/ripple.vue'; | ||||
|  | @ -83,7 +88,6 @@ import * as os from '@/os'; | |||
| import { isTouchUsing } from '@/scripts/touch'; | ||||
| import { deviceKind } from '@/scripts/device-kind'; | ||||
| import { emojiCategories, instance } from '@/instance'; | ||||
| import XSection from './emoji-picker.section.vue'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { defaultStore } from '@/store'; | ||||
| 
 | ||||
|  | @ -266,7 +270,7 @@ watch(q, () => { | |||
| function focus() { | ||||
| 	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { | ||||
| 		search.value?.focus({ | ||||
| 			preventScroll: true | ||||
| 			preventScroll: true, | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | @ -415,19 +419,16 @@ defineExpose({ | |||
| 					font-size: 15px; | ||||
| 				} | ||||
| 
 | ||||
| 				> div { | ||||
| 				> .body { | ||||
| 					display: grid; | ||||
| 					grid-template-columns: var(--columns); | ||||
| 					font-size: 30px; | ||||
| 
 | ||||
| 					> button { | ||||
| 					> .item { | ||||
| 						aspect-ratio: 1 / 1; | ||||
| 						width: auto; | ||||
| 						height: auto; | ||||
| 						min-width: 0; | ||||
| 
 | ||||
| 						> * { | ||||
| 							font-size: 30px; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | @ -478,7 +479,7 @@ defineExpose({ | |||
| 			display: none; | ||||
| 		} | ||||
| 
 | ||||
| 		> div { | ||||
| 		> .group { | ||||
| 			&:not(.index) { | ||||
| 				padding: 4px 0 8px 0; | ||||
| 				border-top: solid 0.5px var(--divider); | ||||
|  | @ -513,16 +514,18 @@ defineExpose({ | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			> div { | ||||
| 			> .body { | ||||
| 				position: relative; | ||||
| 				padding: $pad; | ||||
| 
 | ||||
| 				> button { | ||||
| 				> .item { | ||||
| 					position: relative; | ||||
| 					padding: 0; | ||||
| 					width: var(--eachSize); | ||||
| 					height: var(--eachSize); | ||||
| 					contain: strict; | ||||
| 					border-radius: 4px; | ||||
| 					font-size: 24px; | ||||
| 
 | ||||
| 					&:focus-visible { | ||||
| 						outline: solid 2px var(--focus); | ||||
|  | @ -538,8 +541,7 @@ defineExpose({ | |||
| 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); | ||||
| 					} | ||||
| 
 | ||||
| 					> * { | ||||
| 						font-size: 24px; | ||||
| 					> .emoji { | ||||
| 						height: 1.25em; | ||||
| 						vertical-align: -.25em; | ||||
| 						pointer-events: none; | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| <div v-if="hide" class="qjewsnkg" @click="hide = false"> | ||||
| 	<ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/> | ||||
| 	<div class="text"> | ||||
| 		<div> | ||||
| 			<b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b> | ||||
| 			<span>{{ $ts.clickToShow }}</span> | ||||
| 		<div class="wrapper"> | ||||
| 			<b style="display: block;"><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b> | ||||
| 			<span style="display: block;">{{ $ts.clickToShow }}</span> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -37,8 +37,8 @@ let hide = $ref(true); | |||
| const url = (props.raw || defaultStore.state.loadRawImages) | ||||
| 	? props.image.url | ||||
| 	: defaultStore.state.disableShowingAnimatedImages | ||||
| 			? getStaticImageUrl(props.image.thumbnailUrl) | ||||
| 			: props.image.thumbnailUrl; | ||||
| 		? getStaticImageUrl(props.image.thumbnailUrl) | ||||
| 		: props.image.thumbnailUrl; | ||||
| 
 | ||||
| // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする | ||||
| watch(() => props.image, () => { | ||||
|  | @ -68,15 +68,11 @@ watch(() => props.image, () => { | |||
| 		justify-content: center; | ||||
| 		align-items: center; | ||||
| 
 | ||||
| 		> div { | ||||
| 		> .wrapper { | ||||
| 			display: table-cell; | ||||
| 			text-align: center; | ||||
| 			font-size: 0.8em; | ||||
| 			color: #fff; | ||||
| 
 | ||||
| 			> * { | ||||
| 				display: block; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <div v-show="files.length != 0" class="skeikyzd"> | ||||
| 	<XDraggable v-model="_files" class="files" item-key="id" animation="150" delay="100" delay-on-touch-only="true"> | ||||
| 		<template #item="{element}"> | ||||
| 			<div @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> | ||||
| 			<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> | ||||
| 				<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> | ||||
| 				<div v-if="element.isSensitive" class="sensitive"> | ||||
| 					<i class="fas fa-exclamation-triangle icon"></i> | ||||
|  | @ -22,18 +22,18 @@ import * as os from '@/os'; | |||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), | ||||
| 		MkDriveFileThumbnail | ||||
| 		MkDriveFileThumbnail, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		files: { | ||||
| 			type: Array, | ||||
| 			required: true | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		detachMediaFn: { | ||||
| 			type: Function, | ||||
| 			required: false | ||||
| 		} | ||||
| 			required: false, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['updated', 'detach', 'changeSensitive', 'changeName'], | ||||
|  | @ -51,8 +51,8 @@ export default defineComponent({ | |||
| 			}, | ||||
| 			set(value) { | ||||
| 				this.$emit('updated', value); | ||||
| 			} | ||||
| 		} | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
|  | @ -66,7 +66,7 @@ export default defineComponent({ | |||
| 		toggleSensitive(file) { | ||||
| 			os.api('drive/files/update', { | ||||
| 				fileId: file.id, | ||||
| 				isSensitive: !file.isSensitive | ||||
| 				isSensitive: !file.isSensitive, | ||||
| 			}).then(() => { | ||||
| 				this.$emit('changeSensitive', file, !file.isSensitive); | ||||
| 			}); | ||||
|  | @ -75,12 +75,12 @@ export default defineComponent({ | |||
| 			const { canceled, result } = await os.inputText({ | ||||
| 				title: this.$ts.enterFileName, | ||||
| 				default: file.name, | ||||
| 				allowEmpty: false | ||||
| 				allowEmpty: false, | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 			os.api('drive/files/update', { | ||||
| 				fileId: file.id, | ||||
| 				name: result | ||||
| 				name: result, | ||||
| 			}).then(() => { | ||||
| 				this.$emit('changeName', file, result); | ||||
| 				file.name = result; | ||||
|  | @ -88,13 +88,13 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		async describe(file) { | ||||
| 			os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), { | ||||
| 			os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), { | ||||
| 				title: this.$ts.describeFile, | ||||
| 				input: { | ||||
| 					placeholder: this.$ts.inputNewDescription, | ||||
| 					default: file.comment !== null ? file.comment : "", | ||||
| 					default: file.comment !== null ? file.comment : '', | ||||
| 				}, | ||||
| 				image: file | ||||
| 				image: file, | ||||
| 			}, { | ||||
| 				done: result => { | ||||
| 					if (!result || result.canceled) return; | ||||
|  | @ -105,7 +105,7 @@ export default defineComponent({ | |||
| 					}).then(() => { | ||||
| 						file.comment = comment; | ||||
| 					}); | ||||
| 				} | ||||
| 				}, | ||||
| 			}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
|  | @ -114,22 +114,22 @@ export default defineComponent({ | |||
| 			this.menu = os.popupMenu([{ | ||||
| 				text: this.$ts.renameFile, | ||||
| 				icon: 'fas fa-i-cursor', | ||||
| 				action: () => { this.rename(file); } | ||||
| 				action: () => { this.rename(file); }, | ||||
| 			}, { | ||||
| 				text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, | ||||
| 				icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye', | ||||
| 				action: () => { this.toggleSensitive(file); } | ||||
| 				action: () => { this.toggleSensitive(file); }, | ||||
| 			}, { | ||||
| 				text: this.$ts.describeFile, | ||||
| 				icon: 'fas fa-i-cursor', | ||||
| 				action: () => { this.describe(file); } | ||||
| 				action: () => { this.describe(file); }, | ||||
| 			}, { | ||||
| 				text: this.$ts.attachCancel, | ||||
| 				icon: 'fas fa-times-circle', | ||||
| 				action: () => { this.detachMedia(file.id); } | ||||
| 				action: () => { this.detachMedia(file.id); }, | ||||
| 			}], ev.currentTarget ?? ev.target).then(() => this.menu = null); | ||||
| 		} | ||||
| 	} | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  | @ -142,7 +142,7 @@ export default defineComponent({ | |||
| 		display: flex; | ||||
| 		flex-wrap: wrap; | ||||
| 
 | ||||
| 		> div { | ||||
| 		> .file { | ||||
| 			position: relative; | ||||
| 			width: 64px; | ||||
| 			height: 64px; | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| <template> | ||||
| <div v-size="{ max: [310, 500] }" class="gafaadew" | ||||
| <div | ||||
| 	v-size="{ max: [310, 500] }" class="gafaadew" | ||||
| 	:class="{ modal, _popup: modal }" | ||||
| 	@dragover.stop="onDragover" | ||||
| 	@dragenter="onDragenter" | ||||
|  | @ -11,7 +12,7 @@ | |||
| 		<button v-click-anime v-tooltip="i18n.ts.switchAccount" class="account _button" @click="openAccountMenu"> | ||||
| 			<MkAvatar :user="postAccount ?? $i" class="avatar"/> | ||||
| 		</button> | ||||
| 		<div> | ||||
| 		<div class="right"> | ||||
| 			<span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> | ||||
| 			<span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> | ||||
| 			<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> | ||||
|  | @ -68,6 +69,8 @@ import * as misskey from 'misskey-js'; | |||
| import insertTextAtCursor from 'insert-text-at-cursor'; | ||||
| import { length } from 'stringz'; | ||||
| import { toASCII } from 'punycode/'; | ||||
| import * as Acct from 'misskey-js/built/acct'; | ||||
| import { throttle } from 'throttle-debounce'; | ||||
| import XNoteSimple from './note-simple.vue'; | ||||
| import XNotePreview from './note-preview.vue'; | ||||
| import XPostFormAttaches from './post-form-attaches.vue'; | ||||
|  | @ -75,14 +78,12 @@ import XPollEditor from './poll-editor.vue'; | |||
| import { host, url } from '@/config'; | ||||
| import { erase, unique } from '@/scripts/array'; | ||||
| import { extractMentions } from '@/scripts/extract-mentions'; | ||||
| import * as Acct from 'misskey-js/built/acct'; | ||||
| import { formatTimeString } from '@/scripts/format-time-string'; | ||||
| import { Autocomplete } from '@/scripts/autocomplete'; | ||||
| import * as os from '@/os'; | ||||
| import { stream } from '@/stream'; | ||||
| import { selectFiles } from '@/scripts/select-file'; | ||||
| import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; | ||||
| import { throttle } from 'throttle-debounce'; | ||||
| import MkInfo from '@/components/ui/info.vue'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { instance } from '@/instance'; | ||||
|  | @ -181,7 +182,7 @@ const placeholder = $computed((): string => { | |||
| 			i18n.ts._postForm._placeholders.c, | ||||
| 			i18n.ts._postForm._placeholders.d, | ||||
| 			i18n.ts._postForm._placeholders.e, | ||||
| 			i18n.ts._postForm._placeholders.f | ||||
| 			i18n.ts._postForm._placeholders.f, | ||||
| 		]; | ||||
| 		return xs[Math.floor(Math.random() * xs.length)]; | ||||
| 	} | ||||
|  | @ -238,10 +239,10 @@ if (props.reply && props.reply.text != null) { | |||
| 
 | ||||
| 	for (const x of extractMentions(ast)) { | ||||
| 		const mention = x.host ? | ||||
| 											`@${x.username}@${toASCII(x.host)}` : | ||||
| 											(otherHost == null || otherHost === host) ? | ||||
| 												`@${x.username}` : | ||||
| 												`@${x.username}@${toASCII(otherHost)}`; | ||||
| 			`@${x.username}@${toASCII(x.host)}` : | ||||
| 			(otherHost == null || otherHost === host) ? | ||||
| 				`@${x.username}` : | ||||
| 				`@${x.username}@${toASCII(otherHost)}`; | ||||
| 
 | ||||
| 		// 自分は除外 | ||||
| 		if ($i.username === x.username && (x.host == null || x.host === host)) continue; | ||||
|  | @ -263,7 +264,7 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib | |||
| 	visibility = props.reply.visibility; | ||||
| 	if (props.reply.visibility === 'specified') { | ||||
| 		os.api('users/show', { | ||||
| 			userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId) | ||||
| 			userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId), | ||||
| 		}).then(users => { | ||||
| 			users.forEach(pushVisibleUser); | ||||
| 		}); | ||||
|  | @ -399,7 +400,7 @@ function setVisibility() { | |||
| 			if (defaultStore.state.rememberNoteVisibility) { | ||||
| 				defaultStore.set('localOnly', localOnly); | ||||
| 			} | ||||
| 		} | ||||
| 		}, | ||||
| 	}, 'closed'); | ||||
| } | ||||
| 
 | ||||
|  | @ -522,8 +523,8 @@ function saveDraft() { | |||
| 			visibility: visibility, | ||||
| 			localOnly: localOnly, | ||||
| 			files: files, | ||||
| 			poll: poll | ||||
| 		} | ||||
| 			poll: poll, | ||||
| 		}, | ||||
| 	}; | ||||
| 
 | ||||
| 	localStorage.setItem('drafts', JSON.stringify(draftData)); | ||||
|  | @ -612,11 +613,11 @@ function showActions(ev) { | |||
| 		text: action.title, | ||||
| 		action: () => { | ||||
| 			action.handler({ | ||||
| 				text: text | ||||
| 				text: text, | ||||
| 			}, (key, value) => { | ||||
| 				if (key === 'text') { text = value; } | ||||
| 			}); | ||||
| 		} | ||||
| 		}, | ||||
| 	})), ev.currentTarget ?? ev.target); | ||||
| } | ||||
| 
 | ||||
|  | @ -726,7 +727,7 @@ onMounted(() => { | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> div { | ||||
| 		> .right { | ||||
| 			position: absolute; | ||||
| 			top: 0; | ||||
| 			right: 0; | ||||
|  | @ -924,7 +925,7 @@ onMounted(() => { | |||
| 				line-height: 50px; | ||||
| 			} | ||||
| 
 | ||||
| 			> div { | ||||
| 			> .right { | ||||
| 				> .text-count { | ||||
| 					line-height: 50px; | ||||
| 				} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue