refactor: fix some type errors
This commit is contained in:
		| @@ -17,22 +17,14 @@ import MkButton from '@/components/MkButton.vue'; | |||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	modelValue: boolean; | 	modelValue: boolean; | ||||||
| 	text: string | null; | 	text: string | null; | ||||||
| 	renote: Misskey.entities.Note | null; | 	renote?: Misskey.entities.Note | null; | ||||||
| 	files: Misskey.entities.DriveFile[]; | 	files?: Misskey.entities.DriveFile[]; | ||||||
| 	poll?: { | 	poll?: Misskey.entities.Note['poll'] | { | ||||||
| 		expiresAt: string | null; |  | ||||||
| 		multiple: boolean; |  | ||||||
| 		choices: { |  | ||||||
| 			isVoted: boolean; |  | ||||||
| 			text: string; |  | ||||||
| 			votes: number; |  | ||||||
| 		}[]; |  | ||||||
| 	} | { |  | ||||||
| 		choices: string[]; | 		choices: string[]; | ||||||
| 		multiple: boolean; | 		multiple: boolean; | ||||||
| 		expiresAt: string | null; | 		expiresAt: string | null; | ||||||
| 		expiredAfter: string | null; | 		expiredAfter: string | null; | ||||||
| 	}; | 	} | null; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| @@ -43,7 +35,7 @@ const label = computed(() => { | |||||||
| 	return concat([ | 	return concat([ | ||||||
| 		props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [], | 		props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [], | ||||||
| 		props.renote ? [i18n.ts.quote] : [], | 		props.renote ? [i18n.ts.quote] : [], | ||||||
| 		props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [], | 		props.files && props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [], | ||||||
| 		props.poll != null ? [i18n.ts.poll] : [], | 		props.poll != null ? [i18n.ts.poll] : [], | ||||||
| 	] as string[][]).join(' / '); | 	] as string[][]).join(' / '); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -18,9 +18,9 @@ import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; | |||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	instance?: { | 	instance?: { | ||||||
| 		faviconUrl?: string | 		faviconUrl?: string | null | ||||||
| 		name: string | 		name?: string | null | ||||||
| 		themeColor?: string | 		themeColor?: string | null | ||||||
| 	} | 	} | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ const count = computed(() => props.mediaList.filter(media => previewable(media)) | |||||||
| let lightbox: PhotoSwipeLightbox | null; | let lightbox: PhotoSwipeLightbox | null; | ||||||
|  |  | ||||||
| const popstateHandler = (): void => { | const popstateHandler = (): void => { | ||||||
| 	if (lightbox.pswp && lightbox.pswp.isOpen === true) { | 	if (lightbox?.pswp && lightbox.pswp.isOpen === true) { | ||||||
| 		lightbox.pswp.close(); | 		lightbox.pswp.close(); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| @@ -67,7 +67,10 @@ async function calcAspectRatio() { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`; | 	const ratioMax = (ratio: number) => { | ||||||
|  | 		if (!img.properties.width || !img.properties.height) return ''; | ||||||
|  | 		return `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	switch (defaultStore.state.mediaListWithOneImageAppearance) { | 	switch (defaultStore.state.mediaListWithOneImageAppearance) { | ||||||
| 		case '16_9': | 		case '16_9': | ||||||
| @@ -137,7 +140,7 @@ onMounted(() => { | |||||||
| 		// element is children | 		// element is children | ||||||
| 		const { element } = itemData; | 		const { element } = itemData; | ||||||
|  |  | ||||||
| 		const id = element.dataset.id; | 		const id = element?.dataset.id; | ||||||
| 		const file = props.mediaList.find(media => media.id === id); | 		const file = props.mediaList.find(media => media.id === id); | ||||||
| 		if (!file) return; | 		if (!file) return; | ||||||
|  |  | ||||||
| @@ -147,14 +150,14 @@ onMounted(() => { | |||||||
| 		if (file.properties.orientation != null && file.properties.orientation >= 5) { | 		if (file.properties.orientation != null && file.properties.orientation >= 5) { | ||||||
| 			[itemData.w, itemData.h] = [itemData.h, itemData.w]; | 			[itemData.w, itemData.h] = [itemData.h, itemData.w]; | ||||||
| 		} | 		} | ||||||
| 		itemData.msrc = file.thumbnailUrl; | 		itemData.msrc = file.thumbnailUrl ?? undefined; | ||||||
| 		itemData.alt = file.comment ?? file.name; | 		itemData.alt = file.comment ?? file.name; | ||||||
| 		itemData.comment = file.comment ?? file.name; | 		itemData.comment = file.comment ?? file.name; | ||||||
| 		itemData.thumbCropped = true; | 		itemData.thumbCropped = true; | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	lightbox.on('uiRegister', () => { | 	lightbox.on('uiRegister', () => { | ||||||
| 		lightbox.pswp.ui.registerElement({ | 		lightbox?.pswp?.ui?.registerElement({ | ||||||
| 			name: 'altText', | 			name: 'altText', | ||||||
| 			className: 'pwsp__alt-text-container', | 			className: 'pwsp__alt-text-container', | ||||||
| 			appendTo: 'wrapper', | 			appendTo: 'wrapper', | ||||||
| @@ -163,8 +166,8 @@ onMounted(() => { | |||||||
| 				textBox.className = 'pwsp__alt-text _acrylic'; | 				textBox.className = 'pwsp__alt-text _acrylic'; | ||||||
| 				el.appendChild(textBox); | 				el.appendChild(textBox); | ||||||
|  |  | ||||||
| 				pwsp.on('change', (a) => { | 				pwsp.on('change', () => { | ||||||
| 					textBox.textContent = pwsp.currSlide.data.comment; | 					textBox.textContent = pwsp.currSlide?.data.comment; | ||||||
| 				}); | 				}); | ||||||
| 			}, | 			}, | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
| @@ -16,8 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<video | 	<video | ||||||
| 		ref="videoEl" | 		ref="videoEl" | ||||||
| 		:class="$style.video" | 		:class="$style.video" | ||||||
| 		:poster="video.thumbnailUrl" | 		:poster="video.thumbnailUrl ?? undefined" | ||||||
| 		:title="video.comment" | 		:title="video.comment ?? undefined" | ||||||
| 		:alt="video.comment" | 		:alt="video.comment" | ||||||
| 		preload="none" | 		preload="none" | ||||||
| 		controls | 		controls | ||||||
| @@ -51,7 +51,7 @@ watch(videoEl, () => { | |||||||
| 	if (videoEl.value) { | 	if (videoEl.value) { | ||||||
| 		videoEl.value.volume = 0.3; | 		videoEl.value.volume = 0.3; | ||||||
| 		hasAudio(videoEl.value).then(had => { | 		hasAudio(videoEl.value).then(had => { | ||||||
| 			if (!had) { | 			if (!had && videoEl.value) { | ||||||
| 				videoEl.value.loop = videoEl.value.muted = true; | 				videoEl.value.loop = videoEl.value.muted = true; | ||||||
| 				videoEl.value.play(); | 				videoEl.value.play(); | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -33,6 +33,7 @@ const align = 'left'; | |||||||
| const SCROLLBAR_THICKNESS = 16; | const SCROLLBAR_THICKNESS = 16; | ||||||
|  |  | ||||||
| function setPosition() { | function setPosition() { | ||||||
|  | 	if (!el.value) return; | ||||||
| 	const rootRect = props.rootElement.getBoundingClientRect(); | 	const rootRect = props.rootElement.getBoundingClientRect(); | ||||||
| 	const parentRect = props.targetElement.getBoundingClientRect(); | 	const parentRect = props.targetElement.getBoundingClientRect(); | ||||||
| 	const myRect = el.value.getBoundingClientRect(); | 	const myRect = el.value.getBoundingClientRect(); | ||||||
| @@ -66,7 +67,7 @@ const ro = new ResizeObserver((entries, observer) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	ro.observe(el.value); | 	if (el.value) ro.observe(el.value); | ||||||
| 	setPosition(); | 	setPosition(); | ||||||
| 	nextTick(() => { | 	nextTick(() => { | ||||||
| 		setPosition(); | 		setPosition(); | ||||||
| @@ -79,7 +80,7 @@ onUnmounted(() => { | |||||||
|  |  | ||||||
| defineExpose({ | defineExpose({ | ||||||
| 	checkHit: (ev: MouseEvent) => { | 	checkHit: (ev: MouseEvent) => { | ||||||
| 		return (ev.target === el.value || el.value.contains(ev.target)); | 		return (ev.target === el.value || el.value?.contains(ev.target as Node)); | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" | 		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" | ||||||
| 		@contextmenu.self="e => e.preventDefault()" | 		@contextmenu.self="e => e.preventDefault()" | ||||||
| 	> | 	> | ||||||
| 		<template v-for="(item, i) in items2"> | 		<template v-for="(item, i) in (items2 ?? [])"> | ||||||
| 			<div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div> | 			<div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div> | ||||||
| 			<span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]"> | 			<span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]"> | ||||||
| 				<span style="opacity: 0.7;">{{ item.text }}</span> | 				<span style="opacity: 0.7;">{{ item.text }}</span> | ||||||
| @@ -63,12 +63,12 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				</div> | 				</div> | ||||||
| 			</button> | 			</button> | ||||||
| 		</template> | 		</template> | ||||||
| 		<span v-if="items2.length === 0" :class="[$style.none, $style.item]"> | 		<span v-if="!items2 || items2.length === 0" :class="[$style.none, $style.item]"> | ||||||
| 			<span>{{ i18n.ts.none }}</span> | 			<span>{{ i18n.ts.none }}</span> | ||||||
| 		</span> | 		</span> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div v-if="childMenu"> | 	<div v-if="childMenu"> | ||||||
| 		<XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned" @close="close(false)"/> | 		<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" showing @actioned="childActioned" @close="close(false)"/> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -104,7 +104,7 @@ const emit = defineEmits<{ | |||||||
|  |  | ||||||
| const itemsEl = shallowRef<HTMLDivElement>(); | const itemsEl = shallowRef<HTMLDivElement>(); | ||||||
|  |  | ||||||
| const items2 = ref<InnerMenuItem[]>([]); | const items2 = ref<InnerMenuItem[]>(); | ||||||
|  |  | ||||||
| const child = shallowRef<InstanceType<typeof XChild>>(); | const child = shallowRef<InstanceType<typeof XChild>>(); | ||||||
|  |  | ||||||
| @@ -119,15 +119,15 @@ const childShowingItem = ref<MenuItem | null>(); | |||||||
| let preferClick = isTouchUsing || props.asDrawer; | let preferClick = isTouchUsing || props.asDrawer; | ||||||
|  |  | ||||||
| watch(() => props.items, () => { | watch(() => props.items, () => { | ||||||
| 	const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined); | 	const items = [...props.items].filter(item => item !== undefined) as (NonNullable<MenuItem> | MenuPending)[]; | ||||||
|  |  | ||||||
| 	for (let i = 0; i < items.length; i++) { | 	for (let i = 0; i < items.length; i++) { | ||||||
| 		const item = items[i]; | 		const item = items[i]; | ||||||
|  |  | ||||||
| 		if (item && 'then' in item) { // if item is Promise | 		if ('then' in item) { // if item is Promise | ||||||
| 			items[i] = { type: 'pending' }; | 			items[i] = { type: 'pending' }; | ||||||
| 			item.then(actualItem => { | 			item.then(actualItem => { | ||||||
| 				items2.value[i] = actualItem; | 				if (items2.value?.[i]) items2.value[i] = actualItem; | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -151,7 +151,7 @@ function childActioned() { | |||||||
| } | } | ||||||
|  |  | ||||||
| const onGlobalMousedown = (event: MouseEvent) => { | const onGlobalMousedown = (event: MouseEvent) => { | ||||||
| 	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return; | 	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target as Node))) return; | ||||||
| 	if (child.value && child.value.checkHit(event)) return; | 	if (child.value && child.value.checkHit(event)) return; | ||||||
| 	closeChild(); | 	closeChild(); | ||||||
| }; | }; | ||||||
| @@ -169,7 +169,7 @@ function onItemMouseLeave(item) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function showChildren(item: MenuParent, ev: MouseEvent) { | async function showChildren(item: MenuParent, ev: MouseEvent) { | ||||||
| 	const children = await (async () => { | 	const children: MenuItem[] = await (async () => { | ||||||
| 		if (childrenCache.has(item)) { | 		if (childrenCache.has(item)) { | ||||||
| 			return childrenCache.get(item)!; | 			return childrenCache.get(item)!; | ||||||
| 		} else { | 		} else { | ||||||
| @@ -189,7 +189,7 @@ async function showChildren(item: MenuParent, ev: MouseEvent) { | |||||||
| 		}); | 		}); | ||||||
| 		emit('hide'); | 		emit('hide'); | ||||||
| 	} else { | 	} else { | ||||||
| 		childTarget.value = ev.currentTarget ?? ev.target; | 		childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement; | ||||||
| 		// これでもリアクティビティは保たれる | 		// これでもリアクティビティは保たれる | ||||||
| 		childMenu.value = children; | 		childMenu.value = children; | ||||||
| 		childShowingItem.value = item; | 		childShowingItem.value = item; | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		stroke-width="2" | 		stroke-width="2" | ||||||
| 	/> | 	/> | ||||||
| 	<circle | 	<circle | ||||||
| 		:cx="headX" | 		:cx="headX ?? undefined" | ||||||
| 		:cy="headY" | 		:cy="headY ?? undefined" | ||||||
| 		r="3" | 		r="3" | ||||||
| 		:fill="color" | 		:fill="color" | ||||||
| 	/> | 	/> | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ const bodyWidth = ref(0); | |||||||
| const bodyHeight = ref(0); | const bodyHeight = ref(0); | ||||||
|  |  | ||||||
| const close = () => { | const close = () => { | ||||||
| 	modal.value.close(); | 	modal.value?.close(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const onBgClick = () => { | const onBgClick = () => { | ||||||
| @@ -67,11 +67,13 @@ const onKeydown = (evt) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const ro = new ResizeObserver((entries, observer) => { | const ro = new ResizeObserver((entries, observer) => { | ||||||
|  | 	if (!rootEl.value || !headerEl.value) return; | ||||||
| 	bodyWidth.value = rootEl.value.offsetWidth; | 	bodyWidth.value = rootEl.value.offsetWidth; | ||||||
| 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | ||||||
| }); | }); | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|  | 	if (!rootEl.value || !headerEl.value) return; | ||||||
| 	bodyWidth.value = rootEl.value.offsetWidth; | 	bodyWidth.value = rootEl.value.offsetWidth; | ||||||
| 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | ||||||
| 	ro.observe(rootEl.value); | 	ro.observe(rootEl.value); | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <div | <div | ||||||
| 	v-if="!hardMuted && !muted" | 	v-if="!hardMuted && !muted" | ||||||
| 	v-show="!isDeleted" | 	v-show="!isDeleted" | ||||||
| 	ref="el" | 	ref="rootEl" | ||||||
| 	v-hotkey="keymap" | 	v-hotkey="keymap" | ||||||
| 	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" | 	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" | ||||||
| 	:tabindex="!isDeleted ? '-1' : undefined" | 	:tabindex="!isDeleted ? '-1' : undefined" | ||||||
| @@ -72,16 +72,16 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 						/> | 						/> | ||||||
| 						<div v-if="translating || translation" :class="$style.translation"> | 						<div v-if="translating || translation" :class="$style.translation"> | ||||||
| 							<MkLoading v-if="translating" mini/> | 							<MkLoading v-if="translating" mini/> | ||||||
| 							<div v-else> | 							<div v-else-if="translation"> | ||||||
| 								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> | 								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> | ||||||
| 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> | 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div v-if="appearNote.files.length > 0"> | 					<div v-if="appearNote.files && appearNote.files.length > 0"> | ||||||
| 						<MkMediaList :mediaList="appearNote.files"/> | 						<MkMediaList :mediaList="appearNote.files"/> | ||||||
| 					</div> | 					</div> | ||||||
| 					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/> | 					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/> | ||||||
| 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> | 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> | ||||||
| 					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> | 					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> | ||||||
| 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> | 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> | ||||||
| @@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> | 				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> | ||||||
| 					<i class="ti ti-paperclip"></i> | 					<i class="ti ti-paperclip"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()"> | 				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()"> | ||||||
| 					<i class="ti ti-dots"></i> | 					<i class="ti ti-dots"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 			</footer> | 			</footer> | ||||||
| @@ -214,7 +214,7 @@ if (noteViewInterruptors.length > 0) { | |||||||
| 		let result: Misskey.entities.Note | null = deepClone(note.value); | 		let result: Misskey.entities.Note | null = deepClone(note.value); | ||||||
| 		for (const interruptor of noteViewInterruptors) { | 		for (const interruptor of noteViewInterruptors) { | ||||||
| 			try { | 			try { | ||||||
| 				result = await interruptor.handler(result); | 				result = await interruptor.handler(result!) as Misskey.entities.Note | null; | ||||||
| 				if (result === null) { | 				if (result === null) { | ||||||
| 					isDeleted.value = true; | 					isDeleted.value = true; | ||||||
| 					return; | 					return; | ||||||
| @@ -223,7 +223,7 @@ if (noteViewInterruptors.length > 0) { | |||||||
| 				console.error(err); | 				console.error(err); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		note.value = result; | 		note.value = result as Misskey.entities.Note; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -231,11 +231,11 @@ const isRenote = ( | |||||||
| 	note.value.renote != null && | 	note.value.renote != null && | ||||||
| 	note.value.text == null && | 	note.value.text == null && | ||||||
| 	note.value.cw == null && | 	note.value.cw == null && | ||||||
| 	note.value.fileIds.length === 0 && | 	note.value.fileIds && note.value.fileIds.length === 0 && | ||||||
| 	note.value.poll == null | 	note.value.poll == null | ||||||
| ); | ); | ||||||
|  |  | ||||||
| const el = shallowRef<HTMLElement>(); | const rootEl = shallowRef<HTMLElement>(); | ||||||
| const menuButton = shallowRef<HTMLElement>(); | const menuButton = shallowRef<HTMLElement>(); | ||||||
| const renoteButton = shallowRef<HTMLElement>(); | const renoteButton = shallowRef<HTMLElement>(); | ||||||
| const renoteTime = shallowRef<HTMLElement>(); | const renoteTime = shallowRef<HTMLElement>(); | ||||||
| @@ -254,8 +254,8 @@ const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hard | |||||||
| const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); | const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); | ||||||
| const translating = ref(false); | const translating = ref(false); | ||||||
| const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); | const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); | ||||||
| const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id)); | const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id)); | ||||||
| const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null))); | const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) ?? (appearNote.value.myReaction != null))); | ||||||
|  |  | ||||||
| function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean { | function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean { | ||||||
| 	if (mutedWords == null) return false; | 	if (mutedWords == null) return false; | ||||||
| @@ -269,11 +269,11 @@ function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | strin | |||||||
| const keymap = { | const keymap = { | ||||||
| 	'r': () => reply(true), | 	'r': () => reply(true), | ||||||
| 	'e|a|plus': () => react(true), | 	'e|a|plus': () => react(true), | ||||||
| 	'q': () => renoteButton.value.renote(true), | 	'q': () => renote(true), | ||||||
| 	'up|k|shift+tab': focusBefore, | 	'up|k|shift+tab': focusBefore, | ||||||
| 	'down|j|tab': focusAfter, | 	'down|j|tab': focusAfter, | ||||||
| 	'esc': blur, | 	'esc': blur, | ||||||
| 	'm|o': () => menu(true), | 	'm|o': () => showMenu(true), | ||||||
| 	's': () => showContent.value !== showContent.value, | 	's': () => showContent.value !== showContent.value, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -290,7 +290,7 @@ if (props.mock) { | |||||||
| 	}, { deep: true }); | 	}, { deep: true }); | ||||||
| } else { | } else { | ||||||
| 	useNoteCapture({ | 	useNoteCapture({ | ||||||
| 		rootEl: el, | 		rootEl: rootEl, | ||||||
| 		note: appearNote, | 		note: appearNote, | ||||||
| 		pureNote: note, | 		pureNote: note, | ||||||
| 		isDeletedRef: isDeleted, | 		isDeletedRef: isDeleted, | ||||||
| @@ -336,7 +336,7 @@ function reply(viaKeyboard = false): void { | |||||||
| 		reply: appearNote.value, | 		reply: appearNote.value, | ||||||
| 		channel: appearNote.value.channel, | 		channel: appearNote.value.channel, | ||||||
| 		animation: !viaKeyboard, | 		animation: !viaKeyboard, | ||||||
| 	}, () => { | 	}).then(() => { | ||||||
| 		focus(); | 		focus(); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -355,7 +355,7 @@ function react(viaKeyboard = false): void { | |||||||
| 			noteId: appearNote.value.id, | 			noteId: appearNote.value.id, | ||||||
| 			reaction: '❤️', | 			reaction: '❤️', | ||||||
| 		}); | 		}); | ||||||
| 		const el = reactButton.value as HTMLElement | null | undefined; | 		const el = reactButton.value; | ||||||
| 		if (el) { | 		if (el) { | ||||||
| 			const rect = el.getBoundingClientRect(); | 			const rect = el.getBoundingClientRect(); | ||||||
| 			const x = rect.left + (el.offsetWidth / 2); | 			const x = rect.left + (el.offsetWidth / 2); | ||||||
| @@ -364,7 +364,7 @@ function react(viaKeyboard = false): void { | |||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		blur(); | 		blur(); | ||||||
| 		reactionPicker.show(reactButton.value, reaction => { | 		reactionPicker.show(reactButton.value ?? null, reaction => { | ||||||
| 			sound.play('reaction'); | 			sound.play('reaction'); | ||||||
|  |  | ||||||
| 			if (props.mock) { | 			if (props.mock) { | ||||||
| @@ -385,8 +385,8 @@ function react(viaKeyboard = false): void { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function undoReact(note): void { | function undoReact(targetNote: Misskey.entities.Note): void { | ||||||
| 	const oldReaction = note.myReaction; | 	const oldReaction = targetNote.myReaction; | ||||||
| 	if (!oldReaction) return; | 	if (!oldReaction) return; | ||||||
|  |  | ||||||
| 	if (props.mock) { | 	if (props.mock) { | ||||||
| @@ -395,7 +395,7 @@ function undoReact(note): void { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	misskeyApi('notes/reactions/delete', { | 	misskeyApi('notes/reactions/delete', { | ||||||
| 		noteId: note.id, | 		noteId: targetNote.id, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -404,32 +404,34 @@ function onContextmenu(ev: MouseEvent): void { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const isLink = (el: HTMLElement) => { | 	const isLink = (el: HTMLElement): boolean => { | ||||||
| 		if (el.tagName === 'A') return true; | 		if (el.tagName === 'A') return true; | ||||||
| 		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。 | 		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。 | ||||||
| 		if (el.tagName === 'AUDIO') return true; | 		if (el.tagName === 'AUDIO') return true; | ||||||
| 		if (el.parentElement) { | 		if (el.parentElement) { | ||||||
| 			return isLink(el.parentElement); | 			return isLink(el.parentElement); | ||||||
| 		} | 		} | ||||||
|  | 		return false; | ||||||
| 	}; | 	}; | ||||||
| 	if (isLink(ev.target)) return; |  | ||||||
| 	if (window.getSelection().toString() !== '') return; | 	if (ev.target && isLink(ev.target as HTMLElement)) return; | ||||||
|  | 	if (window.getSelection()?.toString() !== '') return; | ||||||
|  |  | ||||||
| 	if (defaultStore.state.useReactionPickerForContextMenu) { | 	if (defaultStore.state.useReactionPickerForContextMenu) { | ||||||
| 		ev.preventDefault(); | 		ev.preventDefault(); | ||||||
| 		react(); | 		react(); | ||||||
| 	} else { | 	} else { | ||||||
| 		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | 		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); | ||||||
| 		os.contextMenu(menu, ev).then(focus).finally(cleanup); | 		os.contextMenu(menu, ev).then(focus).finally(cleanup); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function menu(viaKeyboard = false): void { | function showMenu(viaKeyboard = false): void { | ||||||
| 	if (props.mock) { | 	if (props.mock) { | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | 	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); | ||||||
| 	os.popupMenu(menu, menuButton.value, { | 	os.popupMenu(menu, menuButton.value, { | ||||||
| 		viaKeyboard, | 		viaKeyboard, | ||||||
| 	}).then(focus).finally(cleanup); | 	}).then(focus).finally(cleanup); | ||||||
| @@ -476,7 +478,7 @@ function showRenoteMenu(viaKeyboard = false): void { | |||||||
| 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), | 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), | ||||||
| 			{ type: 'divider' }, | 			{ type: 'divider' }, | ||||||
| 			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), | 			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), | ||||||
| 			$i.isModerator || $i.isAdmin ? getUnrenote() : undefined, | 			($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined, | ||||||
| 		], renoteTime.value, { | 		], renoteTime.value, { | ||||||
| 			viaKeyboard: viaKeyboard, | 			viaKeyboard: viaKeyboard, | ||||||
| 		}); | 		}); | ||||||
| @@ -484,19 +486,19 @@ function showRenoteMenu(viaKeyboard = false): void { | |||||||
| } | } | ||||||
|  |  | ||||||
| function focus() { | function focus() { | ||||||
| 	el.value.focus(); | 	rootEl.value?.focus(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function blur() { | function blur() { | ||||||
| 	el.value.blur(); | 	rootEl.value?.blur(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function focusBefore() { | function focusBefore() { | ||||||
| 	focusPrev(el.value); | 	focusPrev(rootEl.value ?? null); | ||||||
| } | } | ||||||
|  |  | ||||||
| function focusAfter() { | function focusAfter() { | ||||||
| 	focusNext(el.value); | 	focusNext(rootEl.value ?? null); | ||||||
| } | } | ||||||
|  |  | ||||||
| function readPromo() { | function readPromo() { | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ import { i18n } from '@/i18n.js'; | |||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	checked: boolean | Ref<boolean>; | 	checked: boolean | Ref<boolean>; | ||||||
| 	disabled?: boolean; | 	disabled?: boolean | Ref<boolean>; | ||||||
| }>(), { | }>(), { | ||||||
| 	disabled: false, | 	disabled: false, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import { defineAsyncComponent, Ref } from 'vue'; | import { defineAsyncComponent, Ref, ShallowRef } from 'vue'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
| import { claimAchievement } from './achievements.js'; | import { claimAchievement } from './achievements.js'; | ||||||
| import { $i } from '@/account.js'; | import { $i } from '@/account.js'; | ||||||
| @@ -36,7 +36,7 @@ export async function getNoteClipMenu(props: { | |||||||
| 	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note; | 	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note; | ||||||
|  |  | ||||||
| 	const clips = await clipsCache.fetch(); | 	const clips = await clipsCache.fetch(); | ||||||
| 	return [...clips.map(clip => ({ | 	const menu: MenuItem[] = [...clips.map(clip => ({ | ||||||
| 		text: clip.name, | 		text: clip.name, | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			claimAchievement('noteClipped1'); | 			claimAchievement('noteClipped1'); | ||||||
| @@ -93,6 +93,8 @@ export async function getNoteClipMenu(props: { | |||||||
| 			os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); | 			os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); | ||||||
| 		}, | 		}, | ||||||
| 	}]; | 	}]; | ||||||
|  |  | ||||||
|  | 	return menu; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): MenuItem { | export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): MenuItem { | ||||||
| @@ -122,7 +124,6 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string): | |||||||
|  |  | ||||||
| export function getNoteMenu(props: { | export function getNoteMenu(props: { | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| 	menuButton: Ref<HTMLElement>; |  | ||||||
| 	translation: Ref<Misskey.entities.NotesTranslateResponse | null>; | 	translation: Ref<Misskey.entities.NotesTranslateResponse | null>; | ||||||
| 	translating: Ref<boolean>; | 	translating: Ref<boolean>; | ||||||
| 	isDeleted: Ref<boolean>; | 	isDeleted: Ref<boolean>; | ||||||
| @@ -471,7 +472,7 @@ function smallerVisibility(a: Visibility | string, b: Visibility | string): Visi | |||||||
|  |  | ||||||
| export function getRenoteMenu(props: { | export function getRenoteMenu(props: { | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| 	renoteButton: Ref<HTMLElement>; | 	renoteButton: ShallowRef<HTMLElement | undefined>; | ||||||
| 	mock?: boolean; | 	mock?: boolean; | ||||||
| }) { | }) { | ||||||
| 	const isRenote = ( | 	const isRenote = ( | ||||||
| @@ -491,7 +492,7 @@ export function getRenoteMenu(props: { | |||||||
| 			text: i18n.ts.inChannelRenote, | 			text: i18n.ts.inChannelRenote, | ||||||
| 			icon: 'ti ti-repeat', | 			icon: 'ti ti-repeat', | ||||||
| 			action: () => { | 			action: () => { | ||||||
| 				const el = props.renoteButton.value as HTMLElement | null | undefined; | 				const el = props.renoteButton.value; | ||||||
| 				if (el) { | 				if (el) { | ||||||
| 					const rect = el.getBoundingClientRect(); | 					const rect = el.getBoundingClientRect(); | ||||||
| 					const x = rect.left + (el.offsetWidth / 2); | 					const x = rect.left + (el.offsetWidth / 2); | ||||||
| @@ -527,7 +528,7 @@ export function getRenoteMenu(props: { | |||||||
| 			text: i18n.ts.renote, | 			text: i18n.ts.renote, | ||||||
| 			icon: 'ti ti-repeat', | 			icon: 'ti ti-repeat', | ||||||
| 			action: () => { | 			action: () => { | ||||||
| 				const el = props.renoteButton.value as HTMLElement | null | undefined; | 				const el = props.renoteButton.value; | ||||||
| 				if (el) { | 				if (el) { | ||||||
| 					const rect = el.getBoundingClientRect(); | 					const rect = el.getBoundingClientRect(); | ||||||
| 					const x = rect.left + (el.offsetWidth / 2); | 					const x = rect.left + (el.offsetWidth / 2); | ||||||
| @@ -567,7 +568,7 @@ export function getRenoteMenu(props: { | |||||||
|  |  | ||||||
| 	const renoteItems = [ | 	const renoteItems = [ | ||||||
| 		...normalRenoteItems, | 		...normalRenoteItems, | ||||||
| 		...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] : [], | 		...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [], | ||||||
| 		...channelRenoteItems, | 		...channelRenoteItems, | ||||||
| 	]; | 	]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ class ReactionPicker { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public show(src: HTMLElement, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { | 	public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { | ||||||
| 		this.src.value = src; | 		this.src.value = src; | ||||||
| 		this.manualShowing.value = true; | 		this.manualShowing.value = true; | ||||||
| 		this.onChosen = onChosen; | 		this.onChosen = onChosen; | ||||||
|   | |||||||
| @@ -3,13 +3,13 @@ | |||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import { onUnmounted, Ref } from 'vue'; | import { onUnmounted, Ref, ShallowRef } from 'vue'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
| import { useStream } from '@/stream.js'; | import { useStream } from '@/stream.js'; | ||||||
| import { $i } from '@/account.js'; | import { $i } from '@/account.js'; | ||||||
|  |  | ||||||
| export function useNoteCapture(props: { | export function useNoteCapture(props: { | ||||||
| 	rootEl: Ref<HTMLElement>; | 	rootEl: ShallowRef<HTMLElement | undefined>; | ||||||
| 	note: Ref<Misskey.entities.Note>; | 	note: Ref<Misskey.entities.Note>; | ||||||
| 	pureNote: Ref<Misskey.entities.Note>; | 	pureNote: Ref<Misskey.entities.Note>; | ||||||
| 	isDeletedRef: Ref<boolean>; | 	isDeletedRef: Ref<boolean>; | ||||||
| @@ -83,7 +83,7 @@ export function useNoteCapture(props: { | |||||||
| 	function capture(withHandler = false): void { | 	function capture(withHandler = false): void { | ||||||
| 		if (connection) { | 		if (connection) { | ||||||
| 			// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する | 			// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する | ||||||
| 			connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: note.value.id }); | 			connection.send(document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id }); | ||||||
| 			if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id }); | 			if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id }); | ||||||
| 			if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); | 			if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); | ||||||
| 		} | 		} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 yukineko
					yukineko