refactor(frontend): 非推奨となったReactivity Transformを使わないように (#12539)
* refactor(frontend): 非推奨となったReactivity Transformを使わないように * refactor: 不要な括弧を除去 * fix: 不要なアノテーションを除去 * fix: Refの配列をrefしている部分の対応 * refactor: 不要な括弧を除去 * fix: lint * refactor: Ref、ShallowRef、ComputedRefの変数の宣言をletからconstに置換 * fix: type error * chore: drop reactivity transform from eslint configuration * refactor: remove unnecessary import * fix: 対応漏れ
This commit is contained in:
		| @@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkSwitch from '@/components/MkSwitch.vue'; | ||||
| import MkKeyValue from '@/components/MkKeyValue.vue'; | ||||
| @@ -56,11 +57,11 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'resolved', reportId: string): void; | ||||
| }>(); | ||||
|  | ||||
| let forward = $ref(props.report.forwarded); | ||||
| const forward = ref(props.report.forwarded); | ||||
|  | ||||
| function resolve() { | ||||
| 	os.apiWithDialog('admin/resolve-abuse-user-report', { | ||||
| 		forward: forward, | ||||
| 		forward: forward.value, | ||||
| 		reportId: props.report.id, | ||||
| 	}).then(() => { | ||||
| 		emit('resolved', props.report.id); | ||||
|   | ||||
| @@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref, computed } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js'; | ||||
| @@ -67,15 +67,15 @@ const props = withDefaults(defineProps<{ | ||||
| 	withDescription: true, | ||||
| }); | ||||
|  | ||||
| let achievements = $ref(); | ||||
| const lockedAchievements = $computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements ?? []).some(a => a.name === x))); | ||||
| const achievements = ref(); | ||||
| const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x))); | ||||
|  | ||||
| function fetch() { | ||||
| 	os.api('users/achievements', { userId: props.user.id }).then(res => { | ||||
| 		achievements = []; | ||||
| 		achievements.value = []; | ||||
| 		for (const t of ACHIEVEMENT_TYPES) { | ||||
| 			const a = res.find(x => x.name === t); | ||||
| 			if (a) achievements.push(a); | ||||
| 			if (a) achievements.value.push(a); | ||||
| 		} | ||||
| 		//achievements = res.sort((a, b) => b.unlockedAt - a.unlockedAt); | ||||
| 	}); | ||||
|   | ||||
| @@ -138,45 +138,45 @@ const texts = computed(() => { | ||||
| }); | ||||
|  | ||||
| let enabled = true; | ||||
| let majorGraduationColor = $ref<string>(); | ||||
| const majorGraduationColor = ref<string>(); | ||||
| //let minorGraduationColor = $ref<string>(); | ||||
| let sHandColor = $ref<string>(); | ||||
| let mHandColor = $ref<string>(); | ||||
| let hHandColor = $ref<string>(); | ||||
| let nowColor = $ref<string>(); | ||||
| let h = $ref<number>(0); | ||||
| let m = $ref<number>(0); | ||||
| let s = $ref<number>(0); | ||||
| let hAngle = $ref<number>(0); | ||||
| let mAngle = $ref<number>(0); | ||||
| let sAngle = $ref<number>(0); | ||||
| let disableSAnimate = $ref(false); | ||||
| const sHandColor = ref<string>(); | ||||
| const mHandColor = ref<string>(); | ||||
| const hHandColor = ref<string>(); | ||||
| const nowColor = ref<string>(); | ||||
| const h = ref<number>(0); | ||||
| const m = ref<number>(0); | ||||
| const s = ref<number>(0); | ||||
| const hAngle = ref<number>(0); | ||||
| const mAngle = ref<number>(0); | ||||
| const sAngle = ref<number>(0); | ||||
| const disableSAnimate = ref(false); | ||||
| let sOneRound = false; | ||||
| const sLine = ref<SVGPathElement>(); | ||||
|  | ||||
| function tick() { | ||||
| 	const now = props.now(); | ||||
| 	now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset); | ||||
| 	const previousS = s; | ||||
| 	const previousM = m; | ||||
| 	const previousH = h; | ||||
| 	s = now.getSeconds(); | ||||
| 	m = now.getMinutes(); | ||||
| 	h = now.getHours(); | ||||
| 	if (previousS === s && previousM === m && previousH === h) { | ||||
| 	const previousS = s.value; | ||||
| 	const previousM = m.value; | ||||
| 	const previousH = h.value; | ||||
| 	s.value = now.getSeconds(); | ||||
| 	m.value = now.getMinutes(); | ||||
| 	h.value = now.getHours(); | ||||
| 	if (previousS === s.value && previousM === m.value && previousH === h.value) { | ||||
| 		return; | ||||
| 	} | ||||
| 	hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6); | ||||
| 	mAngle = Math.PI * (m + s / 60) / 30; | ||||
| 	hAngle.value = Math.PI * (h.value % (props.twentyfour ? 24 : 12) + (m.value + s.value / 60) / 60) / (props.twentyfour ? 12 : 6); | ||||
| 	mAngle.value = Math.PI * (m.value + s.value / 60) / 30; | ||||
| 	if (sOneRound && sLine.value) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない) | ||||
| 		sAngle = Math.PI * 60 / 30; | ||||
| 		sAngle.value = Math.PI * 60 / 30; | ||||
| 		defaultIdlingRenderScheduler.delete(tick); | ||||
| 		sLine.value.addEventListener('transitionend', () => { | ||||
| 			disableSAnimate = true; | ||||
| 			disableSAnimate.value = true; | ||||
| 			requestAnimationFrame(() => { | ||||
| 				sAngle = 0; | ||||
| 				sAngle.value = 0; | ||||
| 				requestAnimationFrame(() => { | ||||
| 					disableSAnimate = false; | ||||
| 					disableSAnimate.value = false; | ||||
| 					if (enabled) { | ||||
| 						defaultIdlingRenderScheduler.add(tick); | ||||
| 					} | ||||
| @@ -184,9 +184,9 @@ function tick() { | ||||
| 			}); | ||||
| 		}, { once: true }); | ||||
| 	} else { | ||||
| 		sAngle = Math.PI * s / 30; | ||||
| 		sAngle.value = Math.PI * s.value / 30; | ||||
| 	} | ||||
| 	sOneRound = s === 59; | ||||
| 	sOneRound = s.value === 59; | ||||
| } | ||||
|  | ||||
| tick(); | ||||
| @@ -195,12 +195,12 @@ function calcColors() { | ||||
| 	const computedStyle = getComputedStyle(document.documentElement); | ||||
| 	const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark(); | ||||
| 	const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); | ||||
| 	majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; | ||||
| 	majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; | ||||
| 	//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||
| 	sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; | ||||
| 	mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString(); | ||||
| 	hHandColor = accent; | ||||
| 	nowColor = accent; | ||||
| 	sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; | ||||
| 	mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString(); | ||||
| 	hHandColor.value = accent; | ||||
| 	nowColor.value = accent; | ||||
| } | ||||
|  | ||||
| calcColors(); | ||||
|   | ||||
| @@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { Ref } from 'vue'; | ||||
| import { Ref, ref } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| @@ -87,10 +87,10 @@ function g(id) { | ||||
| 	return props.components.find(x => x.value.id === id).value; | ||||
| } | ||||
|  | ||||
| let valueForSwitch = $ref(c.default ?? false); | ||||
| const valueForSwitch = ref(c.default ?? false); | ||||
|  | ||||
| function onSwitchUpdate(v) { | ||||
| 	valueForSwitch = v; | ||||
| 	valueForSwitch.value = v; | ||||
| 	if (c.onChange) c.onChange(v); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { nextTick, onMounted } from 'vue'; | ||||
| import { nextTick, onMounted, shallowRef } from 'vue'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	type?: 'button' | 'submit' | 'reset'; | ||||
| @@ -59,13 +59,13 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'click', payload: MouseEvent): void; | ||||
| }>(); | ||||
|  | ||||
| let el = $shallowRef<HTMLElement | null>(null); | ||||
| let ripples = $shallowRef<HTMLElement | null>(null); | ||||
| const el = shallowRef<HTMLElement | null>(null); | ||||
| const ripples = shallowRef<HTMLElement | null>(null); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (props.autofocus) { | ||||
| 		nextTick(() => { | ||||
| 			el!.focus(); | ||||
| 			el.value!.focus(); | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| @@ -88,11 +88,11 @@ function onMousedown(evt: MouseEvent): void { | ||||
| 	const rect = target.getBoundingClientRect(); | ||||
|  | ||||
| 	const ripple = document.createElement('div'); | ||||
| 	ripple.classList.add(ripples!.dataset.childrenClass!); | ||||
| 	ripple.classList.add(ripples.value!.dataset.childrenClass!); | ||||
| 	ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; | ||||
| 	ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; | ||||
|  | ||||
| 	ripples!.appendChild(ripple); | ||||
| 	ripples.value!.appendChild(ripple); | ||||
|  | ||||
| 	const circleCenterX = evt.clientX - rect.left; | ||||
| 	const circleCenterY = evt.clientY - rect.top; | ||||
| @@ -107,7 +107,7 @@ function onMousedown(evt: MouseEvent): void { | ||||
| 		ripple.style.opacity = '0'; | ||||
| 	}, 1000); | ||||
| 	window.setTimeout(() => { | ||||
| 		if (ripples) ripples.removeChild(ripple); | ||||
| 		if (ripples.value) ripples.value.removeChild(ripple); | ||||
| 	}, 2000); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -74,7 +74,7 @@ const props = defineProps({ | ||||
| 	}, | ||||
| }); | ||||
|  | ||||
| let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>(); | ||||
| const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>(); | ||||
|  | ||||
| const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); | ||||
| const negate = arr => arr.map(x => -x); | ||||
| @@ -268,7 +268,7 @@ const render = () => { | ||||
| 				gradient, | ||||
| 			}, | ||||
| 		}, | ||||
| 		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])], | ||||
| 		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl.value)] : [])], | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -13,29 +13,30 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { shallowRef } from 'vue'; | ||||
| import { Chart, LegendItem } from 'chart.js'; | ||||
|  | ||||
| const props = defineProps({ | ||||
| }); | ||||
|  | ||||
| let chart = $shallowRef<Chart>(); | ||||
| let items = $shallowRef<LegendItem[]>([]); | ||||
| const chart = shallowRef<Chart>(); | ||||
| const items = shallowRef<LegendItem[]>([]); | ||||
|  | ||||
| function update(_chart: Chart, _items: LegendItem[]) { | ||||
| 	chart = _chart, | ||||
| 	items = _items; | ||||
| 	chart.value = _chart, | ||||
| 	items.value = _items; | ||||
| } | ||||
|  | ||||
| function onClick(item: LegendItem) { | ||||
| 	if (chart == null) return; | ||||
| 	const { type } = chart.config; | ||||
| 	if (chart.value == null) return; | ||||
| 	const { type } = chart.value.config; | ||||
| 	if (type === 'pie' || type === 'doughnut') { | ||||
| 		// Pie and doughnut charts only have a single dataset and visibility is per item | ||||
| 		chart.toggleDataVisibility(item.index); | ||||
| 		chart.value.toggleDataVisibility(item.index); | ||||
| 	} else { | ||||
| 		chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex)); | ||||
| 		chart.value.setDatasetVisibility(item.datasetIndex, !chart.value.isDatasetVisible(item.datasetIndex)); | ||||
| 	} | ||||
| 	chart.update(); | ||||
| 	chart.value.update(); | ||||
| } | ||||
|  | ||||
| defineExpose({ | ||||
|   | ||||
| @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed, onMounted, onUnmounted } from 'vue'; | ||||
| import { computed, onMounted, onUnmounted, ref } from 'vue'; | ||||
| import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { useInterval } from '@/scripts/use-interval.js'; | ||||
| @@ -29,8 +29,8 @@ import { claimAchievement } from '@/scripts/achievements.js'; | ||||
|  | ||||
| const saveData = game.saveData; | ||||
| const cookies = computed(() => saveData.value?.cookies); | ||||
| let cps = $ref(0); | ||||
| let prevCookies = $ref(0); | ||||
| const cps = ref(0); | ||||
| const prevCookies = ref(0); | ||||
|  | ||||
| function onClick(ev: MouseEvent) { | ||||
| 	const x = ev.clientX; | ||||
| @@ -48,9 +48,9 @@ function onClick(ev: MouseEvent) { | ||||
| } | ||||
|  | ||||
| useInterval(() => { | ||||
| 	const diff = saveData.value!.cookies - prevCookies; | ||||
| 	cps = diff; | ||||
| 	prevCookies = saveData.value!.cookies; | ||||
| 	const diff = saveData.value!.cookies - prevCookies.value; | ||||
| 	cps.value = diff; | ||||
| 	prevCookies.value = saveData.value!.cookies; | ||||
| }, 1000, { | ||||
| 	immediate: false, | ||||
| 	afterMounted: true, | ||||
| @@ -63,7 +63,7 @@ useInterval(game.save, 1000 * 5, { | ||||
|  | ||||
| onMounted(async () => { | ||||
| 	await game.load(); | ||||
| 	prevCookies = saveData.value!.cookies; | ||||
| 	prevCookies.value = saveData.value!.cookies; | ||||
| }); | ||||
|  | ||||
| onUnmounted(() => { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onBeforeUnmount } from 'vue'; | ||||
| import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue'; | ||||
| import MkMenu from './MkMenu.vue'; | ||||
| import { MenuItem } from './types/menu.vue'; | ||||
| import contains from '@/scripts/contains.js'; | ||||
| @@ -34,9 +34,9 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| let rootEl = $shallowRef<HTMLDivElement>(); | ||||
| const rootEl = shallowRef<HTMLDivElement>(); | ||||
|  | ||||
| let zIndex = $ref<number>(os.claimZIndex('high')); | ||||
| const zIndex = ref<number>(os.claimZIndex('high')); | ||||
|  | ||||
| const SCROLLBAR_THICKNESS = 16; | ||||
|  | ||||
| @@ -44,8 +44,8 @@ onMounted(() => { | ||||
| 	let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 | ||||
| 	let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 | ||||
|  | ||||
| 	const width = rootEl.offsetWidth; | ||||
| 	const height = rootEl.offsetHeight; | ||||
| 	const width = rootEl.value.offsetWidth; | ||||
| 	const height = rootEl.value.offsetHeight; | ||||
|  | ||||
| 	if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) { | ||||
| 		left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset; | ||||
| @@ -63,8 +63,8 @@ onMounted(() => { | ||||
| 		left = 0; | ||||
| 	} | ||||
|  | ||||
| 	rootEl.style.top = `${top}px`; | ||||
| 	rootEl.style.left = `${left}px`; | ||||
| 	rootEl.value.style.top = `${top}px`; | ||||
| 	rootEl.value.style.left = `${left}px`; | ||||
|  | ||||
| 	document.body.addEventListener('mousedown', onMousedown); | ||||
| }); | ||||
| @@ -74,7 +74,7 @@ onBeforeUnmount(() => { | ||||
| }); | ||||
|  | ||||
| function onMousedown(evt: Event) { | ||||
| 	if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed'); | ||||
| 	if (!contains(rootEl.value, evt.target) && (rootEl.value !== evt.target)) emit('closed'); | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, shallowRef, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import Cropper from 'cropperjs'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| @@ -56,10 +56,10 @@ const props = defineProps<{ | ||||
| }>(); | ||||
|  | ||||
| const imgUrl = getProxiedImageUrl(props.file.url, undefined, true); | ||||
| let dialogEl = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| let imgEl = $shallowRef<HTMLImageElement>(); | ||||
| const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const imgEl = shallowRef<HTMLImageElement>(); | ||||
| let cropper: Cropper | null = null; | ||||
| let loading = $ref(true); | ||||
| const loading = ref(true); | ||||
|  | ||||
| const ok = async () => { | ||||
| 	const promise = new Promise<Misskey.entities.DriveFile>(async (res) => { | ||||
| @@ -94,16 +94,16 @@ const ok = async () => { | ||||
| 	const f = await promise; | ||||
|  | ||||
| 	emit('ok', f); | ||||
| 	dialogEl!.close(); | ||||
| 	dialogEl.value!.close(); | ||||
| }; | ||||
|  | ||||
| const cancel = () => { | ||||
| 	emit('cancel'); | ||||
| 	dialogEl!.close(); | ||||
| 	dialogEl.value!.close(); | ||||
| }; | ||||
|  | ||||
| const onImageLoad = () => { | ||||
| 	loading = false; | ||||
| 	loading.value = false; | ||||
|  | ||||
| 	if (cropper) { | ||||
| 		cropper.getCropperImage()!.$center('contain'); | ||||
| @@ -112,7 +112,7 @@ const onImageLoad = () => { | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
| 	cropper = new Cropper(imgEl!, { | ||||
| 	cropper = new Cropper(imgEl.value!, { | ||||
| 	}); | ||||
|  | ||||
| 	const computedStyle = getComputedStyle(document.documentElement); | ||||
|   | ||||
| @@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> | ||||
| 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> | ||||
| 			<template #caption> | ||||
| 				<span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/> | ||||
| 				<span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/> | ||||
| 				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/> | ||||
| 				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/> | ||||
| 			</template> | ||||
| 		</MkInput> | ||||
| 		<MkSelect v-if="select" v-model="selectedValue" autofocus> | ||||
| @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 			</template> | ||||
| 		</MkSelect> | ||||
| 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> | ||||
| 			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> | ||||
| 			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> | ||||
| 			<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton> | ||||
| 		</div> | ||||
| 		<div v-if="actions" :class="$style.buttons"> | ||||
| @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'; | ||||
| import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| @@ -122,24 +122,21 @@ const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const inputValue = ref<string | number | null>(props.input?.default ?? null); | ||||
| const selectedValue = ref(props.select?.default ?? null); | ||||
|  | ||||
| let disabledReason = $ref<null | 'charactersExceeded' | 'charactersBelow'>(null); | ||||
| const okButtonDisabled = $computed<boolean>(() => { | ||||
| const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => { | ||||
| 	if (props.input) { | ||||
| 		if (props.input.minLength) { | ||||
| 			if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) { | ||||
| 				disabledReason = 'charactersBelow'; | ||||
| 				return true; | ||||
| 				return 'charactersBelow'; | ||||
| 			} | ||||
| 		} | ||||
| 		if (props.input.maxLength) { | ||||
| 			if (inputValue.value && (inputValue.value as string).length > props.input.maxLength) { | ||||
| 				disabledReason = 'charactersExceeded'; | ||||
| 				return true; | ||||
| 				return 'charactersExceeded'; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| 	return null; | ||||
| }); | ||||
|  | ||||
| function done(canceled: boolean, result?) { | ||||
|   | ||||
| @@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { shallowRef } from 'vue'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -54,23 +55,23 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| const modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const picker = $shallowRef<InstanceType<typeof MkEmojiPicker>>(); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>(); | ||||
|  | ||||
| function chosen(emoji: any) { | ||||
| 	emit('done', emoji); | ||||
| 	if (props.choseAndClose) { | ||||
| 		modal?.close(); | ||||
| 		modal.value?.close(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function opening() { | ||||
| 	picker?.reset(); | ||||
| 	picker?.focus(); | ||||
| 	picker.value?.reset(); | ||||
| 	picker.value?.focus(); | ||||
|  | ||||
| 	// 何故かちょっと待たないとフォーカスされない | ||||
| 	setTimeout(() => { | ||||
| 		picker?.focus(); | ||||
| 		picker.value?.focus(); | ||||
| 	}, 10); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { shallowRef, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
| @@ -42,12 +42,12 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| let caption = $ref(props.default); | ||||
| const caption = ref(props.default); | ||||
|  | ||||
| async function ok() { | ||||
| 	emit('done', caption); | ||||
| 	dialog.close(); | ||||
| 	emit('done', caption.value); | ||||
| 	dialog.value.close(); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { nextTick, onMounted } from 'vue'; | ||||
| import { nextTick, onMounted, shallowRef, ref } from 'vue'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| @@ -70,10 +70,10 @@ const getBgColor = (el: HTMLElement) => { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| let rootEl = $shallowRef<HTMLElement>(); | ||||
| let bgSame = $ref(false); | ||||
| let opened = $ref(props.defaultOpen); | ||||
| let openedAtLeastOnce = $ref(props.defaultOpen); | ||||
| const rootEl = shallowRef<HTMLElement>(); | ||||
| const bgSame = ref(false); | ||||
| const opened = ref(props.defaultOpen); | ||||
| const openedAtLeastOnce = ref(props.defaultOpen); | ||||
|  | ||||
| function enter(el) { | ||||
| 	const elementHeight = el.getBoundingClientRect().height; | ||||
| @@ -98,20 +98,20 @@ function afterLeave(el) { | ||||
| } | ||||
|  | ||||
| function toggle() { | ||||
| 	if (!opened) { | ||||
| 		openedAtLeastOnce = true; | ||||
| 	if (!opened.value) { | ||||
| 		openedAtLeastOnce.value = true; | ||||
| 	} | ||||
|  | ||||
| 	nextTick(() => { | ||||
| 		opened = !opened; | ||||
| 		opened.value = !opened.value; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
| 	const computedStyle = getComputedStyle(document.documentElement); | ||||
| 	const parentBg = getBgColor(rootEl.parentElement); | ||||
| 	const parentBg = getBgColor(rootEl.value.parentElement); | ||||
| 	const myBg = computedStyle.getPropertyValue('--panel'); | ||||
| 	bgSame = parentBg === myBg; | ||||
| 	bgSame.value = parentBg === myBg; | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onBeforeUnmount, onMounted } from 'vue'; | ||||
| import { onBeforeUnmount, onMounted, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { useStream } from '@/stream.js'; | ||||
| @@ -57,9 +57,9 @@ const emit = defineEmits<{ | ||||
| 	(_: 'update:user', value: Misskey.entities.UserDetailed): void | ||||
| }>(); | ||||
|  | ||||
| let isFollowing = $ref(props.user.isFollowing); | ||||
| let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); | ||||
| let wait = $ref(false); | ||||
| const isFollowing = ref(props.user.isFollowing); | ||||
| const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); | ||||
| const wait = ref(false); | ||||
| const connection = useStream().useChannel('main'); | ||||
|  | ||||
| if (props.user.isFollowing == null) { | ||||
| @@ -71,16 +71,16 @@ if (props.user.isFollowing == null) { | ||||
|  | ||||
| function onFollowChange(user: Misskey.entities.UserDetailed) { | ||||
| 	if (user.id === props.user.id) { | ||||
| 		isFollowing = user.isFollowing; | ||||
| 		hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 		isFollowing.value = user.isFollowing; | ||||
| 		hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async function onClick() { | ||||
| 	wait = true; | ||||
| 	wait.value = true; | ||||
|  | ||||
| 	try { | ||||
| 		if (isFollowing) { | ||||
| 		if (isFollowing.value) { | ||||
| 			const { canceled } = await os.confirm({ | ||||
| 				type: 'warning', | ||||
| 				text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), | ||||
| @@ -92,11 +92,11 @@ async function onClick() { | ||||
| 				userId: props.user.id, | ||||
| 			}); | ||||
| 		} else { | ||||
| 			if (hasPendingFollowRequestFromYou) { | ||||
| 			if (hasPendingFollowRequestFromYou.value) { | ||||
| 				await os.api('following/requests/cancel', { | ||||
| 					userId: props.user.id, | ||||
| 				}); | ||||
| 				hasPendingFollowRequestFromYou = false; | ||||
| 				hasPendingFollowRequestFromYou.value = false; | ||||
| 			} else { | ||||
| 				await os.api('following/create', { | ||||
| 					userId: props.user.id, | ||||
| @@ -106,7 +106,7 @@ async function onClick() { | ||||
| 					...props.user, | ||||
| 					withReplies: defaultStore.state.defaultWithReplies | ||||
| 				}); | ||||
| 				hasPendingFollowRequestFromYou = true; | ||||
| 				hasPendingFollowRequestFromYou.value = true; | ||||
|  | ||||
| 				claimAchievement('following1'); | ||||
|  | ||||
| @@ -127,7 +127,7 @@ async function onClick() { | ||||
| 	} catch (err) { | ||||
| 		console.error(err); | ||||
| 	} finally { | ||||
| 		wait = false; | ||||
| 		wait.value = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| @@ -53,19 +53,19 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| let dialog: InstanceType<typeof MkModalWindow> = $ref(); | ||||
| const dialog = ref<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| let username = $ref(''); | ||||
| let email = $ref(''); | ||||
| let processing = $ref(false); | ||||
| const username = ref(''); | ||||
| const email = ref(''); | ||||
| const processing = ref(false); | ||||
|  | ||||
| async function onSubmit() { | ||||
| 	processing = true; | ||||
| 	processing.value = true; | ||||
| 	await os.apiWithDialog('request-reset-password', { | ||||
| 		username, | ||||
| 		email, | ||||
| 		username: username.value, | ||||
| 		email: email.value, | ||||
| 	}); | ||||
| 	emit('done'); | ||||
| 	dialog.close(); | ||||
| 	dialog.value.close(); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, nextTick, watch } from 'vue'; | ||||
| import { onMounted, nextTick, watch, shallowRef, ref } from 'vue'; | ||||
| import { Chart } from 'chart.js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -27,11 +27,11 @@ const props = defineProps<{ | ||||
| 	src: string; | ||||
| }>(); | ||||
|  | ||||
| const rootEl = $shallowRef<HTMLDivElement>(null); | ||||
| const chartEl = $shallowRef<HTMLCanvasElement>(null); | ||||
| const rootEl = shallowRef<HTMLDivElement>(null); | ||||
| const chartEl = shallowRef<HTMLCanvasElement>(null); | ||||
| const now = new Date(); | ||||
| let chartInstance: Chart = null; | ||||
| let fetching = $ref(true); | ||||
| const fetching = ref(true); | ||||
|  | ||||
| const { handler: externalTooltipHandler } = useChartTooltip({ | ||||
| 	position: 'middle', | ||||
| @@ -42,8 +42,8 @@ async function renderChart() { | ||||
| 		chartInstance.destroy(); | ||||
| 	} | ||||
|  | ||||
| 	const wide = rootEl.offsetWidth > 700; | ||||
| 	const narrow = rootEl.offsetWidth < 400; | ||||
| 	const wide = rootEl.value.offsetWidth > 700; | ||||
| 	const narrow = rootEl.value.offsetWidth < 400; | ||||
|  | ||||
| 	const weeks = wide ? 50 : narrow ? 10 : 25; | ||||
| 	const chartLimit = 7 * weeks; | ||||
| @@ -88,7 +88,7 @@ async function renderChart() { | ||||
| 		values = raw.deliverFailed; | ||||
| 	} | ||||
|  | ||||
| 	fetching = false; | ||||
| 	fetching.value = false; | ||||
|  | ||||
| 	await nextTick(); | ||||
|  | ||||
| @@ -101,7 +101,7 @@ async function renderChart() { | ||||
|  | ||||
| 	const marginEachCell = 4; | ||||
|  | ||||
| 	chartInstance = new Chart(chartEl, { | ||||
| 	chartInstance = new Chart(chartEl.value, { | ||||
| 		type: 'matrix', | ||||
| 		data: { | ||||
| 			datasets: [{ | ||||
| @@ -210,7 +210,7 @@ async function renderChart() { | ||||
| } | ||||
|  | ||||
| watch(() => props.src, () => { | ||||
| 	fetching = true; | ||||
| 	fetching.value = true; | ||||
| 	renderChart(); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { $ref } from 'vue/macros'; | ||||
| import DrawBlurhash from '@/workers/draw-blurhash?worker'; | ||||
| import TestWebGL2 from '@/workers/test-webgl2?worker'; | ||||
| import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js'; | ||||
| @@ -58,7 +57,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol | ||||
| </script> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue'; | ||||
| import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { render } from 'buraha'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -98,41 +97,41 @@ const viewId = uuid(); | ||||
| const canvas = shallowRef<HTMLCanvasElement>(); | ||||
| const root = shallowRef<HTMLDivElement>(); | ||||
| const img = shallowRef<HTMLImageElement>(); | ||||
| let loaded = $ref(false); | ||||
| let canvasWidth = $ref(64); | ||||
| let canvasHeight = $ref(64); | ||||
| let imgWidth = $ref(props.width); | ||||
| let imgHeight = $ref(props.height); | ||||
| let bitmapTmp = $ref<CanvasImageSource | undefined>(); | ||||
| const hide = computed(() => !loaded || props.forceBlurhash); | ||||
| const loaded = ref(false); | ||||
| const canvasWidth = ref(64); | ||||
| const canvasHeight = ref(64); | ||||
| const imgWidth = ref(props.width); | ||||
| const imgHeight = ref(props.height); | ||||
| const bitmapTmp = ref<CanvasImageSource | undefined>(); | ||||
| const hide = computed(() => !loaded.value || props.forceBlurhash); | ||||
|  | ||||
| function waitForDecode() { | ||||
| 	if (props.src != null && props.src !== '') { | ||||
| 		nextTick() | ||||
| 			.then(() => img.value?.decode()) | ||||
| 			.then(() => { | ||||
| 				loaded = true; | ||||
| 				loaded.value = true; | ||||
| 			}, error => { | ||||
| 				console.log('Error occurred during decoding image', img.value, error); | ||||
| 			}); | ||||
| 	} else { | ||||
| 		loaded = false; | ||||
| 		loaded.value = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| watch([() => props.width, () => props.height, root], () => { | ||||
| 	const ratio = props.width / props.height; | ||||
| 	if (ratio > 1) { | ||||
| 		canvasWidth = Math.round(64 * ratio); | ||||
| 		canvasHeight = 64; | ||||
| 		canvasWidth.value = Math.round(64 * ratio); | ||||
| 		canvasHeight.value = 64; | ||||
| 	} else { | ||||
| 		canvasWidth = 64; | ||||
| 		canvasHeight = Math.round(64 / ratio); | ||||
| 		canvasWidth.value = 64; | ||||
| 		canvasHeight.value = Math.round(64 / ratio); | ||||
| 	} | ||||
|  | ||||
| 	const clientWidth = root.value?.clientWidth ?? 300; | ||||
| 	imgWidth = clientWidth; | ||||
| 	imgHeight = Math.round(clientWidth / ratio); | ||||
| 	imgWidth.value = clientWidth; | ||||
| 	imgHeight.value = Math.round(clientWidth / ratio); | ||||
| }, { | ||||
| 	immediate: true, | ||||
| }); | ||||
| @@ -140,15 +139,15 @@ watch([() => props.width, () => props.height, root], () => { | ||||
| function drawImage(bitmap: CanvasImageSource) { | ||||
| 	// canvasがない(mountedされていない)場合はTmpに保存しておく | ||||
| 	if (!canvas.value) { | ||||
| 		bitmapTmp = bitmap; | ||||
| 		bitmapTmp.value = bitmap; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// canvasがあれば描画する | ||||
| 	bitmapTmp = undefined; | ||||
| 	bitmapTmp.value = undefined; | ||||
| 	const ctx = canvas.value.getContext('2d'); | ||||
| 	if (!ctx) return; | ||||
| 	ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight); | ||||
| 	ctx.drawImage(bitmap, 0, 0, canvasWidth.value, canvasHeight.value); | ||||
| } | ||||
|  | ||||
| function drawAvg() { | ||||
| @@ -160,7 +159,7 @@ function drawAvg() { | ||||
| 	// avgColorでお茶をにごす | ||||
| 	ctx.beginPath(); | ||||
| 	ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888'; | ||||
| 	ctx.fillRect(0, 0, canvasWidth, canvasHeight); | ||||
| 	ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value); | ||||
| } | ||||
|  | ||||
| async function draw() { | ||||
| @@ -212,8 +211,8 @@ watch(() => props.hash, () => { | ||||
|  | ||||
| onMounted(() => { | ||||
| 	// drawImageがmountedより先に呼ばれている場合はここで描画する | ||||
| 	if (bitmapTmp) { | ||||
| 		drawImage(bitmapTmp); | ||||
| 	if (bitmapTmp.value) { | ||||
| 		drawImage(bitmapTmp.value); | ||||
| 	} | ||||
| 	waitForDecode(); | ||||
| }); | ||||
|   | ||||
| @@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkMiniChart from '@/components/MkMiniChart.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| @@ -24,12 +25,12 @@ const props = defineProps<{ | ||||
| 	instance: Misskey.entities.FederationInstance; | ||||
| }>(); | ||||
|  | ||||
| let chartValues = $ref<number[] | null>(null); | ||||
| const chartValues = ref<number[] | null>(null); | ||||
|  | ||||
| os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => { | ||||
| 	// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く | ||||
| 	res['requests.received'].splice(0, 1); | ||||
| 	chartValues = res['requests.received']; | ||||
| 	chartValues.value = res['requests.received']; | ||||
| }); | ||||
|  | ||||
| function getInstanceIcon(instance): string { | ||||
|   | ||||
| @@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref, shallowRef } from 'vue'; | ||||
| import { Chart } from 'chart.js'; | ||||
| import MkSelect from '@/components/MkSelect.vue'; | ||||
| import MkChart from '@/components/MkChart.vue'; | ||||
| @@ -100,11 +100,11 @@ import { initChart } from '@/scripts/init-chart.js'; | ||||
| initChart(); | ||||
|  | ||||
| const chartLimit = 500; | ||||
| let chartSpan = $ref<'hour' | 'day'>('hour'); | ||||
| let chartSrc = $ref('active-users'); | ||||
| let heatmapSrc = $ref('active-users'); | ||||
| let subDoughnutEl = $shallowRef<HTMLCanvasElement>(); | ||||
| let pubDoughnutEl = $shallowRef<HTMLCanvasElement>(); | ||||
| const chartSpan = ref<'hour' | 'day'>('hour'); | ||||
| const chartSrc = ref('active-users'); | ||||
| const heatmapSrc = ref('active-users'); | ||||
| const subDoughnutEl = shallowRef<HTMLCanvasElement>(); | ||||
| const pubDoughnutEl = shallowRef<HTMLCanvasElement>(); | ||||
|  | ||||
| const { handler: externalTooltipHandler1 } = useChartTooltip({ | ||||
| 	position: 'middle', | ||||
| @@ -163,7 +163,7 @@ function createDoughnut(chartEl, tooltip, data) { | ||||
|  | ||||
| onMounted(() => { | ||||
| 	os.apiGet('federation/stats', { limit: 30 }).then(fedStats => { | ||||
| 		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ | ||||
| 		createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ | ||||
| 			name: x.host, | ||||
| 			color: x.themeColor, | ||||
| 			value: x.followersCount, | ||||
| @@ -172,7 +172,7 @@ onMounted(() => { | ||||
| 			}, | ||||
| 		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }])); | ||||
|  | ||||
| 		createDoughnut(pubDoughnutEl, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({ | ||||
| 		createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({ | ||||
| 			name: x.host, | ||||
| 			color: x.themeColor, | ||||
| 			value: x.followingCount, | ||||
|   | ||||
| @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { computed } from 'vue'; | ||||
| import { instanceName } from '@/config.js'; | ||||
| import { instance as Instance } from '@/instance.js'; | ||||
| import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; | ||||
| @@ -30,7 +30,7 @@ const instance = props.instance ?? { | ||||
| 	themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content, | ||||
| }; | ||||
|  | ||||
| const faviconUrl = $computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico'); | ||||
| const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico'); | ||||
|  | ||||
| const themeColor = instance.themeColor ?? '#777777'; | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { shallowRef } from 'vue'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import { navbarItemDef } from '@/navbar.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop | ||||
| 	deviceKind === 'smartphone' ? 'drawer' : | ||||
| 	'dialog'; | ||||
|  | ||||
| const modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
|  | ||||
| const menu = defaultStore.state.menu; | ||||
|  | ||||
| @@ -63,7 +63,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => | ||||
| })); | ||||
|  | ||||
| function close() { | ||||
| 	modal.close(); | ||||
| 	modal.value.close(); | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { defineAsyncComponent } from 'vue'; | ||||
| import { defineAsyncComponent, ref } from 'vue'; | ||||
| import { url as local } from '@/config.js'; | ||||
| import { useTooltip } from '@/scripts/use-tooltip.js'; | ||||
| import * as os from '@/os.js'; | ||||
| @@ -29,13 +29,13 @@ const self = props.url.startsWith(local); | ||||
| const attr = self ? 'to' : 'href'; | ||||
| const target = self ? null : '_blank'; | ||||
|  | ||||
| const el = $ref(); | ||||
| const el = ref(); | ||||
|  | ||||
| useTooltip($$(el), (showing) => { | ||||
| useTooltip(el, (showing) => { | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { | ||||
| 		showing, | ||||
| 		url: props.url, | ||||
| 		source: el, | ||||
| 		source: el.value, | ||||
| 	}, {}, 'closed'); | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, shallowRef, watch } from 'vue'; | ||||
| import { onMounted, shallowRef, watch, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| @@ -42,7 +42,7 @@ const props = withDefaults(defineProps<{ | ||||
| }); | ||||
|  | ||||
| const audioEl = shallowRef<HTMLAudioElement>(); | ||||
| let hide = $ref(true); | ||||
| const hide = ref(true); | ||||
|  | ||||
| watch(audioEl, () => { | ||||
| 	if (audioEl.value) { | ||||
|   | ||||
| @@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { watch } from 'vue'; | ||||
| import { watch, ref, computed } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { getStaticImageUrl } from '@/scripts/media-proxy.js'; | ||||
| import bytes from '@/filters/bytes.js'; | ||||
| @@ -73,10 +73,10 @@ const props = withDefaults(defineProps<{ | ||||
| 	controls: true, | ||||
| }); | ||||
|  | ||||
| let hide = $ref(true); | ||||
| let darkMode: boolean = $ref(defaultStore.state.darkMode); | ||||
| const hide = ref(true); | ||||
| const darkMode = ref<boolean>(defaultStore.state.darkMode); | ||||
|  | ||||
| const url = $computed(() => (props.raw || defaultStore.state.loadRawImages) | ||||
| const url = computed(() => (props.raw || defaultStore.state.loadRawImages) | ||||
| 	? props.image.url | ||||
| 	: defaultStore.state.disableShowingAnimatedImages | ||||
| 		? getStaticImageUrl(props.image.url) | ||||
| @@ -87,14 +87,14 @@ function onclick() { | ||||
| 	if (!props.controls) { | ||||
| 		return; | ||||
| 	} | ||||
| 	if (hide) { | ||||
| 		hide = false; | ||||
| 	if (hide.value) { | ||||
| 		hide.value = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする | ||||
| watch(() => props.image, () => { | ||||
| 	hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore'); | ||||
| 	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore'); | ||||
| }, { | ||||
| 	deep: true, | ||||
| 	immediate: true, | ||||
| @@ -105,7 +105,7 @@ function showMenu(ev: MouseEvent) { | ||||
| 		text: i18n.ts.hide, | ||||
| 		icon: 'ti ti-eye-off', | ||||
| 		action: () => { | ||||
| 			hide = true; | ||||
| 			hide.value = true; | ||||
| 		}, | ||||
| 	}, ...(iAmModerator ? [{ | ||||
| 		text: i18n.ts.markAsSensitive, | ||||
| @@ -126,7 +126,7 @@ function showMenu(ev: MouseEvent) { | ||||
|  | ||||
| .sensitive { | ||||
| 	position: relative; | ||||
| 	 | ||||
|  | ||||
| 	&::after { | ||||
| 		content: ""; | ||||
| 		position: absolute; | ||||
|   | ||||
| @@ -63,7 +63,7 @@ async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLE | ||||
| </script> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, shallowRef } from 'vue'; | ||||
| import { computed, onMounted, onUnmounted, shallowRef } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import PhotoSwipeLightbox from 'photoswipe/lightbox'; | ||||
| import PhotoSwipe from 'photoswipe'; | ||||
| @@ -86,7 +86,7 @@ const container = shallowRef<HTMLElement | null | undefined>(undefined); | ||||
| const gallery = shallowRef<HTMLDivElement>(); | ||||
| const pswpZIndex = os.claimZIndex('middle'); | ||||
| document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); | ||||
| const count = $computed(() => props.mediaList.filter(media => previewable(media)).length); | ||||
| const count = computed(() => props.mediaList.filter(media => previewable(media)).length); | ||||
| let lightbox: PhotoSwipeLightbox | null; | ||||
|  | ||||
| const popstateHandler = (): void => { | ||||
|   | ||||
| @@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { Ref, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; | ||||
| import { Ref, computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; | ||||
| import { focusPrev, focusNext } from '@/scripts/focus.js'; | ||||
| import MkSwitchButton from '@/components/MkSwitch.button.vue'; | ||||
| import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu'; | ||||
| @@ -90,19 +90,19 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'hide'): void; | ||||
| }>(); | ||||
|  | ||||
| let itemsEl = $shallowRef<HTMLDivElement>(); | ||||
| const itemsEl = shallowRef<HTMLDivElement>(); | ||||
|  | ||||
| let items2: InnerMenuItem[] = $ref([]); | ||||
| const items2 = ref<InnerMenuItem[]>([]); | ||||
|  | ||||
| let child = $shallowRef<InstanceType<typeof XChild>>(); | ||||
| const child = shallowRef<InstanceType<typeof XChild>>(); | ||||
|  | ||||
| let keymap = $computed(() => ({ | ||||
| const keymap = computed(() => ({ | ||||
| 	'up|k|shift+tab': focusUp, | ||||
| 	'down|j|tab': focusDown, | ||||
| 	'esc': close, | ||||
| })); | ||||
|  | ||||
| let childShowingItem = $ref<MenuItem | null>(); | ||||
| const childShowingItem = ref<MenuItem | null>(); | ||||
|  | ||||
| let preferClick = isTouchUsing || props.asDrawer; | ||||
|  | ||||
| @@ -115,22 +115,22 @@ watch(() => props.items, () => { | ||||
| 		if (item && 'then' in item) { // if item is Promise | ||||
| 			items[i] = { type: 'pending' }; | ||||
| 			item.then(actualItem => { | ||||
| 				items2[i] = actualItem; | ||||
| 				items2.value[i] = actualItem; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	items2 = items as InnerMenuItem[]; | ||||
| 	items2.value = items as InnerMenuItem[]; | ||||
| }, { | ||||
| 	immediate: true, | ||||
| }); | ||||
|  | ||||
| const childMenu = ref<MenuItem[] | null>(); | ||||
| let childTarget = $shallowRef<HTMLElement | null>(); | ||||
| const childTarget = shallowRef<HTMLElement | null>(); | ||||
|  | ||||
| function closeChild() { | ||||
| 	childMenu.value = null; | ||||
| 	childShowingItem = null; | ||||
| 	childShowingItem.value = null; | ||||
| } | ||||
|  | ||||
| function childActioned() { | ||||
| @@ -139,8 +139,8 @@ function childActioned() { | ||||
| } | ||||
|  | ||||
| const onGlobalMousedown = (event: MouseEvent) => { | ||||
| 	if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return; | ||||
| 	if (child && child.checkHit(event)) return; | ||||
| 	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return; | ||||
| 	if (child.value && child.value.checkHit(event)) return; | ||||
| 	closeChild(); | ||||
| }; | ||||
|  | ||||
| @@ -177,10 +177,10 @@ async function showChildren(item: MenuParent, ev: MouseEvent) { | ||||
| 		}); | ||||
| 		emit('hide'); | ||||
| 	} else { | ||||
| 		childTarget = ev.currentTarget ?? ev.target; | ||||
| 		childTarget.value = ev.currentTarget ?? ev.target; | ||||
| 		// これでもリアクティビティは保たれる | ||||
| 		childMenu.value = children; | ||||
| 		childShowingItem = item; | ||||
| 		childShowingItem.value = item; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -209,7 +209,7 @@ function switchItem(item: MenuSwitch & { ref: any }) { | ||||
| onMounted(() => { | ||||
| 	if (props.viaKeyboard) { | ||||
| 		nextTick(() => { | ||||
| 			if (itemsEl) focusNext(itemsEl.children[0], true, false); | ||||
| 			if (itemsEl.value) focusNext(itemsEl.value.children[0], true, false); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { watch } from 'vue'; | ||||
| import { watch, ref } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| import { useInterval } from '@/scripts/use-interval.js'; | ||||
| @@ -43,11 +43,11 @@ const props = defineProps<{ | ||||
| const viewBoxX = 50; | ||||
| const viewBoxY = 50; | ||||
| const gradientId = uuid(); | ||||
| let polylinePoints = $ref(''); | ||||
| let polygonPoints = $ref(''); | ||||
| let headX = $ref<number | null>(null); | ||||
| let headY = $ref<number | null>(null); | ||||
| let clock = $ref<number | null>(null); | ||||
| const polylinePoints = ref(''); | ||||
| const polygonPoints = ref(''); | ||||
| const headX = ref<number | null>(null); | ||||
| const headY = ref<number | null>(null); | ||||
| const clock = ref<number | null>(null); | ||||
| const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent')); | ||||
| const color = accent.toRgbString(); | ||||
|  | ||||
| @@ -60,12 +60,12 @@ function draw(): void { | ||||
| 		(1 - (n / peak)) * viewBoxY, | ||||
| 	]); | ||||
|  | ||||
| 	polylinePoints = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
| 	polylinePoints.value = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
|  | ||||
| 	polygonPoints = `0,${ viewBoxY } ${ polylinePoints } ${ viewBoxX },${ viewBoxY }`; | ||||
| 	polygonPoints.value = `0,${ viewBoxY } ${ polylinePoints.value } ${ viewBoxX },${ viewBoxY }`; | ||||
|  | ||||
| 	headX = _polylinePoints.at(-1)![0]; | ||||
| 	headY = _polylinePoints.at(-1)![1]; | ||||
| 	headX.value = _polylinePoints.at(-1)![0]; | ||||
| 	headY.value = _polylinePoints.at(-1)![1]; | ||||
| } | ||||
|  | ||||
| watch(() => props.src, draw, { immediate: true }); | ||||
|   | ||||
| @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue'; | ||||
| import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { isTouchUsing } from '@/scripts/touch.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -89,14 +89,14 @@ const emit = defineEmits<{ | ||||
|  | ||||
| provide('modal', true); | ||||
|  | ||||
| let maxHeight = $ref<number>(); | ||||
| let fixed = $ref(false); | ||||
| let transformOrigin = $ref('center'); | ||||
| let showing = $ref(true); | ||||
| let content = $shallowRef<HTMLElement>(); | ||||
| const maxHeight = ref<number>(); | ||||
| const fixed = ref(false); | ||||
| const transformOrigin = ref('center'); | ||||
| const showing = ref(true); | ||||
| const content = shallowRef<HTMLElement>(); | ||||
| const zIndex = os.claimZIndex(props.zPriority); | ||||
| let useSendAnime = $ref(false); | ||||
| const type = $computed<ModalTypes>(() => { | ||||
| const useSendAnime = ref(false); | ||||
| const type = computed<ModalTypes>(() => { | ||||
| 	if (props.preferType === 'auto') { | ||||
| 		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') { | ||||
| 			return 'drawer'; | ||||
| @@ -107,26 +107,26 @@ const type = $computed<ModalTypes>(() => { | ||||
| 		return props.preferType!; | ||||
| 	} | ||||
| }); | ||||
| const isEnableBgTransparent = $computed(() => props.transparentBg && (type === 'popup')); | ||||
| let transitionName = $computed((() => | ||||
| const isEnableBgTransparent = computed(() => props.transparentBg && (type.value === 'popup')); | ||||
| const transitionName = computed((() => | ||||
| 	defaultStore.state.animation | ||||
| 		? useSendAnime | ||||
| 		? useSendAnime.value | ||||
| 			? 'send' | ||||
| 			: type === 'drawer' | ||||
| 			: type.value === 'drawer' | ||||
| 				? 'modal-drawer' | ||||
| 				: type === 'popup' | ||||
| 				: type.value === 'popup' | ||||
| 					? 'modal-popup' | ||||
| 					: 'modal' | ||||
| 		: '' | ||||
| )); | ||||
| let transitionDuration = $computed((() => | ||||
| 	transitionName === 'send' | ||||
| const transitionDuration = computed((() => | ||||
| 	transitionName.value === 'send' | ||||
| 		? 400 | ||||
| 		: transitionName === 'modal-popup' | ||||
| 		: transitionName.value === 'modal-popup' | ||||
| 			? 100 | ||||
| 			: transitionName === 'modal' | ||||
| 			: transitionName.value === 'modal' | ||||
| 				? 200 | ||||
| 				: transitionName === 'modal-drawer' | ||||
| 				: transitionName.value === 'modal-drawer' | ||||
| 					? 200 | ||||
| 					: 0 | ||||
| )); | ||||
| @@ -135,12 +135,12 @@ let contentClicking = false; | ||||
|  | ||||
| function close(opts: { useSendAnimation?: boolean } = {}) { | ||||
| 	if (opts.useSendAnimation) { | ||||
| 		useSendAnime = true; | ||||
| 		useSendAnime.value = true; | ||||
| 	} | ||||
|  | ||||
| 	// eslint-disable-next-line vue/no-mutating-props | ||||
| 	if (props.src) props.src.style.pointerEvents = 'auto'; | ||||
| 	showing = false; | ||||
| 	showing.value = false; | ||||
| 	emit('close'); | ||||
| } | ||||
|  | ||||
| @@ -149,8 +149,8 @@ function onBgClick() { | ||||
| 	emit('click'); | ||||
| } | ||||
|  | ||||
| if (type === 'drawer') { | ||||
| 	maxHeight = window.innerHeight / 1.5; | ||||
| if (type.value === 'drawer') { | ||||
| 	maxHeight.value = window.innerHeight / 1.5; | ||||
| } | ||||
|  | ||||
| const keymap = { | ||||
| @@ -162,21 +162,21 @@ const SCROLLBAR_THICKNESS = 16; | ||||
|  | ||||
| const align = () => { | ||||
| 	if (props.src == null) return; | ||||
| 	if (type === 'drawer') return; | ||||
| 	if (type === 'dialog') return; | ||||
| 	if (type.value === 'drawer') return; | ||||
| 	if (type.value === 'dialog') return; | ||||
|  | ||||
| 	if (content == null) return; | ||||
| 	if (content.value == null) return; | ||||
|  | ||||
| 	const srcRect = props.src.getBoundingClientRect(); | ||||
|  | ||||
| 	const width = content!.offsetWidth; | ||||
| 	const height = content!.offsetHeight; | ||||
| 	const width = content.value!.offsetWidth; | ||||
| 	const height = content.value!.offsetHeight; | ||||
|  | ||||
| 	let left; | ||||
| 	let top; | ||||
|  | ||||
| 	const x = srcRect.left + (fixed ? 0 : window.pageXOffset); | ||||
| 	const y = srcRect.top + (fixed ? 0 : window.pageYOffset); | ||||
| 	const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset); | ||||
| 	const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset); | ||||
|  | ||||
| 	if (props.anchor.x === 'center') { | ||||
| 		left = x + (props.src.offsetWidth / 2) - (width / 2); | ||||
| @@ -194,7 +194,7 @@ const align = () => { | ||||
| 		top = y + props.src.offsetHeight; | ||||
| 	} | ||||
|  | ||||
| 	if (fixed) { | ||||
| 	if (fixed.value) { | ||||
| 		// 画面から横にはみ出る場合 | ||||
| 		if (left + width > (window.innerWidth - SCROLLBAR_THICKNESS)) { | ||||
| 			left = (window.innerWidth - SCROLLBAR_THICKNESS) - width; | ||||
| @@ -207,16 +207,16 @@ const align = () => { | ||||
| 		if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) { | ||||
| 			if (props.noOverlap && props.anchor.x === 'center') { | ||||
| 				if (underSpace >= (upperSpace / 3)) { | ||||
| 					maxHeight = underSpace; | ||||
| 					maxHeight.value = underSpace; | ||||
| 				} else { | ||||
| 					maxHeight = upperSpace; | ||||
| 					maxHeight.value = upperSpace; | ||||
| 					top = (upperSpace + MARGIN) - height; | ||||
| 				} | ||||
| 			} else { | ||||
| 				top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height; | ||||
| 			} | ||||
| 		} else { | ||||
| 			maxHeight = underSpace; | ||||
| 			maxHeight.value = underSpace; | ||||
| 		} | ||||
| 	} else { | ||||
| 		// 画面から横にはみ出る場合 | ||||
| @@ -231,16 +231,16 @@ const align = () => { | ||||
| 		if (top + height - window.pageYOffset > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) { | ||||
| 			if (props.noOverlap && props.anchor.x === 'center') { | ||||
| 				if (underSpace >= (upperSpace / 3)) { | ||||
| 					maxHeight = underSpace; | ||||
| 					maxHeight.value = underSpace; | ||||
| 				} else { | ||||
| 					maxHeight = upperSpace; | ||||
| 					maxHeight.value = upperSpace; | ||||
| 					top = window.pageYOffset + ((upperSpace + MARGIN) - height); | ||||
| 				} | ||||
| 			} else { | ||||
| 				top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.pageYOffset - 1; | ||||
| 			} | ||||
| 		} else { | ||||
| 			maxHeight = underSpace; | ||||
| 			maxHeight.value = underSpace; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -255,29 +255,29 @@ const align = () => { | ||||
| 	let transformOriginX = 'center'; | ||||
| 	let transformOriginY = 'center'; | ||||
|  | ||||
| 	if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) { | ||||
| 	if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) { | ||||
| 		transformOriginY = 'top'; | ||||
| 	} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) { | ||||
| 	} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) { | ||||
| 		transformOriginY = 'bottom'; | ||||
| 	} | ||||
|  | ||||
| 	if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) { | ||||
| 	if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) { | ||||
| 		transformOriginX = 'left'; | ||||
| 	} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) { | ||||
| 	} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) { | ||||
| 		transformOriginX = 'right'; | ||||
| 	} | ||||
|  | ||||
| 	transformOrigin = `${transformOriginX} ${transformOriginY}`; | ||||
| 	transformOrigin.value = `${transformOriginX} ${transformOriginY}`; | ||||
|  | ||||
| 	content.style.left = left + 'px'; | ||||
| 	content.style.top = top + 'px'; | ||||
| 	content.value.style.left = left + 'px'; | ||||
| 	content.value.style.top = top + 'px'; | ||||
| }; | ||||
|  | ||||
| const onOpened = () => { | ||||
| 	emit('opened'); | ||||
|  | ||||
| 	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する | ||||
| 	const el = content!.children[0]; | ||||
| 	const el = content.value!.children[0]; | ||||
| 	el.addEventListener('mousedown', ev => { | ||||
| 		contentClicking = true; | ||||
| 		window.addEventListener('mouseup', ev => { | ||||
| @@ -299,7 +299,7 @@ onMounted(() => { | ||||
| 			// eslint-disable-next-line vue/no-mutating-props | ||||
| 			props.src.style.pointerEvents = 'none'; | ||||
| 		} | ||||
| 		fixed = (type === 'drawer') || (getFixedContainer(props.src) != null); | ||||
| 		fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); | ||||
|  | ||||
| 		await nextTick(); | ||||
|  | ||||
| @@ -307,7 +307,7 @@ onMounted(() => { | ||||
| 	}, { immediate: true }); | ||||
|  | ||||
| 	nextTick(() => { | ||||
| 		alignObserver.observe(content!); | ||||
| 		alignObserver.observe(content.value!); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted } from 'vue'; | ||||
| import { onMounted, onUnmounted, shallowRef, ref } from 'vue'; | ||||
| import MkModal from './MkModal.vue'; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| @@ -44,14 +44,14 @@ const emit = defineEmits<{ | ||||
| 	(event: 'ok'): void; | ||||
| }>(); | ||||
|  | ||||
| let modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| let rootEl = $shallowRef<HTMLElement>(); | ||||
| let headerEl = $shallowRef<HTMLElement>(); | ||||
| let bodyWidth = $ref(0); | ||||
| let bodyHeight = $ref(0); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const rootEl = shallowRef<HTMLElement>(); | ||||
| const headerEl = shallowRef<HTMLElement>(); | ||||
| const bodyWidth = ref(0); | ||||
| const bodyHeight = ref(0); | ||||
|  | ||||
| const close = () => { | ||||
| 	modal.close(); | ||||
| 	modal.value.close(); | ||||
| }; | ||||
|  | ||||
| const onBgClick = () => { | ||||
| @@ -67,14 +67,14 @@ const onKeydown = (evt) => { | ||||
| }; | ||||
|  | ||||
| const ro = new ResizeObserver((entries, observer) => { | ||||
| 	bodyWidth = rootEl.offsetWidth; | ||||
| 	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; | ||||
| 	bodyWidth.value = rootEl.value.offsetWidth; | ||||
| 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | ||||
| }); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	bodyWidth = rootEl.offsetWidth; | ||||
| 	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; | ||||
| 	ro.observe(rootEl); | ||||
| 	bodyWidth.value = rootEl.value.offsetWidth; | ||||
| 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight; | ||||
| 	ro.observe(rootEl.value); | ||||
| }); | ||||
|  | ||||
| onUnmounted(() => { | ||||
|   | ||||
| @@ -205,12 +205,12 @@ const emit = defineEmits<{ | ||||
| const inChannel = inject('inChannel', null); | ||||
| const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null); | ||||
|  | ||||
| let note = $ref(deepClone(props.note)); | ||||
| const note = ref(deepClone(props.note)); | ||||
|  | ||||
| // plugin | ||||
| if (noteViewInterruptors.length > 0) { | ||||
| 	onMounted(async () => { | ||||
| 		let result: Misskey.entities.Note | null = deepClone(note); | ||||
| 		let result: Misskey.entities.Note | null = deepClone(note.value); | ||||
| 		for (const interruptor of noteViewInterruptors) { | ||||
| 			try { | ||||
| 				result = await interruptor.handler(result); | ||||
| @@ -222,15 +222,15 @@ if (noteViewInterruptors.length > 0) { | ||||
| 				console.error(err); | ||||
| 			} | ||||
| 		} | ||||
| 		note = result; | ||||
| 		note.value = result; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| const isRenote = ( | ||||
| 	note.renote != null && | ||||
| 	note.text == null && | ||||
| 	note.fileIds.length === 0 && | ||||
| 	note.poll == null | ||||
| 	note.value.renote != null && | ||||
| 	note.value.text == null && | ||||
| 	note.value.fileIds.length === 0 && | ||||
| 	note.value.poll == null | ||||
| ); | ||||
|  | ||||
| const el = shallowRef<HTMLElement>(); | ||||
| @@ -239,21 +239,21 @@ const renoteButton = shallowRef<HTMLElement>(); | ||||
| const renoteTime = shallowRef<HTMLElement>(); | ||||
| const reactButton = shallowRef<HTMLElement>(); | ||||
| const clipButton = shallowRef<HTMLElement>(); | ||||
| let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note); | ||||
| const isMyRenote = $i && ($i.id === note.userId); | ||||
| const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); | ||||
| const isMyRenote = $i && ($i.id === note.value.userId); | ||||
| const showContent = ref(false); | ||||
| const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null); | ||||
| const urls = $computed(() => parsed ? extractUrlFromMfm(parsed) : null); | ||||
| const isLong = shouldCollapsed(appearNote, urls ?? []); | ||||
| const collapsed = ref(appearNote.cw == null && isLong); | ||||
| const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); | ||||
| const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null); | ||||
| const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); | ||||
| const collapsed = ref(appearNote.value.cw == null && isLong); | ||||
| const isDeleted = ref(false); | ||||
| const muted = ref(checkMute(appearNote, $i?.mutedWords)); | ||||
| const hardMuted = ref(props.withHardMute && checkMute(appearNote, $i?.hardMutedWords)); | ||||
| const muted = ref(checkMute(appearNote.value, $i?.mutedWords)); | ||||
| const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords)); | ||||
| const translation = ref<any>(null); | ||||
| const translating = ref(false); | ||||
| const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); | ||||
| const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id)); | ||||
| let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null))); | ||||
| 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 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 { | ||||
| 	if (mutedWords == null) return false; | ||||
| @@ -277,20 +277,20 @@ const keymap = { | ||||
|  | ||||
| provide('react', (reaction: string) => { | ||||
| 	os.api('notes/reactions/create', { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 		reaction: reaction, | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| if (props.mock) { | ||||
| 	watch(() => props.note, (to) => { | ||||
| 		note = deepClone(to); | ||||
| 		note.value = deepClone(to); | ||||
| 	}, { deep: true }); | ||||
| } else { | ||||
| 	useNoteCapture({ | ||||
| 		rootEl: el, | ||||
| 		note: $$(appearNote), | ||||
| 		pureNote: $$(note), | ||||
| 		note: appearNote, | ||||
| 		pureNote: note, | ||||
| 		isDeletedRef: isDeleted, | ||||
| 	}); | ||||
| } | ||||
| @@ -298,7 +298,7 @@ if (props.mock) { | ||||
| if (!props.mock) { | ||||
| 	useTooltip(renoteButton, async (showing) => { | ||||
| 		const renotes = await os.api('notes/renotes', { | ||||
| 			noteId: appearNote.id, | ||||
| 			noteId: appearNote.value.id, | ||||
| 			limit: 11, | ||||
| 		}); | ||||
|  | ||||
| @@ -309,7 +309,7 @@ if (!props.mock) { | ||||
| 		os.popup(MkUsersTooltip, { | ||||
| 			showing, | ||||
| 			users, | ||||
| 			count: appearNote.renoteCount, | ||||
| 			count: appearNote.value.renoteCount, | ||||
| 			targetElement: renoteButton.value, | ||||
| 		}, {}, 'closed'); | ||||
| 	}); | ||||
| @@ -319,7 +319,7 @@ function renote(viaKeyboard = false) { | ||||
| 	pleaseLogin(); | ||||
| 	showMovedDialog(); | ||||
|  | ||||
| 	const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock }); | ||||
| 	const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock }); | ||||
| 	os.popupMenu(menu, renoteButton.value, { | ||||
| 		viaKeyboard, | ||||
| 	}); | ||||
| @@ -331,8 +331,8 @@ function reply(viaKeyboard = false): void { | ||||
| 		return; | ||||
| 	} | ||||
| 	os.post({ | ||||
| 		reply: appearNote, | ||||
| 		channel: appearNote.channel, | ||||
| 		reply: appearNote.value, | ||||
| 		channel: appearNote.value.channel, | ||||
| 		animation: !viaKeyboard, | ||||
| 	}, () => { | ||||
| 		focus(); | ||||
| @@ -342,7 +342,7 @@ function reply(viaKeyboard = false): void { | ||||
| function react(viaKeyboard = false): void { | ||||
| 	pleaseLogin(); | ||||
| 	showMovedDialog(); | ||||
| 	if (appearNote.reactionAcceptance === 'likeOnly') { | ||||
| 	if (appearNote.value.reactionAcceptance === 'likeOnly') { | ||||
| 		sound.play('reaction'); | ||||
|  | ||||
| 		if (props.mock) { | ||||
| @@ -350,7 +350,7 @@ function react(viaKeyboard = false): void { | ||||
| 		} | ||||
|  | ||||
| 		os.api('notes/reactions/create', { | ||||
| 			noteId: appearNote.id, | ||||
| 			noteId: appearNote.value.id, | ||||
| 			reaction: '❤️', | ||||
| 		}); | ||||
| 		const el = reactButton.value as HTMLElement | null | undefined; | ||||
| @@ -371,10 +371,10 @@ function react(viaKeyboard = false): void { | ||||
| 			} | ||||
|  | ||||
| 			os.api('notes/reactions/create', { | ||||
| 				noteId: appearNote.id, | ||||
| 				noteId: appearNote.value.id, | ||||
| 				reaction: reaction, | ||||
| 			}); | ||||
| 			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) { | ||||
| 			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) { | ||||
| 				claimAchievement('reactWithoutRead'); | ||||
| 			} | ||||
| 		}, () => { | ||||
| @@ -417,7 +417,7 @@ function onContextmenu(ev: MouseEvent): void { | ||||
| 		ev.preventDefault(); | ||||
| 		react(); | ||||
| 	} else { | ||||
| 		const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | ||||
| 		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | ||||
| 		os.contextMenu(menu, ev).then(focus).finally(cleanup); | ||||
| 	} | ||||
| } | ||||
| @@ -427,7 +427,7 @@ function menu(viaKeyboard = false): void { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | ||||
| 	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); | ||||
| 	os.popupMenu(menu, menuButton.value, { | ||||
| 		viaKeyboard, | ||||
| 	}).then(focus).finally(cleanup); | ||||
| @@ -438,7 +438,7 @@ async function clip() { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus); | ||||
| 	os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus); | ||||
| } | ||||
|  | ||||
| function showRenoteMenu(viaKeyboard = false): void { | ||||
| @@ -453,7 +453,7 @@ function showRenoteMenu(viaKeyboard = false): void { | ||||
| 			danger: true, | ||||
| 			action: () => { | ||||
| 				os.api('notes/delete', { | ||||
| 					noteId: note.id, | ||||
| 					noteId: note.value.id, | ||||
| 				}); | ||||
| 				isDeleted.value = true; | ||||
| 			}, | ||||
| @@ -463,7 +463,7 @@ function showRenoteMenu(viaKeyboard = false): void { | ||||
| 	if (isMyRenote) { | ||||
| 		pleaseLogin(); | ||||
| 		os.popupMenu([ | ||||
| 			getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), | ||||
| 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), | ||||
| 			null, | ||||
| 			getUnrenote(), | ||||
| 		], renoteTime.value, { | ||||
| @@ -471,9 +471,9 @@ function showRenoteMenu(viaKeyboard = false): void { | ||||
| 		}); | ||||
| 	} else { | ||||
| 		os.popupMenu([ | ||||
| 			getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), | ||||
| 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), | ||||
| 			null, | ||||
| 			getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote), | ||||
| 			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), | ||||
| 			$i.isModerator || $i.isAdmin ? getUnrenote() : undefined, | ||||
| 		], renoteTime.value, { | ||||
| 			viaKeyboard: viaKeyboard, | ||||
| @@ -499,7 +499,7 @@ function focusAfter() { | ||||
|  | ||||
| function readPromo() { | ||||
| 	os.api('promo/read', { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 	}); | ||||
| 	isDeleted.value = true; | ||||
| } | ||||
|   | ||||
| @@ -235,12 +235,12 @@ const props = defineProps<{ | ||||
|  | ||||
| const inChannel = inject('inChannel', null); | ||||
|  | ||||
| let note = $ref(deepClone(props.note)); | ||||
| const note = ref(deepClone(props.note)); | ||||
|  | ||||
| // plugin | ||||
| if (noteViewInterruptors.length > 0) { | ||||
| 	onMounted(async () => { | ||||
| 		let result: Misskey.entities.Note | null = deepClone(note); | ||||
| 		let result: Misskey.entities.Note | null = deepClone(note.value); | ||||
| 		for (const interruptor of noteViewInterruptors) { | ||||
| 			try { | ||||
| 				result = await interruptor.handler(result); | ||||
| @@ -252,15 +252,15 @@ if (noteViewInterruptors.length > 0) { | ||||
| 				console.error(err); | ||||
| 			} | ||||
| 		} | ||||
| 		note = result; | ||||
| 		note.value = result; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| const isRenote = ( | ||||
| 	note.renote != null && | ||||
| 	note.text == null && | ||||
| 	note.fileIds.length === 0 && | ||||
| 	note.poll == null | ||||
| 	note.value.renote != null && | ||||
| 	note.value.text == null && | ||||
| 	note.value.fileIds.length === 0 && | ||||
| 	note.value.poll == null | ||||
| ); | ||||
|  | ||||
| const el = shallowRef<HTMLElement>(); | ||||
| @@ -269,19 +269,19 @@ const renoteButton = shallowRef<HTMLElement>(); | ||||
| const renoteTime = shallowRef<HTMLElement>(); | ||||
| const reactButton = shallowRef<HTMLElement>(); | ||||
| const clipButton = shallowRef<HTMLElement>(); | ||||
| let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note); | ||||
| const isMyRenote = $i && ($i.id === note.userId); | ||||
| const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); | ||||
| const isMyRenote = $i && ($i.id === note.value.userId); | ||||
| const showContent = ref(false); | ||||
| const isDeleted = ref(false); | ||||
| const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false); | ||||
| const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false); | ||||
| const translation = ref(null); | ||||
| const translating = ref(false); | ||||
| const parsed = appearNote.text ? mfm.parse(appearNote.text) : null; | ||||
| const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; | ||||
| const urls = parsed ? extractUrlFromMfm(parsed) : null; | ||||
| const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); | ||||
| const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); | ||||
| const conversation = ref<Misskey.entities.Note[]>([]); | ||||
| const replies = ref<Misskey.entities.Note[]>([]); | ||||
| const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id); | ||||
| const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i.id); | ||||
|  | ||||
| const keymap = { | ||||
| 	'r': () => reply(true), | ||||
| @@ -294,41 +294,41 @@ const keymap = { | ||||
|  | ||||
| provide('react', (reaction: string) => { | ||||
| 	os.api('notes/reactions/create', { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 		reaction: reaction, | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| let tab = $ref('replies'); | ||||
| let reactionTabType = $ref(null); | ||||
| const tab = ref('replies'); | ||||
| const reactionTabType = ref(null); | ||||
|  | ||||
| const renotesPagination = $computed(() => ({ | ||||
| const renotesPagination = computed(() => ({ | ||||
| 	endpoint: 'notes/renotes', | ||||
| 	limit: 10, | ||||
| 	params: { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 	}, | ||||
| })); | ||||
|  | ||||
| const reactionsPagination = $computed(() => ({ | ||||
| const reactionsPagination = computed(() => ({ | ||||
| 	endpoint: 'notes/reactions', | ||||
| 	limit: 10, | ||||
| 	params: { | ||||
| 		noteId: appearNote.id, | ||||
| 		type: reactionTabType, | ||||
| 		noteId: appearNote.value.id, | ||||
| 		type: reactionTabType.value, | ||||
| 	}, | ||||
| })); | ||||
|  | ||||
| useNoteCapture({ | ||||
| 	rootEl: el, | ||||
| 	note: $$(appearNote), | ||||
| 	pureNote: $$(note), | ||||
| 	note: appearNote, | ||||
| 	pureNote: note, | ||||
| 	isDeletedRef: isDeleted, | ||||
| }); | ||||
|  | ||||
| useTooltip(renoteButton, async (showing) => { | ||||
| 	const renotes = await os.api('notes/renotes', { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 		limit: 11, | ||||
| 	}); | ||||
|  | ||||
| @@ -339,7 +339,7 @@ useTooltip(renoteButton, async (showing) => { | ||||
| 	os.popup(MkUsersTooltip, { | ||||
| 		showing, | ||||
| 		users, | ||||
| 		count: appearNote.renoteCount, | ||||
| 		count: appearNote.value.renoteCount, | ||||
| 		targetElement: renoteButton.value, | ||||
| 	}, {}, 'closed'); | ||||
| }); | ||||
| @@ -348,7 +348,7 @@ function renote(viaKeyboard = false) { | ||||
| 	pleaseLogin(); | ||||
| 	showMovedDialog(); | ||||
|  | ||||
| 	const { menu } = getRenoteMenu({ note: note, renoteButton }); | ||||
| 	const { menu } = getRenoteMenu({ note: note.value, renoteButton }); | ||||
| 	os.popupMenu(menu, renoteButton.value, { | ||||
| 		viaKeyboard, | ||||
| 	}); | ||||
| @@ -358,8 +358,8 @@ function reply(viaKeyboard = false): void { | ||||
| 	pleaseLogin(); | ||||
| 	showMovedDialog(); | ||||
| 	os.post({ | ||||
| 		reply: appearNote, | ||||
| 		channel: appearNote.channel, | ||||
| 		reply: appearNote.value, | ||||
| 		channel: appearNote.value.channel, | ||||
| 		animation: !viaKeyboard, | ||||
| 	}, () => { | ||||
| 		focus(); | ||||
| @@ -369,11 +369,11 @@ function reply(viaKeyboard = false): void { | ||||
| function react(viaKeyboard = false): void { | ||||
| 	pleaseLogin(); | ||||
| 	showMovedDialog(); | ||||
| 	if (appearNote.reactionAcceptance === 'likeOnly') { | ||||
| 	if (appearNote.value.reactionAcceptance === 'likeOnly') { | ||||
| 		sound.play('reaction'); | ||||
|  | ||||
| 		os.api('notes/reactions/create', { | ||||
| 			noteId: appearNote.id, | ||||
| 			noteId: appearNote.value.id, | ||||
| 			reaction: '❤️', | ||||
| 		}); | ||||
| 		const el = reactButton.value as HTMLElement | null | undefined; | ||||
| @@ -389,10 +389,10 @@ function react(viaKeyboard = false): void { | ||||
| 			sound.play('reaction'); | ||||
|  | ||||
| 			os.api('notes/reactions/create', { | ||||
| 				noteId: appearNote.id, | ||||
| 				noteId: appearNote.value.id, | ||||
| 				reaction: reaction, | ||||
| 			}); | ||||
| 			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) { | ||||
| 			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) { | ||||
| 				claimAchievement('reactWithoutRead'); | ||||
| 			} | ||||
| 		}, () => { | ||||
| @@ -423,20 +423,20 @@ function onContextmenu(ev: MouseEvent): void { | ||||
| 		ev.preventDefault(); | ||||
| 		react(); | ||||
| 	} else { | ||||
| 		const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }); | ||||
| 		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted }); | ||||
| 		os.contextMenu(menu, ev).then(focus).finally(cleanup); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function menu(viaKeyboard = false): void { | ||||
| 	const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }); | ||||
| 	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted }); | ||||
| 	os.popupMenu(menu, menuButton.value, { | ||||
| 		viaKeyboard, | ||||
| 	}).then(focus).finally(cleanup); | ||||
| } | ||||
|  | ||||
| async function clip() { | ||||
| 	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted }), clipButton.value).then(focus); | ||||
| 	os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted }), clipButton.value).then(focus); | ||||
| } | ||||
|  | ||||
| function showRenoteMenu(viaKeyboard = false): void { | ||||
| @@ -448,7 +448,7 @@ function showRenoteMenu(viaKeyboard = false): void { | ||||
| 		danger: true, | ||||
| 		action: () => { | ||||
| 			os.api('notes/delete', { | ||||
| 				noteId: note.id, | ||||
| 				noteId: note.value.id, | ||||
| 			}); | ||||
| 			isDeleted.value = true; | ||||
| 		}, | ||||
| @@ -470,7 +470,7 @@ const repliesLoaded = ref(false); | ||||
| function loadReplies() { | ||||
| 	repliesLoaded.value = true; | ||||
| 	os.api('notes/children', { | ||||
| 		noteId: appearNote.id, | ||||
| 		noteId: appearNote.value.id, | ||||
| 		limit: 30, | ||||
| 	}).then(res => { | ||||
| 		replies.value = res; | ||||
| @@ -482,7 +482,7 @@ const conversationLoaded = ref(false); | ||||
| function loadConversation() { | ||||
| 	conversationLoaded.value = true; | ||||
| 	os.api('notes/conversation', { | ||||
| 		noteId: appearNote.replyId, | ||||
| 		noteId: appearNote.value.replyId, | ||||
| 	}).then(res => { | ||||
| 		conversation.value = res.reverse(); | ||||
| 	}); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkNoteHeader from '@/components/MkNoteHeader.vue'; | ||||
| import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; | ||||
| @@ -33,7 +33,7 @@ const props = defineProps<{ | ||||
| 	note: Misskey.entities.Note; | ||||
| }>(); | ||||
|  | ||||
| const showContent = $ref(false); | ||||
| const showContent = ref(false); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
|   | ||||
| @@ -65,15 +65,15 @@ const props = withDefaults(defineProps<{ | ||||
|  | ||||
| const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false); | ||||
|  | ||||
| let showContent = $ref(false); | ||||
| let replies: Misskey.entities.Note[] = $ref([]); | ||||
| const showContent = ref(false); | ||||
| const replies = ref<Misskey.entities.Note[]>([]); | ||||
|  | ||||
| if (props.detail) { | ||||
| 	os.api('notes/children', { | ||||
| 		noteId: props.note.id, | ||||
| 		limit: 5, | ||||
| 	}).then(res => { | ||||
| 		replies = res; | ||||
| 		replies.value = res; | ||||
| 	}); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref, Ref } from 'vue'; | ||||
| import { ref, Ref, shallowRef } from 'vue'; | ||||
| import MkSwitch from './MkSwitch.vue'; | ||||
| import MkInfo from './MkInfo.vue'; | ||||
| import MkButton from './MkButton.vue'; | ||||
| @@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{ | ||||
| 	excludeTypes: () => [], | ||||
| }); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any); | ||||
|  | ||||
| @@ -61,7 +61,7 @@ function ok() { | ||||
| 			.filter(type => !typesMap[type].value), | ||||
| 	}); | ||||
|  | ||||
| 	if (dialog) dialog.close(); | ||||
| 	if (dialog.value) dialog.value.close(); | ||||
| } | ||||
|  | ||||
| function disableAll() { | ||||
|   | ||||
| @@ -43,7 +43,7 @@ const props = defineProps<{ | ||||
|  | ||||
| const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>(); | ||||
|  | ||||
| let pagination = $computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? { | ||||
| const pagination = computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? { | ||||
| 	endpoint: 'i/notifications-grouped' as const, | ||||
| 	limit: 20, | ||||
| 	params: computed(() => ({ | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted } from 'vue'; | ||||
| import { onMounted, onUnmounted, shallowRef, ref } from 'vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| @@ -22,13 +22,13 @@ const props = withDefaults(defineProps<{ | ||||
| 	maxHeight: 200, | ||||
| }); | ||||
|  | ||||
| let content = $shallowRef<HTMLElement>(); | ||||
| let omitted = $ref(false); | ||||
| let ignoreOmit = $ref(false); | ||||
| const content = shallowRef<HTMLElement>(); | ||||
| const omitted = ref(false); | ||||
| const ignoreOmit = ref(false); | ||||
|  | ||||
| const calcOmit = () => { | ||||
| 	if (omitted || ignoreOmit) return; | ||||
| 	omitted = content.offsetHeight > props.maxHeight; | ||||
| 	if (omitted.value || ignoreOmit.value) return; | ||||
| 	omitted.value = content.value.offsetHeight > props.maxHeight; | ||||
| }; | ||||
|  | ||||
| const omitObserver = new ResizeObserver((entries, observer) => { | ||||
| @@ -37,7 +37,7 @@ const omitObserver = new ResizeObserver((entries, observer) => { | ||||
|  | ||||
| onMounted(() => { | ||||
| 	calcOmit(); | ||||
| 	omitObserver.observe(content); | ||||
| 	omitObserver.observe(content.value); | ||||
| }); | ||||
|  | ||||
| onUnmounted(() => { | ||||
|   | ||||
| @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ComputedRef, onMounted, onUnmounted, provide, shallowRef } from 'vue'; | ||||
| import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue'; | ||||
| import RouterView from '@/components/global/RouterView.vue'; | ||||
| import MkWindow from '@/components/MkWindow.vue'; | ||||
| import { popout as _popout } from '@/scripts/popout.js'; | ||||
| @@ -55,16 +55,16 @@ defineEmits<{ | ||||
| const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue'))); | ||||
|  | ||||
| const contents = shallowRef<HTMLElement>(); | ||||
| let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); | ||||
| let windowEl = $shallowRef<InstanceType<typeof MkWindow>>(); | ||||
| const history = $ref<{ path: string; key: any; }[]>([{ | ||||
| const pageMetadata = ref<null | ComputedRef<PageMetadata>>(); | ||||
| const windowEl = shallowRef<InstanceType<typeof MkWindow>>(); | ||||
| const history = ref<{ path: string; key: any; }[]>([{ | ||||
| 	path: router.getCurrentPath(), | ||||
| 	key: router.getCurrentKey(), | ||||
| }]); | ||||
| const buttonsLeft = $computed(() => { | ||||
| const buttonsLeft = computed(() => { | ||||
| 	const buttons = []; | ||||
|  | ||||
| 	if (history.length > 1) { | ||||
| 	if (history.value.length > 1) { | ||||
| 		buttons.push({ | ||||
| 			icon: 'ti ti-arrow-left', | ||||
| 			onClick: back, | ||||
| @@ -73,7 +73,7 @@ const buttonsLeft = $computed(() => { | ||||
|  | ||||
| 	return buttons; | ||||
| }); | ||||
| const buttonsRight = $computed(() => { | ||||
| const buttonsRight = computed(() => { | ||||
| 	const buttons = [{ | ||||
| 		icon: 'ti ti-reload', | ||||
| 		title: i18n.ts.reload, | ||||
| @@ -86,21 +86,21 @@ const buttonsRight = $computed(() => { | ||||
|  | ||||
| 	return buttons; | ||||
| }); | ||||
| let reloadCount = $ref(0); | ||||
| const reloadCount = ref(0); | ||||
|  | ||||
| router.addListener('push', ctx => { | ||||
| 	history.push({ path: ctx.path, key: ctx.key }); | ||||
| 	history.value.push({ path: ctx.path, key: ctx.key }); | ||||
| }); | ||||
|  | ||||
| provide('router', router); | ||||
| provideMetadataReceiver((info) => { | ||||
| 	pageMetadata = info; | ||||
| 	pageMetadata.value = info; | ||||
| }); | ||||
| provide('shouldOmitHeaderTitle', true); | ||||
| provide('shouldHeaderThin', true); | ||||
| provide('forceSpacerMin', true); | ||||
|  | ||||
| const contextmenu = $computed(() => ([{ | ||||
| const contextmenu = computed(() => ([{ | ||||
| 	icon: 'ti ti-player-eject', | ||||
| 	text: i18n.ts.showInPage, | ||||
| 	action: expand, | ||||
| @@ -113,7 +113,7 @@ const contextmenu = $computed(() => ([{ | ||||
| 	text: i18n.ts.openInNewTab, | ||||
| 	action: () => { | ||||
| 		window.open(url + router.getCurrentPath(), '_blank'); | ||||
| 		windowEl.close(); | ||||
| 		windowEl.value.close(); | ||||
| 	}, | ||||
| }, { | ||||
| 	icon: 'ti ti-link', | ||||
| @@ -124,26 +124,26 @@ const contextmenu = $computed(() => ([{ | ||||
| }])); | ||||
|  | ||||
| function back() { | ||||
| 	history.pop(); | ||||
| 	router.replace(history.at(-1)!.path, history.at(-1)!.key); | ||||
| 	history.value.pop(); | ||||
| 	router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key); | ||||
| } | ||||
|  | ||||
| function reload() { | ||||
| 	reloadCount++; | ||||
| 	reloadCount.value++; | ||||
| } | ||||
|  | ||||
| function close() { | ||||
| 	windowEl.close(); | ||||
| 	windowEl.value.close(); | ||||
| } | ||||
|  | ||||
| function expand() { | ||||
| 	mainRouter.push(router.getCurrentPath(), 'forcePage'); | ||||
| 	windowEl.close(); | ||||
| 	windowEl.value.close(); | ||||
| } | ||||
|  | ||||
| function popout() { | ||||
| 	_popout(router.getCurrentPath(), windowEl.$el); | ||||
| 	windowEl.close(); | ||||
| 	_popout(router.getCurrentPath(), windowEl.value.$el); | ||||
| 	windowEl.value.close(); | ||||
| } | ||||
|  | ||||
| useScrollPositionManager(() => getScrollContainer(contents.value), router); | ||||
|   | ||||
| @@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, watch } from 'vue'; | ||||
| import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js'; | ||||
| @@ -105,12 +105,12 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'status', error: boolean): void; | ||||
| }>(); | ||||
|  | ||||
| let rootEl = $shallowRef<HTMLElement>(); | ||||
| const rootEl = shallowRef<HTMLElement>(); | ||||
|  | ||||
| // 遡り中かどうか | ||||
| let backed = $ref(false); | ||||
| const backed = ref(false); | ||||
|  | ||||
| let scrollRemove = $ref<(() => void) | null>(null); | ||||
| const scrollRemove = ref<(() => void) | null>(null); | ||||
|  | ||||
| /** | ||||
|  * 表示するアイテムのソース | ||||
| @@ -142,8 +142,8 @@ const { | ||||
| 	enableInfiniteScroll, | ||||
| } = defaultStore.reactiveState; | ||||
|  | ||||
| const contentEl = $computed(() => props.pagination.pageEl ?? rootEl); | ||||
| const scrollableElement = $computed(() => contentEl ? getScrollContainer(contentEl) : document.body); | ||||
| const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value); | ||||
| const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body); | ||||
|  | ||||
| const visibility = useDocumentVisibility(); | ||||
|  | ||||
| @@ -153,35 +153,35 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10; | ||||
|  | ||||
| // 先頭が表示されているかどうかを検出 | ||||
| // https://qiita.com/mkataigi/items/0154aefd2223ce23398e | ||||
| let scrollObserver = $ref<IntersectionObserver>(); | ||||
| const scrollObserver = ref<IntersectionObserver>(); | ||||
|  | ||||
| watch([() => props.pagination.reversed, $$(scrollableElement)], () => { | ||||
| 	if (scrollObserver) scrollObserver.disconnect(); | ||||
| watch([() => props.pagination.reversed, scrollableElement], () => { | ||||
| 	if (scrollObserver.value) scrollObserver.value.disconnect(); | ||||
|  | ||||
| 	scrollObserver = new IntersectionObserver(entries => { | ||||
| 		backed = entries[0].isIntersecting; | ||||
| 	scrollObserver.value = new IntersectionObserver(entries => { | ||||
| 		backed.value = entries[0].isIntersecting; | ||||
| 	}, { | ||||
| 		root: scrollableElement, | ||||
| 		root: scrollableElement.value, | ||||
| 		rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px', | ||||
| 		threshold: 0.01, | ||||
| 	}); | ||||
| }, { immediate: true }); | ||||
|  | ||||
| watch($$(rootEl), () => { | ||||
| 	scrollObserver?.disconnect(); | ||||
| watch(rootEl, () => { | ||||
| 	scrollObserver.value?.disconnect(); | ||||
| 	nextTick(() => { | ||||
| 		if (rootEl) scrollObserver?.observe(rootEl); | ||||
| 		if (rootEl.value) scrollObserver.value?.observe(rootEl.value); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| watch([$$(backed), $$(contentEl)], () => { | ||||
| 	if (!backed) { | ||||
| 		if (!contentEl) return; | ||||
| watch([backed, contentEl], () => { | ||||
| 	if (!backed.value) { | ||||
| 		if (!contentEl.value) return; | ||||
|  | ||||
| 		scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE); | ||||
| 		scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE); | ||||
| 	} else { | ||||
| 		if (scrollRemove) scrollRemove(); | ||||
| 		scrollRemove = null; | ||||
| 		if (scrollRemove.value) scrollRemove.value(); | ||||
| 		scrollRemove.value = null; | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| @@ -254,14 +254,14 @@ const fetchMore = async (): Promise<void> => { | ||||
| 		} | ||||
|  | ||||
| 		const reverseConcat = _res => { | ||||
| 			const oldHeight = scrollableElement ? scrollableElement.scrollHeight : getBodyScrollHeight(); | ||||
| 			const oldScroll = scrollableElement ? scrollableElement.scrollTop : window.scrollY; | ||||
| 			const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight(); | ||||
| 			const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY; | ||||
|  | ||||
| 			items.value = concatMapWithArray(items.value, _res); | ||||
|  | ||||
| 			return nextTick(() => { | ||||
| 				if (scrollableElement) { | ||||
| 					scroll(scrollableElement, { top: oldScroll + (scrollableElement.scrollHeight - oldHeight), behavior: 'instant' }); | ||||
| 				if (scrollableElement.value) { | ||||
| 					scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' }); | ||||
| 				} else { | ||||
| 					window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' }); | ||||
| 				} | ||||
| @@ -351,7 +351,7 @@ const appearFetchMoreAhead = async (): Promise<void> => { | ||||
| 	fetchMoreAppearTimeout(); | ||||
| }; | ||||
|  | ||||
| const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl!, TOLERANCE); | ||||
| const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE); | ||||
|  | ||||
| watch(visibility, () => { | ||||
| 	if (visibility.value === 'hidden') { | ||||
| @@ -445,11 +445,11 @@ onActivated(() => { | ||||
| }); | ||||
|  | ||||
| onDeactivated(() => { | ||||
| 	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl ? rootEl.scrollHeight - window.innerHeight : 0) : window.scrollY === 0; | ||||
| 	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl.value ? rootEl.value.scrollHeight - window.innerHeight : 0) : window.scrollY === 0; | ||||
| }); | ||||
|  | ||||
| function toBottom() { | ||||
| 	scrollToBottom(contentEl!); | ||||
| 	scrollToBottom(contentEl.value!); | ||||
| } | ||||
|  | ||||
| onBeforeMount(() => { | ||||
| @@ -477,13 +477,13 @@ onBeforeUnmount(() => { | ||||
| 		clearTimeout(preventAppearFetchMoreTimer.value); | ||||
| 		preventAppearFetchMoreTimer.value = null; | ||||
| 	} | ||||
| 	scrollObserver?.disconnect(); | ||||
| 	scrollObserver.value?.disconnect(); | ||||
| }); | ||||
|  | ||||
| defineExpose({ | ||||
| 	items, | ||||
| 	queue, | ||||
| 	backed, | ||||
| 	backed: backed.value, | ||||
| 	more, | ||||
| 	reload, | ||||
| 	prepend, | ||||
|   | ||||
| @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, shallowRef, ref } from 'vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| @@ -49,22 +49,22 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'cancelled'): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const passwordInput = $shallowRef<InstanceType<typeof MkInput>>(); | ||||
| const password = $ref(''); | ||||
| const token = $ref(null); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const passwordInput = shallowRef<InstanceType<typeof MkInput>>(); | ||||
| const password = ref(''); | ||||
| const token = ref(null); | ||||
|  | ||||
| function onClose() { | ||||
| 	emit('cancelled'); | ||||
| 	if (dialog) dialog.close(); | ||||
| 	if (dialog.value) dialog.value.close(); | ||||
| } | ||||
|  | ||||
| function done(res) { | ||||
| 	emit('done', { password, token }); | ||||
| 	if (dialog) dialog.close(); | ||||
| 	emit('done', { password: password.value, token: token.value }); | ||||
| 	if (dialog.value) dialog.value.close(); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (passwordInput) passwordInput.focus(); | ||||
| 	if (passwordInput.value) passwordInput.value.focus(); | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| @@ -23,13 +23,13 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'end'): void; | ||||
| }>(); | ||||
|  | ||||
| let up = $ref(false); | ||||
| const up = ref(false); | ||||
| const zIndex = os.claimZIndex('middle'); | ||||
| const angle = (45 - (Math.random() * 90)) + 'deg'; | ||||
|  | ||||
| onMounted(() => { | ||||
| 	window.setTimeout(() => { | ||||
| 		up = true; | ||||
| 		up.value = true; | ||||
| 	}, 10); | ||||
|  | ||||
| 	window.setTimeout(() => { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| import { ref, shallowRef } from 'vue'; | ||||
| import MkModal from './MkModal.vue'; | ||||
| import MkMenu from './MkMenu.vue'; | ||||
| import { MenuItem } from '@/types/menu'; | ||||
| @@ -28,7 +28,7 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closing'): void; | ||||
| }>(); | ||||
|  | ||||
| let modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const manualShowing = ref(true); | ||||
| const hiding = ref(false); | ||||
|  | ||||
| @@ -60,14 +60,14 @@ function hide() { | ||||
| 	hiding.value = true; | ||||
|  | ||||
| 	// closeは呼ぶ必要がある | ||||
| 	modal?.close(); | ||||
| 	modal.value?.close(); | ||||
| } | ||||
|  | ||||
| function close() { | ||||
| 	manualShowing.value = false; | ||||
|  | ||||
| 	// closeは呼ぶ必要がある | ||||
| 	modal?.close(); | ||||
| 	modal.value?.close(); | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, ref } from 'vue'; | ||||
| import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue'; | ||||
| import * as mfm from 'mfm-js'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | ||||
| @@ -162,42 +162,42 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'fileChangeSensitive', fileId: string, to: boolean): void; | ||||
| }>(); | ||||
|  | ||||
| const textareaEl = $shallowRef<HTMLTextAreaElement | null>(null); | ||||
| const cwInputEl = $shallowRef<HTMLInputElement | null>(null); | ||||
| const hashtagsInputEl = $shallowRef<HTMLInputElement | null>(null); | ||||
| const visibilityButton = $shallowRef<HTMLElement | null>(null); | ||||
| const textareaEl = shallowRef<HTMLTextAreaElement | null>(null); | ||||
| const cwInputEl = shallowRef<HTMLInputElement | null>(null); | ||||
| const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null); | ||||
| const visibilityButton = shallowRef<HTMLElement | null>(null); | ||||
|  | ||||
| let posting = $ref(false); | ||||
| let posted = $ref(false); | ||||
| let text = $ref(props.initialText ?? ''); | ||||
| let files = $ref(props.initialFiles ?? []); | ||||
| let poll = $ref<{ | ||||
| const posting = ref(false); | ||||
| const posted = ref(false); | ||||
| const text = ref(props.initialText ?? ''); | ||||
| const files = ref(props.initialFiles ?? []); | ||||
| const poll = ref<{ | ||||
| 	choices: string[]; | ||||
| 	multiple: boolean; | ||||
| 	expiresAt: string | null; | ||||
| 	expiredAfter: string | null; | ||||
| } | null>(null); | ||||
| let useCw = $ref(false); | ||||
| let showPreview = $ref(defaultStore.state.showPreview); | ||||
| watch($$(showPreview), () => defaultStore.set('showPreview', showPreview)); | ||||
| let cw = $ref<string | null>(null); | ||||
| let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); | ||||
| let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]); | ||||
| let visibleUsers = $ref([]); | ||||
| const useCw = ref(false); | ||||
| const showPreview = ref(defaultStore.state.showPreview); | ||||
| watch(showPreview, () => defaultStore.set('showPreview', showPreview.value)); | ||||
| const cw = ref<string | null>(null); | ||||
| const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); | ||||
| const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]); | ||||
| const visibleUsers = ref([]); | ||||
| if (props.initialVisibleUsers) { | ||||
| 	props.initialVisibleUsers.forEach(pushVisibleUser); | ||||
| } | ||||
| let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance); | ||||
| let autocomplete = $ref(null); | ||||
| let draghover = $ref(false); | ||||
| let quoteId = $ref(null); | ||||
| let hasNotSpecifiedMentions = $ref(false); | ||||
| let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]')); | ||||
| let imeText = $ref(''); | ||||
| let showingOptions = $ref(false); | ||||
| const reactionAcceptance = ref(defaultStore.state.reactionAcceptance); | ||||
| const autocomplete = ref(null); | ||||
| const draghover = ref(false); | ||||
| const quoteId = ref(null); | ||||
| const hasNotSpecifiedMentions = ref(false); | ||||
| const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]')); | ||||
| const imeText = ref(''); | ||||
| const showingOptions = ref(false); | ||||
| const textAreaReadOnly = ref(false); | ||||
|  | ||||
| const draftKey = $computed((): string => { | ||||
| const draftKey = computed((): string => { | ||||
| 	let key = props.channel ? `channel:${props.channel.id}` : ''; | ||||
|  | ||||
| 	if (props.renote) { | ||||
| @@ -211,7 +211,7 @@ const draftKey = $computed((): string => { | ||||
| 	return key; | ||||
| }); | ||||
|  | ||||
| const placeholder = $computed((): string => { | ||||
| const placeholder = computed((): string => { | ||||
| 	if (props.renote) { | ||||
| 		return i18n.ts._postForm.quotePlaceholder; | ||||
| 	} else if (props.reply) { | ||||
| @@ -231,7 +231,7 @@ const placeholder = $computed((): string => { | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| const submitText = $computed((): string => { | ||||
| const submitText = computed((): string => { | ||||
| 	return props.renote | ||||
| 		? i18n.ts.quote | ||||
| 		: props.reply | ||||
| @@ -239,45 +239,45 @@ const submitText = $computed((): string => { | ||||
| 			: i18n.ts.note; | ||||
| }); | ||||
|  | ||||
| const textLength = $computed((): number => { | ||||
| 	return (text + imeText).trim().length; | ||||
| const textLength = computed((): number => { | ||||
| 	return (text.value + imeText.value).trim().length; | ||||
| }); | ||||
|  | ||||
| const maxTextLength = $computed((): number => { | ||||
| const maxTextLength = computed((): number => { | ||||
| 	return instance ? instance.maxNoteTextLength : 1000; | ||||
| }); | ||||
|  | ||||
| const canPost = $computed((): boolean => { | ||||
| 	return !props.mock && !posting && !posted && | ||||
| 		(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && | ||||
| 		(textLength <= maxTextLength) && | ||||
| 		(!poll || poll.choices.length >= 2); | ||||
| const canPost = computed((): boolean => { | ||||
| 	return !props.mock && !posting.value && !posted.value && | ||||
| 		(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote) && | ||||
| 		(textLength.value <= maxTextLength.value) && | ||||
| 		(!poll.value || poll.value.choices.length >= 2); | ||||
| }); | ||||
|  | ||||
| const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags')); | ||||
| const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags')); | ||||
| const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags')); | ||||
| const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags')); | ||||
|  | ||||
| watch($$(text), () => { | ||||
| watch(text, () => { | ||||
| 	checkMissingMention(); | ||||
| }, { immediate: true }); | ||||
|  | ||||
| watch($$(visibility), () => { | ||||
| watch(visibility, () => { | ||||
| 	checkMissingMention(); | ||||
| }, { immediate: true }); | ||||
|  | ||||
| watch($$(visibleUsers), () => { | ||||
| watch(visibleUsers, () => { | ||||
| 	checkMissingMention(); | ||||
| }, { | ||||
| 	deep: true, | ||||
| }); | ||||
|  | ||||
| if (props.mention) { | ||||
| 	text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`; | ||||
| 	text += ' '; | ||||
| 	text.value = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`; | ||||
| 	text.value += ' '; | ||||
| } | ||||
|  | ||||
| if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) { | ||||
| 	text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; | ||||
| 	text.value = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; | ||||
| } | ||||
|  | ||||
| if (props.reply && props.reply.text != null) { | ||||
| @@ -295,32 +295,32 @@ if (props.reply && props.reply.text != null) { | ||||
| 		if ($i.username === x.username && (x.host == null || x.host === host)) continue; | ||||
|  | ||||
| 		// 重複は除外 | ||||
| 		if (text.includes(`${mention} `)) continue; | ||||
| 		if (text.value.includes(`${mention} `)) continue; | ||||
|  | ||||
| 		text += `${mention} `; | ||||
| 		text.value += `${mention} `; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| if ($i?.isSilenced && visibility === 'public') { | ||||
| 	visibility = 'home'; | ||||
| if ($i?.isSilenced && visibility.value === 'public') { | ||||
| 	visibility.value = 'home'; | ||||
| } | ||||
|  | ||||
| if (props.channel) { | ||||
| 	visibility = 'public'; | ||||
| 	localOnly = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 	visibility.value = 'public'; | ||||
| 	localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| } | ||||
|  | ||||
| // 公開以外へのリプライ時は元の公開範囲を引き継ぐ | ||||
| if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) { | ||||
| 	if (props.reply.visibility === 'home' && visibility === 'followers') { | ||||
| 		visibility = 'followers'; | ||||
| 	} else if (['home', 'followers'].includes(props.reply.visibility) && visibility === 'specified') { | ||||
| 		visibility = 'specified'; | ||||
| 	if (props.reply.visibility === 'home' && visibility.value === 'followers') { | ||||
| 		visibility.value = 'followers'; | ||||
| 	} else if (['home', 'followers'].includes(props.reply.visibility) && visibility.value === 'specified') { | ||||
| 		visibility.value = 'specified'; | ||||
| 	} else { | ||||
| 		visibility = props.reply.visibility; | ||||
| 		visibility.value = props.reply.visibility; | ||||
| 	} | ||||
|  | ||||
| 	if (visibility === 'specified') { | ||||
| 	if (visibility.value === 'specified') { | ||||
| 		if (props.reply.visibleUserIds) { | ||||
| 			os.api('users/show', { | ||||
| 				userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId), | ||||
| @@ -338,57 +338,57 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib | ||||
| } | ||||
|  | ||||
| if (props.specified) { | ||||
| 	visibility = 'specified'; | ||||
| 	visibility.value = 'specified'; | ||||
| 	pushVisibleUser(props.specified); | ||||
| } | ||||
|  | ||||
| // keep cw when reply | ||||
| if (defaultStore.state.keepCw && props.reply && props.reply.cw) { | ||||
| 	useCw = true; | ||||
| 	cw = props.reply.cw; | ||||
| 	useCw.value = true; | ||||
| 	cw.value = props.reply.cw; | ||||
| } | ||||
|  | ||||
| function watchForDraft() { | ||||
| 	watch($$(text), () => saveDraft()); | ||||
| 	watch($$(useCw), () => saveDraft()); | ||||
| 	watch($$(cw), () => saveDraft()); | ||||
| 	watch($$(poll), () => saveDraft()); | ||||
| 	watch($$(files), () => saveDraft(), { deep: true }); | ||||
| 	watch($$(visibility), () => saveDraft()); | ||||
| 	watch($$(localOnly), () => saveDraft()); | ||||
| 	watch(text, () => saveDraft()); | ||||
| 	watch(useCw, () => saveDraft()); | ||||
| 	watch(cw, () => saveDraft()); | ||||
| 	watch(poll, () => saveDraft()); | ||||
| 	watch(files, () => saveDraft(), { deep: true }); | ||||
| 	watch(visibility, () => saveDraft()); | ||||
| 	watch(localOnly, () => saveDraft()); | ||||
| } | ||||
|  | ||||
| function checkMissingMention() { | ||||
| 	if (visibility === 'specified') { | ||||
| 		const ast = mfm.parse(text); | ||||
| 	if (visibility.value === 'specified') { | ||||
| 		const ast = mfm.parse(text.value); | ||||
|  | ||||
| 		for (const x of extractMentions(ast)) { | ||||
| 			if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { | ||||
| 				hasNotSpecifiedMentions = true; | ||||
| 			if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) { | ||||
| 				hasNotSpecifiedMentions.value = true; | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	hasNotSpecifiedMentions = false; | ||||
| 	hasNotSpecifiedMentions.value = false; | ||||
| } | ||||
|  | ||||
| function addMissingMention() { | ||||
| 	const ast = mfm.parse(text); | ||||
| 	const ast = mfm.parse(text.value); | ||||
|  | ||||
| 	for (const x of extractMentions(ast)) { | ||||
| 		if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { | ||||
| 		if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) { | ||||
| 			os.api('users/show', { username: x.username, host: x.host }).then(user => { | ||||
| 				visibleUsers.push(user); | ||||
| 				visibleUsers.value.push(user); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function togglePoll() { | ||||
| 	if (poll) { | ||||
| 		poll = null; | ||||
| 	if (poll.value) { | ||||
| 		poll.value = null; | ||||
| 	} else { | ||||
| 		poll = { | ||||
| 		poll.value = { | ||||
| 			choices: ['', ''], | ||||
| 			multiple: false, | ||||
| 			expiresAt: null, | ||||
| @@ -398,13 +398,13 @@ function togglePoll() { | ||||
| } | ||||
|  | ||||
| function addTag(tag: string) { | ||||
| 	insertTextAtCursor(textareaEl, ` #${tag} `); | ||||
| 	insertTextAtCursor(textareaEl.value, ` #${tag} `); | ||||
| } | ||||
|  | ||||
| function focus() { | ||||
| 	if (textareaEl) { | ||||
| 		textareaEl.focus(); | ||||
| 		textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length); | ||||
| 	if (textareaEl.value) { | ||||
| 		textareaEl.value.focus(); | ||||
| 		textareaEl.value.setSelectionRange(textareaEl.value.value.length, textareaEl.value.value.length); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -413,55 +413,55 @@ function chooseFileFrom(ev) { | ||||
|  | ||||
| 	selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => { | ||||
| 		for (const file of files_) { | ||||
| 			files.push(file); | ||||
| 			files.value.push(file); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function detachFile(id) { | ||||
| 	files = files.filter(x => x.id !== id); | ||||
| 	files.value = files.value.filter(x => x.id !== id); | ||||
| } | ||||
|  | ||||
| function updateFileSensitive(file, sensitive) { | ||||
| 	if (props.mock) { | ||||
| 		emit('fileChangeSensitive', file.id, sensitive); | ||||
| 	} | ||||
| 	files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; | ||||
| 	files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive; | ||||
| } | ||||
|  | ||||
| function updateFileName(file, name) { | ||||
| 	files[files.findIndex(x => x.id === file.id)].name = name; | ||||
| 	files.value[files.value.findIndex(x => x.id === file.id)].name = name; | ||||
| } | ||||
|  | ||||
| function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void { | ||||
| 	files[files.findIndex(x => x.id === file.id)] = newFile; | ||||
| 	files.value[files.value.findIndex(x => x.id === file.id)] = newFile; | ||||
| } | ||||
|  | ||||
| function upload(file: File, name?: string): void { | ||||
| 	if (props.mock) return; | ||||
|  | ||||
| 	uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { | ||||
| 		files.push(res); | ||||
| 		files.value.push(res); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function setVisibility() { | ||||
| 	if (props.channel) { | ||||
| 		visibility = 'public'; | ||||
| 		localOnly = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 		visibility.value = 'public'; | ||||
| 		localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), { | ||||
| 		currentVisibility: visibility, | ||||
| 		currentVisibility: visibility.value, | ||||
| 		isSilenced: $i?.isSilenced, | ||||
| 		localOnly: localOnly, | ||||
| 		src: visibilityButton, | ||||
| 		localOnly: localOnly.value, | ||||
| 		src: visibilityButton.value, | ||||
| 	}, { | ||||
| 		changeVisibility: v => { | ||||
| 			visibility = v; | ||||
| 			visibility.value = v; | ||||
| 			if (defaultStore.state.rememberNoteVisibility) { | ||||
| 				defaultStore.set('visibility', visibility); | ||||
| 				defaultStore.set('visibility', visibility.value); | ||||
| 			} | ||||
| 		}, | ||||
| 	}, 'closed'); | ||||
| @@ -469,14 +469,14 @@ function setVisibility() { | ||||
|  | ||||
| async function toggleLocalOnly() { | ||||
| 	if (props.channel) { | ||||
| 		visibility = 'public'; | ||||
| 		localOnly = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 		visibility.value = 'public'; | ||||
| 		localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const neverShowInfo = miLocalStorage.getItem('neverShowLocalOnlyInfo'); | ||||
|  | ||||
| 	if (!localOnly && neverShowInfo !== 'true') { | ||||
| 	if (!localOnly.value && neverShowInfo !== 'true') { | ||||
| 		const confirm = await os.actions({ | ||||
| 			type: 'question', | ||||
| 			title: i18n.ts.disableFederationConfirm, | ||||
| @@ -506,7 +506,7 @@ async function toggleLocalOnly() { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	localOnly = !localOnly; | ||||
| 	localOnly.value = !localOnly.value; | ||||
| } | ||||
|  | ||||
| async function toggleReactionAcceptance() { | ||||
| @@ -519,15 +519,15 @@ async function toggleReactionAcceptance() { | ||||
| 			{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }, | ||||
| 			{ value: 'likeOnly' as const, text: i18n.ts.likeOnly }, | ||||
| 		], | ||||
| 		default: reactionAcceptance, | ||||
| 		default: reactionAcceptance.value, | ||||
| 	}); | ||||
| 	if (select.canceled) return; | ||||
| 	reactionAcceptance = select.result; | ||||
| 	reactionAcceptance.value = select.result; | ||||
| } | ||||
|  | ||||
| function pushVisibleUser(user) { | ||||
| 	if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) { | ||||
| 		visibleUsers.push(user); | ||||
| 	if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) { | ||||
| 		visibleUsers.value.push(user); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -535,34 +535,34 @@ function addVisibleUser() { | ||||
| 	os.selectUser().then(user => { | ||||
| 		pushVisibleUser(user); | ||||
|  | ||||
| 		if (!text.toLowerCase().includes(`@${user.username.toLowerCase()}`)) { | ||||
| 			text = `@${Misskey.acct.toString(user)} ${text}`; | ||||
| 		if (!text.value.toLowerCase().includes(`@${user.username.toLowerCase()}`)) { | ||||
| 			text.value = `@${Misskey.acct.toString(user)} ${text.value}`; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function removeVisibleUser(user) { | ||||
| 	visibleUsers = erase(user, visibleUsers); | ||||
| 	visibleUsers.value = erase(user, visibleUsers.value); | ||||
| } | ||||
|  | ||||
| function clear() { | ||||
| 	text = ''; | ||||
| 	files = []; | ||||
| 	poll = null; | ||||
| 	quoteId = null; | ||||
| 	text.value = ''; | ||||
| 	files.value = []; | ||||
| 	poll.value = null; | ||||
| 	quoteId.value = null; | ||||
| } | ||||
|  | ||||
| function onKeydown(ev: KeyboardEvent) { | ||||
| 	if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost) post(); | ||||
| 	if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost.value) post(); | ||||
| 	if (ev.key === 'Escape') emit('esc'); | ||||
| } | ||||
|  | ||||
| function onCompositionUpdate(ev: CompositionEvent) { | ||||
| 	imeText = ev.data; | ||||
| 	imeText.value = ev.data; | ||||
| } | ||||
|  | ||||
| function onCompositionEnd(ev: CompositionEvent) { | ||||
| 	imeText = ''; | ||||
| 	imeText.value = ''; | ||||
| } | ||||
|  | ||||
| async function onPaste(ev: ClipboardEvent) { | ||||
| @@ -580,7 +580,7 @@ async function onPaste(ev: ClipboardEvent) { | ||||
|  | ||||
| 	const paste = ev.clipboardData.getData('text'); | ||||
|  | ||||
| 	if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { | ||||
| 	if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) { | ||||
| 		ev.preventDefault(); | ||||
|  | ||||
| 		os.confirm({ | ||||
| @@ -588,11 +588,11 @@ async function onPaste(ev: ClipboardEvent) { | ||||
| 			text: i18n.ts.quoteQuestion, | ||||
| 		}).then(({ canceled }) => { | ||||
| 			if (canceled) { | ||||
| 				insertTextAtCursor(textareaEl, paste); | ||||
| 				insertTextAtCursor(textareaEl.value, paste); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			quoteId = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; | ||||
| 			quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| @@ -603,7 +603,7 @@ function onDragover(ev) { | ||||
| 	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; | ||||
| 	if (isFile || isDriveFile) { | ||||
| 		ev.preventDefault(); | ||||
| 		draghover = true; | ||||
| 		draghover.value = true; | ||||
| 		switch (ev.dataTransfer.effectAllowed) { | ||||
| 			case 'all': | ||||
| 			case 'uninitialized': | ||||
| @@ -624,15 +624,15 @@ function onDragover(ev) { | ||||
| } | ||||
|  | ||||
| function onDragenter(ev) { | ||||
| 	draghover = true; | ||||
| 	draghover.value = true; | ||||
| } | ||||
|  | ||||
| function onDragleave(ev) { | ||||
| 	draghover = false; | ||||
| 	draghover.value = false; | ||||
| } | ||||
|  | ||||
| function onDrop(ev): void { | ||||
| 	draghover = false; | ||||
| 	draghover.value = false; | ||||
|  | ||||
| 	// ファイルだったら | ||||
| 	if (ev.dataTransfer.files.length > 0) { | ||||
| @@ -645,7 +645,7 @@ function onDrop(ev): void { | ||||
| 	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); | ||||
| 	if (driveFile != null && driveFile !== '') { | ||||
| 		const file = JSON.parse(driveFile); | ||||
| 		files.push(file); | ||||
| 		files.value.push(file); | ||||
| 		ev.preventDefault(); | ||||
| 	} | ||||
| 	//#endregion | ||||
| @@ -656,16 +656,16 @@ function saveDraft() { | ||||
|  | ||||
| 	const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); | ||||
|  | ||||
| 	draftData[draftKey] = { | ||||
| 	draftData[draftKey.value] = { | ||||
| 		updatedAt: new Date(), | ||||
| 		data: { | ||||
| 			text: text, | ||||
| 			useCw: useCw, | ||||
| 			cw: cw, | ||||
| 			visibility: visibility, | ||||
| 			localOnly: localOnly, | ||||
| 			files: files, | ||||
| 			poll: poll, | ||||
| 			text: text.value, | ||||
| 			useCw: useCw.value, | ||||
| 			cw: cw.value, | ||||
| 			visibility: visibility.value, | ||||
| 			localOnly: localOnly.value, | ||||
| 			files: files.value, | ||||
| 			poll: poll.value, | ||||
| 		}, | ||||
| 	}; | ||||
|  | ||||
| @@ -675,13 +675,13 @@ function saveDraft() { | ||||
| function deleteDraft() { | ||||
| 	const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); | ||||
|  | ||||
| 	delete draftData[draftKey]; | ||||
| 	delete draftData[draftKey.value]; | ||||
|  | ||||
| 	miLocalStorage.setItem('drafts', JSON.stringify(draftData)); | ||||
| } | ||||
|  | ||||
| async function post(ev?: MouseEvent) { | ||||
| 	if (useCw && (cw == null || cw.trim() === '')) { | ||||
| 	if (useCw.value && (cw.value == null || cw.value.trim() === '')) { | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
| 			text: i18n.ts.cwNotationRequired, | ||||
| @@ -700,13 +700,13 @@ async function post(ev?: MouseEvent) { | ||||
| 	if (props.mock) return; | ||||
|  | ||||
| 	const annoying = | ||||
| 		text.includes('$[x2') || | ||||
| 		text.includes('$[x3') || | ||||
| 		text.includes('$[x4') || | ||||
| 		text.includes('$[scale') || | ||||
| 		text.includes('$[position'); | ||||
| 		text.value.includes('$[x2') || | ||||
| 		text.value.includes('$[x3') || | ||||
| 		text.value.includes('$[x4') || | ||||
| 		text.value.includes('$[scale') || | ||||
| 		text.value.includes('$[position'); | ||||
|  | ||||
| 	if (annoying && visibility === 'public') { | ||||
| 	if (annoying && visibility.value === 'public') { | ||||
| 		const { canceled, result } = await os.actions({ | ||||
| 			type: 'warning', | ||||
| 			text: i18n.ts.thisPostMayBeAnnoying, | ||||
| @@ -726,26 +726,26 @@ async function post(ev?: MouseEvent) { | ||||
| 		if (canceled) return; | ||||
| 		if (result === 'cancel') return; | ||||
| 		if (result === 'home') { | ||||
| 			visibility = 'home'; | ||||
| 			visibility.value = 'home'; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	let postData = { | ||||
| 		text: text === '' ? null : text, | ||||
| 		fileIds: files.length > 0 ? files.map(f => f.id) : undefined, | ||||
| 		text: text.value === '' ? null : text.value, | ||||
| 		fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined, | ||||
| 		replyId: props.reply ? props.reply.id : undefined, | ||||
| 		renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, | ||||
| 		renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined, | ||||
| 		channelId: props.channel ? props.channel.id : undefined, | ||||
| 		poll: poll, | ||||
| 		cw: useCw ? cw ?? '' : null, | ||||
| 		localOnly: localOnly, | ||||
| 		visibility: visibility, | ||||
| 		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined, | ||||
| 		reactionAcceptance, | ||||
| 		poll: poll.value, | ||||
| 		cw: useCw.value ? cw.value ?? '' : null, | ||||
| 		localOnly: localOnly.value, | ||||
| 		visibility: visibility.value, | ||||
| 		visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined, | ||||
| 		reactionAcceptance: reactionAcceptance.value, | ||||
| 	}; | ||||
|  | ||||
| 	if (withHashtags && hashtags && hashtags.trim() !== '') { | ||||
| 		const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); | ||||
| 	if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { | ||||
| 		const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); | ||||
| 		postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; | ||||
| 	} | ||||
|  | ||||
| @@ -762,15 +762,15 @@ async function post(ev?: MouseEvent) { | ||||
|  | ||||
| 	let token = undefined; | ||||
|  | ||||
| 	if (postAccount) { | ||||
| 	if (postAccount.value) { | ||||
| 		const storedAccounts = await getAccounts(); | ||||
| 		token = storedAccounts.find(x => x.id === postAccount.id)?.token; | ||||
| 		token = storedAccounts.find(x => x.id === postAccount.value.id)?.token; | ||||
| 	} | ||||
|  | ||||
| 	posting = true; | ||||
| 	posting.value = true; | ||||
| 	os.api('notes/create', postData, token).then(() => { | ||||
| 		if (props.freezeAfterPosted) { | ||||
| 			posted = true; | ||||
| 			posted.value = true; | ||||
| 		} else { | ||||
| 			clear(); | ||||
| 		} | ||||
| @@ -782,8 +782,8 @@ async function post(ev?: MouseEvent) { | ||||
| 				const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[]; | ||||
| 				miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history)))); | ||||
| 			} | ||||
| 			posting = false; | ||||
| 			postAccount = null; | ||||
| 			posting.value = false; | ||||
| 			postAccount.value = null; | ||||
|  | ||||
| 			incNotesCount(); | ||||
| 			if (notesCount === 1) { | ||||
| @@ -828,7 +828,7 @@ async function post(ev?: MouseEvent) { | ||||
| 			} | ||||
| 		}); | ||||
| 	}).catch(err => { | ||||
| 		posting = false; | ||||
| 		posting.value = false; | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
| 			text: err.message + '\n' + (err as any).id, | ||||
| @@ -842,7 +842,7 @@ function cancel() { | ||||
|  | ||||
| function insertMention() { | ||||
| 	os.selectUser().then(user => { | ||||
| 		insertTextAtCursor(textareaEl, '@' + Misskey.acct.toString(user) + ' '); | ||||
| 		insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' '); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @@ -852,7 +852,7 @@ async function insertEmoji(ev: MouseEvent) { | ||||
| 	emojiPicker.show( | ||||
| 		ev.currentTarget ?? ev.target, | ||||
| 		emoji => { | ||||
| 			insertTextAtCursor(textareaEl, emoji); | ||||
| 			insertTextAtCursor(textareaEl.value, emoji); | ||||
| 		}, | ||||
| 		() => { | ||||
| 			textAreaReadOnly.value = false; | ||||
| @@ -866,17 +866,17 @@ function showActions(ev) { | ||||
| 		text: action.title, | ||||
| 		action: () => { | ||||
| 			action.handler({ | ||||
| 				text: text, | ||||
| 				cw: cw, | ||||
| 				text: text.value, | ||||
| 				cw: cw.value, | ||||
| 			}, (key, value) => { | ||||
| 				if (key === 'text') { text = value; } | ||||
| 				if (key === 'cw') { useCw = value !== null; cw = value; } | ||||
| 				if (key === 'text') { text.value = value; } | ||||
| 				if (key === 'cw') { useCw.value = value !== null; cw.value = value; } | ||||
| 			}); | ||||
| 		}, | ||||
| 	})), ev.currentTarget ?? ev.target); | ||||
| } | ||||
|  | ||||
| let postAccount = $ref<Misskey.entities.UserDetailed | null>(null); | ||||
| const postAccount = ref<Misskey.entities.UserDetailed | null>(null); | ||||
|  | ||||
| function openAccountMenu(ev: MouseEvent) { | ||||
| 	if (props.mock) return; | ||||
| @@ -884,12 +884,12 @@ function openAccountMenu(ev: MouseEvent) { | ||||
| 	openAccountMenu_({ | ||||
| 		withExtraOperation: false, | ||||
| 		includeCurrentAccount: true, | ||||
| 		active: postAccount != null ? postAccount.id : $i.id, | ||||
| 		active: postAccount.value != null ? postAccount.value.id : $i.id, | ||||
| 		onChoose: (account) => { | ||||
| 			if (account.id === $i.id) { | ||||
| 				postAccount = null; | ||||
| 				postAccount.value = null; | ||||
| 			} else { | ||||
| 				postAccount = account; | ||||
| 				postAccount.value = account; | ||||
| 			} | ||||
| 		}, | ||||
| 	}, ev); | ||||
| @@ -905,23 +905,23 @@ onMounted(() => { | ||||
| 	} | ||||
|  | ||||
| 	// TODO: detach when unmount | ||||
| 	new Autocomplete(textareaEl, $$(text)); | ||||
| 	new Autocomplete(cwInputEl, $$(cw)); | ||||
| 	new Autocomplete(hashtagsInputEl, $$(hashtags)); | ||||
| 	new Autocomplete(textareaEl.value, text); | ||||
| 	new Autocomplete(cwInputEl.value, cw); | ||||
| 	new Autocomplete(hashtagsInputEl.value, hashtags); | ||||
|  | ||||
| 	nextTick(() => { | ||||
| 		// 書きかけの投稿を復元 | ||||
| 		if (!props.instant && !props.mention && !props.specified && !props.mock) { | ||||
| 			const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey]; | ||||
| 			const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value]; | ||||
| 			if (draft) { | ||||
| 				text = draft.data.text; | ||||
| 				useCw = draft.data.useCw; | ||||
| 				cw = draft.data.cw; | ||||
| 				visibility = draft.data.visibility; | ||||
| 				localOnly = draft.data.localOnly; | ||||
| 				files = (draft.data.files || []).filter(draftFile => draftFile); | ||||
| 				text.value = draft.data.text; | ||||
| 				useCw.value = draft.data.useCw; | ||||
| 				cw.value = draft.data.cw; | ||||
| 				visibility.value = draft.data.visibility; | ||||
| 				localOnly.value = draft.data.localOnly; | ||||
| 				files.value = (draft.data.files || []).filter(draftFile => draftFile); | ||||
| 				if (draft.data.poll) { | ||||
| 					poll = draft.data.poll; | ||||
| 					poll.value = draft.data.poll; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @@ -929,21 +929,21 @@ onMounted(() => { | ||||
| 		// 削除して編集 | ||||
| 		if (props.initialNote) { | ||||
| 			const init = props.initialNote; | ||||
| 			text = init.text ? init.text : ''; | ||||
| 			files = init.files; | ||||
| 			cw = init.cw; | ||||
| 			useCw = init.cw != null; | ||||
| 			text.value = init.text ? init.text : ''; | ||||
| 			files.value = init.files; | ||||
| 			cw.value = init.cw; | ||||
| 			useCw.value = init.cw != null; | ||||
| 			if (init.poll) { | ||||
| 				poll = { | ||||
| 				poll.value = { | ||||
| 					choices: init.poll.choices.map(x => x.text), | ||||
| 					multiple: init.poll.multiple, | ||||
| 					expiresAt: init.poll.expiresAt, | ||||
| 					expiredAfter: init.poll.expiredAfter, | ||||
| 				}; | ||||
| 			} | ||||
| 			visibility = init.visibility; | ||||
| 			localOnly = init.localOnly; | ||||
| 			quoteId = init.renote ? init.renote.id : null; | ||||
| 			visibility.value = init.visibility; | ||||
| 			localOnly.value = init.localOnly; | ||||
| 			quoteId.value = init.renote ? init.renote.id : null; | ||||
| 		} | ||||
|  | ||||
| 		nextTick(() => watchForDraft()); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { shallowRef } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import MkPostForm from '@/components/MkPostForm.vue'; | ||||
| @@ -36,11 +36,11 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| let modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| let form = $shallowRef<InstanceType<typeof MkPostForm>>(); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const form = shallowRef<InstanceType<typeof MkPostForm>>(); | ||||
|  | ||||
| function onPosted() { | ||||
| 	modal.close({ | ||||
| 	modal.value.close({ | ||||
| 		useSendAnimation: true, | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, watch } from 'vue'; | ||||
| import { onMounted, onUnmounted, watch, ref, shallowRef } from 'vue'; | ||||
| import { deviceKind } from '@/scripts/device-kind.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { getScrollContainer } from '@/scripts/scroll.js'; | ||||
| @@ -35,15 +35,15 @@ const RELEASE_TRANSITION_DURATION = 200; | ||||
| const PULL_BRAKE_BASE = 1.5; | ||||
| const PULL_BRAKE_FACTOR = 170; | ||||
|  | ||||
| let isPullStart = $ref(false); | ||||
| let isPullEnd = $ref(false); | ||||
| let isRefreshing = $ref(false); | ||||
| let pullDistance = $ref(0); | ||||
| const isPullStart = ref(false); | ||||
| const isPullEnd = ref(false); | ||||
| const isRefreshing = ref(false); | ||||
| const pullDistance = ref(0); | ||||
|  | ||||
| let supportPointerDesktop = false; | ||||
| let startScreenY: number | null = null; | ||||
|  | ||||
| const rootEl = $shallowRef<HTMLDivElement>(); | ||||
| const rootEl = shallowRef<HTMLDivElement>(); | ||||
| let scrollEl: HTMLElement | null = null; | ||||
|  | ||||
| let disabled = false; | ||||
| @@ -66,17 +66,17 @@ function getScreenY(event) { | ||||
| } | ||||
|  | ||||
| function moveStart(event) { | ||||
| 	if (!isPullStart && !isRefreshing && !disabled) { | ||||
| 		isPullStart = true; | ||||
| 	if (!isPullStart.value && !isRefreshing.value && !disabled) { | ||||
| 		isPullStart.value = true; | ||||
| 		startScreenY = getScreenY(event); | ||||
| 		pullDistance = 0; | ||||
| 		pullDistance.value = 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function moveBySystem(to: number): Promise<void> { | ||||
| 	return new Promise(r => { | ||||
| 		const startHeight = pullDistance; | ||||
| 		const overHeight = pullDistance - to; | ||||
| 		const startHeight = pullDistance.value; | ||||
| 		const overHeight = pullDistance.value - to; | ||||
| 		if (overHeight < 1) { | ||||
| 			r(); | ||||
| 			return; | ||||
| @@ -85,36 +85,36 @@ function moveBySystem(to: number): Promise<void> { | ||||
| 		let intervalId = setInterval(() => { | ||||
| 			const time = Date.now() - startTime; | ||||
| 			if (time > RELEASE_TRANSITION_DURATION) { | ||||
| 				pullDistance = to; | ||||
| 				pullDistance.value = to; | ||||
| 				clearInterval(intervalId); | ||||
| 				r(); | ||||
| 				return; | ||||
| 			} | ||||
| 			const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time; | ||||
| 			if (pullDistance < nextHeight) return; | ||||
| 			pullDistance = nextHeight; | ||||
| 			if (pullDistance.value < nextHeight) return; | ||||
| 			pullDistance.value = nextHeight; | ||||
| 		}, 1); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| async function fixOverContent() { | ||||
| 	if (pullDistance > FIRE_THRESHOLD) { | ||||
| 	if (pullDistance.value > FIRE_THRESHOLD) { | ||||
| 		await moveBySystem(FIRE_THRESHOLD); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async function closeContent() { | ||||
| 	if (pullDistance > 0) { | ||||
| 	if (pullDistance.value > 0) { | ||||
| 		await moveBySystem(0); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function moveEnd() { | ||||
| 	if (isPullStart && !isRefreshing) { | ||||
| 	if (isPullStart.value && !isRefreshing.value) { | ||||
| 		startScreenY = null; | ||||
| 		if (isPullEnd) { | ||||
| 			isPullEnd = false; | ||||
| 			isRefreshing = true; | ||||
| 		if (isPullEnd.value) { | ||||
| 			isPullEnd.value = false; | ||||
| 			isRefreshing.value = true; | ||||
| 			fixOverContent().then(() => { | ||||
| 				emit('refresh'); | ||||
| 				props.refresher().then(() => { | ||||
| @@ -122,17 +122,17 @@ function moveEnd() { | ||||
| 				}); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			closeContent().then(() => isPullStart = false); | ||||
| 			closeContent().then(() => isPullStart.value = false); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function moving(event: TouchEvent | PointerEvent) { | ||||
| 	if (!isPullStart || isRefreshing || disabled) return; | ||||
| 	if (!isPullStart.value || isRefreshing.value || disabled) return; | ||||
|  | ||||
| 	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) { | ||||
| 		pullDistance = 0; | ||||
| 		isPullEnd = false; | ||||
| 	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) { | ||||
| 		pullDistance.value = 0; | ||||
| 		isPullEnd.value = false; | ||||
| 		moveEnd(); | ||||
| 		return; | ||||
| 	} | ||||
| @@ -143,13 +143,13 @@ function moving(event: TouchEvent | PointerEvent) { | ||||
| 	const moveScreenY = getScreenY(event); | ||||
|  | ||||
| 	const moveHeight = moveScreenY - startScreenY!; | ||||
| 	pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE); | ||||
| 	pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE); | ||||
|  | ||||
| 	if (pullDistance > 0) { | ||||
| 	if (pullDistance.value > 0) { | ||||
| 		if (event.cancelable) event.preventDefault(); | ||||
| 	} | ||||
|  | ||||
| 	isPullEnd = pullDistance >= FIRE_THRESHOLD; | ||||
| 	isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -159,8 +159,8 @@ function moving(event: TouchEvent | PointerEvent) { | ||||
|  */ | ||||
| function refreshFinished() { | ||||
| 	closeContent().then(() => { | ||||
| 		isPullStart = false; | ||||
| 		isRefreshing = false; | ||||
| 		isPullStart.value = false; | ||||
| 		isRefreshing.value = false; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @@ -182,26 +182,26 @@ function onScrollContainerScroll() { | ||||
| } | ||||
|  | ||||
| function registerEventListenersForReadyToPull() { | ||||
| 	if (rootEl == null) return; | ||||
| 	rootEl.addEventListener('touchstart', moveStart, { passive: true }); | ||||
| 	rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない | ||||
| 	if (rootEl.value == null) return; | ||||
| 	rootEl.value.addEventListener('touchstart', moveStart, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない | ||||
| } | ||||
|  | ||||
| function unregisterEventListenersForReadyToPull() { | ||||
| 	if (rootEl == null) return; | ||||
| 	rootEl.removeEventListener('touchstart', moveStart); | ||||
| 	rootEl.removeEventListener('touchmove', moving); | ||||
| 	if (rootEl.value == null) return; | ||||
| 	rootEl.value.removeEventListener('touchstart', moveStart); | ||||
| 	rootEl.value.removeEventListener('touchmove', moving); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (rootEl == null) return; | ||||
| 	if (rootEl.value == null) return; | ||||
|  | ||||
| 	scrollEl = getScrollContainer(rootEl); | ||||
| 	scrollEl = getScrollContainer(rootEl.value); | ||||
| 	if (scrollEl == null) return; | ||||
|  | ||||
| 	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true }); | ||||
|  | ||||
| 	rootEl.addEventListener('touchend', moveEnd, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchend', moveEnd, { passive: true }); | ||||
|  | ||||
| 	registerEventListenersForReadyToPull(); | ||||
| }); | ||||
|   | ||||
| @@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from 'vue'; | ||||
| import { $i, getAccounts } from '@/account.js'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import { instance } from '@/instance.js'; | ||||
| @@ -62,26 +63,26 @@ defineProps<{ | ||||
| }>(); | ||||
|  | ||||
| // ServiceWorker registration | ||||
| let registration = $ref<ServiceWorkerRegistration | undefined>(); | ||||
| const registration = ref<ServiceWorkerRegistration | undefined>(); | ||||
| // If this browser supports push notification | ||||
| let supported = $ref(false); | ||||
| const supported = ref(false); | ||||
| // If this browser has already subscribed to push notification | ||||
| let pushSubscription = $ref<PushSubscription | null>(null); | ||||
| let pushRegistrationInServer = $ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>(); | ||||
| const pushSubscription = ref<PushSubscription | null>(null); | ||||
| const pushRegistrationInServer = ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>(); | ||||
|  | ||||
| function subscribe() { | ||||
| 	if (!registration || !supported || !instance.swPublickey) return; | ||||
| 	if (!registration.value || !supported.value || !instance.swPublickey) return; | ||||
|  | ||||
| 	// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters | ||||
| 	return promiseDialog(registration.pushManager.subscribe({ | ||||
| 	return promiseDialog(registration.value.pushManager.subscribe({ | ||||
| 		userVisibleOnly: true, | ||||
| 		applicationServerKey: urlBase64ToUint8Array(instance.swPublickey), | ||||
| 	}) | ||||
| 		.then(async subscription => { | ||||
| 			pushSubscription = subscription; | ||||
| 			pushSubscription.value = subscription; | ||||
|  | ||||
| 			// Register | ||||
| 			pushRegistrationInServer = await api('sw/register', { | ||||
| 			pushRegistrationInServer.value = await api('sw/register', { | ||||
| 				endpoint: subscription.endpoint, | ||||
| 				auth: encode(subscription.getKey('auth')), | ||||
| 				publickey: encode(subscription.getKey('p256dh')), | ||||
| @@ -102,12 +103,12 @@ function subscribe() { | ||||
| } | ||||
|  | ||||
| async function unsubscribe() { | ||||
| 	if (!pushSubscription) return; | ||||
| 	if (!pushSubscription.value) return; | ||||
|  | ||||
| 	const endpoint = pushSubscription.endpoint; | ||||
| 	const endpoint = pushSubscription.value.endpoint; | ||||
| 	const accounts = await getAccounts(); | ||||
|  | ||||
| 	pushRegistrationInServer = undefined; | ||||
| 	pushRegistrationInServer.value = undefined; | ||||
|  | ||||
| 	if ($i && accounts.length >= 2) { | ||||
| 		apiWithDialog('sw/unregister', { | ||||
| @@ -115,11 +116,11 @@ async function unsubscribe() { | ||||
| 			endpoint, | ||||
| 		}); | ||||
| 	} else { | ||||
| 		pushSubscription.unsubscribe(); | ||||
| 		pushSubscription.value.unsubscribe(); | ||||
| 		apiWithDialog('sw/unregister', { | ||||
| 			endpoint, | ||||
| 		}); | ||||
| 		pushSubscription = null; | ||||
| 		pushSubscription.value = null; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -150,20 +151,20 @@ if (navigator.serviceWorker == null) { | ||||
| 	// TODO: よしなに? | ||||
| } else { | ||||
| 	navigator.serviceWorker.ready.then(async swr => { | ||||
| 		registration = swr; | ||||
| 		registration.value = swr; | ||||
|  | ||||
| 		pushSubscription = await registration.pushManager.getSubscription(); | ||||
| 		pushSubscription.value = await registration.value.pushManager.getSubscription(); | ||||
|  | ||||
| 		if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { | ||||
| 			supported = true; | ||||
| 			supported.value = true; | ||||
|  | ||||
| 			if (pushSubscription) { | ||||
| 			if (pushSubscription.value) { | ||||
| 				const res = await api('sw/show-registration', { | ||||
| 					endpoint: pushSubscription.endpoint, | ||||
| 					endpoint: pushSubscription.value.endpoint, | ||||
| 				}); | ||||
|  | ||||
| 				if (res) { | ||||
| 					pushRegistrationInServer = res; | ||||
| 					pushRegistrationInServer.value = res; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @@ -171,6 +172,6 @@ if (navigator.serviceWorker == null) { | ||||
| } | ||||
|  | ||||
| defineExpose({ | ||||
| 	pushRegistrationInServer: $$(pushRegistrationInServer), | ||||
| 	pushRegistrationInServer: pushRegistrationInServer, | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { computed } from 'vue'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	modelValue: any; | ||||
| @@ -36,7 +36,7 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'update:modelValue', value: any): void; | ||||
| }>(); | ||||
|  | ||||
| let checked = $computed(() => props.modelValue === props.value); | ||||
| const checked = computed(() => props.modelValue === props.value); | ||||
|  | ||||
| function toggle(): void { | ||||
| 	if (props.disabled) return; | ||||
|   | ||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import MkReactionIcon from '@/components/MkReactionIcon.vue'; | ||||
|  | ||||
| @@ -27,13 +27,13 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'end'): void; | ||||
| }>(); | ||||
|  | ||||
| let up = $ref(false); | ||||
| const up = ref(false); | ||||
| const zIndex = os.claimZIndex('middle'); | ||||
| const angle = (90 - (Math.random() * 180)) + 'deg'; | ||||
|  | ||||
| onMounted(() => { | ||||
| 	window.setTimeout(() => { | ||||
| 		up = true; | ||||
| 		up.value = true; | ||||
| 	}, 10); | ||||
|  | ||||
| 	window.setTimeout(() => { | ||||
|   | ||||
| @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { inject, watch } from 'vue'; | ||||
| import { inject, watch, ref } from 'vue'; | ||||
| import XReaction from '@/components/MkReactionsViewer.reaction.vue'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
|  | ||||
| @@ -38,31 +38,31 @@ const emit = defineEmits<{ | ||||
|  | ||||
| const initialReactions = new Set(Object.keys(props.note.reactions)); | ||||
|  | ||||
| let reactions = $ref<[string, number][]>([]); | ||||
| let hasMoreReactions = $ref(false); | ||||
| const reactions = ref<[string, number][]>([]); | ||||
| const hasMoreReactions = ref(false); | ||||
|  | ||||
| if (props.note.myReaction && !Object.keys(reactions).includes(props.note.myReaction)) { | ||||
| 	reactions[props.note.myReaction] = props.note.reactions[props.note.myReaction]; | ||||
| if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) { | ||||
| 	reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction]; | ||||
| } | ||||
|  | ||||
| function onMockToggleReaction(emoji: string, count: number) { | ||||
| 	if (!mock) return; | ||||
|  | ||||
| 	const i = reactions.findIndex((item) => item[0] === emoji); | ||||
| 	const i = reactions.value.findIndex((item) => item[0] === emoji); | ||||
| 	if (i < 0) return; | ||||
|  | ||||
| 	emit('mockUpdateMyReaction', emoji, (count - reactions[i][1])); | ||||
| 	emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1])); | ||||
| } | ||||
|  | ||||
| watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { | ||||
| 	let newReactions: [string, number][] = []; | ||||
| 	hasMoreReactions = Object.keys(newSource).length > maxNumber; | ||||
| 	hasMoreReactions.value = Object.keys(newSource).length > maxNumber; | ||||
|  | ||||
| 	for (let i = 0; i < reactions.length; i++) { | ||||
| 		const reaction = reactions[i][0]; | ||||
| 	for (let i = 0; i < reactions.value.length; i++) { | ||||
| 		const reaction = reactions.value[i][0]; | ||||
| 		if (reaction in newSource && newSource[reaction] !== 0) { | ||||
| 			reactions[i][1] = newSource[reaction]; | ||||
| 			newReactions.push(reactions[i]); | ||||
| 			reactions.value[i][1] = newSource[reaction]; | ||||
| 			newReactions.push(reactions.value[i]); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -80,7 +80,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe | ||||
| 		newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]); | ||||
| 	} | ||||
|  | ||||
| 	reactions = newReactions; | ||||
| 	reactions.value = newReactions; | ||||
| }, { immediate: true, deep: true }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, nextTick } from 'vue'; | ||||
| import { onMounted, nextTick, shallowRef, ref } from 'vue'; | ||||
| import { Chart } from 'chart.js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -23,11 +23,11 @@ import { initChart } from '@/scripts/init-chart.js'; | ||||
|  | ||||
| initChart(); | ||||
|  | ||||
| const rootEl = $shallowRef<HTMLDivElement>(null); | ||||
| const chartEl = $shallowRef<HTMLCanvasElement>(null); | ||||
| const rootEl = shallowRef<HTMLDivElement>(null); | ||||
| const chartEl = shallowRef<HTMLCanvasElement>(null); | ||||
| const now = new Date(); | ||||
| let chartInstance: Chart = null; | ||||
| let fetching = $ref(true); | ||||
| const fetching = ref(true); | ||||
|  | ||||
| const { handler: externalTooltipHandler } = useChartTooltip({ | ||||
| 	position: 'middle', | ||||
| @@ -38,8 +38,8 @@ async function renderChart() { | ||||
| 		chartInstance.destroy(); | ||||
| 	} | ||||
|  | ||||
| 	const wide = rootEl.offsetWidth > 600; | ||||
| 	const narrow = rootEl.offsetWidth < 400; | ||||
| 	const wide = rootEl.value.offsetWidth > 600; | ||||
| 	const narrow = rootEl.value.offsetWidth < 400; | ||||
|  | ||||
| 	const maxDays = wide ? 10 : narrow ? 5 : 7; | ||||
|  | ||||
| @@ -66,7 +66,7 @@ async function renderChart() { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	fetching = false; | ||||
| 	fetching.value = false; | ||||
|  | ||||
| 	await nextTick(); | ||||
|  | ||||
| @@ -83,7 +83,7 @@ async function renderChart() { | ||||
|  | ||||
| 	const marginEachCell = 12; | ||||
|  | ||||
| 	chartInstance = new Chart(chartEl, { | ||||
| 	chartInstance = new Chart(chartEl.value, { | ||||
| 		type: 'matrix', | ||||
| 		data: { | ||||
| 			datasets: [{ | ||||
|   | ||||
| @@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { defineAsyncComponent } from 'vue'; | ||||
| import { defineAsyncComponent, ref } from 'vue'; | ||||
| import { toUnicode } from 'punycode/'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; | ||||
| @@ -62,17 +62,17 @@ import * as os from '@/os.js'; | ||||
| import { login } from '@/account.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| let signing = $ref(false); | ||||
| let user = $ref<Misskey.entities.UserDetailed | null>(null); | ||||
| let username = $ref(''); | ||||
| let password = $ref(''); | ||||
| let token = $ref(''); | ||||
| let host = $ref(toUnicode(configHost)); | ||||
| let totpLogin = $ref(false); | ||||
| let queryingKey = $ref(false); | ||||
| let credentialRequest = $ref<CredentialRequestOptions | null>(null); | ||||
| let hCaptchaResponse = $ref(null); | ||||
| let reCaptchaResponse = $ref(null); | ||||
| const signing = ref(false); | ||||
| const user = ref<Misskey.entities.UserDetailed | null>(null); | ||||
| const username = ref(''); | ||||
| const password = ref(''); | ||||
| const token = ref(''); | ||||
| const host = ref(toUnicode(configHost)); | ||||
| const totpLogin = ref(false); | ||||
| const queryingKey = ref(false); | ||||
| const credentialRequest = ref<CredentialRequestOptions | null>(null); | ||||
| const hCaptchaResponse = ref(null); | ||||
| const reCaptchaResponse = ref(null); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'login', v: any): void; | ||||
| @@ -98,11 +98,11 @@ const props = defineProps({ | ||||
|  | ||||
| function onUsernameChange(): void { | ||||
| 	os.api('users/show', { | ||||
| 		username: username, | ||||
| 		username: username.value, | ||||
| 	}).then(userResponse => { | ||||
| 		user = userResponse; | ||||
| 		user.value = userResponse; | ||||
| 	}, () => { | ||||
| 		user = null; | ||||
| 		user.value = null; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @@ -113,21 +113,21 @@ function onLogin(res: any): Promise<void> | void { | ||||
| } | ||||
|  | ||||
| async function queryKey(): Promise<void> { | ||||
| 	queryingKey = true; | ||||
| 	await webAuthnRequest(credentialRequest) | ||||
| 	queryingKey.value = true; | ||||
| 	await webAuthnRequest(credentialRequest.value) | ||||
| 		.catch(() => { | ||||
| 			queryingKey = false; | ||||
| 			queryingKey.value = false; | ||||
| 			return Promise.reject(null); | ||||
| 		}).then(credential => { | ||||
| 			credentialRequest = null; | ||||
| 			queryingKey = false; | ||||
| 			signing = true; | ||||
| 			credentialRequest.value = null; | ||||
| 			queryingKey.value = false; | ||||
| 			signing.value = true; | ||||
| 			return os.api('signin', { | ||||
| 				username, | ||||
| 				password, | ||||
| 				username: username.value, | ||||
| 				password: password.value, | ||||
| 				credential: credential.toJSON(), | ||||
| 				'hcaptcha-response': hCaptchaResponse, | ||||
| 				'g-recaptcha-response': reCaptchaResponse, | ||||
| 				'hcaptcha-response': hCaptchaResponse.value, | ||||
| 				'g-recaptcha-response': reCaptchaResponse.value, | ||||
| 			}); | ||||
| 		}).then(res => { | ||||
| 			emit('login', res); | ||||
| @@ -138,39 +138,39 @@ async function queryKey(): Promise<void> { | ||||
| 				type: 'error', | ||||
| 				text: i18n.ts.signinFailed, | ||||
| 			}); | ||||
| 			signing = false; | ||||
| 			signing.value = false; | ||||
| 		}); | ||||
| } | ||||
|  | ||||
| function onSubmit(): void { | ||||
| 	signing = true; | ||||
| 	if (!totpLogin && user && user.twoFactorEnabled) { | ||||
| 		if (webAuthnSupported() && user.securityKeys) { | ||||
| 	signing.value = true; | ||||
| 	if (!totpLogin.value && user.value && user.value.twoFactorEnabled) { | ||||
| 		if (webAuthnSupported() && user.value.securityKeys) { | ||||
| 			os.api('signin', { | ||||
| 				username, | ||||
| 				password, | ||||
| 				'hcaptcha-response': hCaptchaResponse, | ||||
| 				'g-recaptcha-response': reCaptchaResponse, | ||||
| 				username: username.value, | ||||
| 				password: password.value, | ||||
| 				'hcaptcha-response': hCaptchaResponse.value, | ||||
| 				'g-recaptcha-response': reCaptchaResponse.value, | ||||
| 			}).then(res => { | ||||
| 				totpLogin = true; | ||||
| 				signing = false; | ||||
| 				credentialRequest = parseRequestOptionsFromJSON({ | ||||
| 				totpLogin.value = true; | ||||
| 				signing.value = false; | ||||
| 				credentialRequest.value = parseRequestOptionsFromJSON({ | ||||
| 					publicKey: res, | ||||
| 				}); | ||||
| 			}) | ||||
| 				.then(() => queryKey()) | ||||
| 				.catch(loginFailed); | ||||
| 		} else { | ||||
| 			totpLogin = true; | ||||
| 			signing = false; | ||||
| 			totpLogin.value = true; | ||||
| 			signing.value = false; | ||||
| 		} | ||||
| 	} else { | ||||
| 		os.api('signin', { | ||||
| 			username, | ||||
| 			password, | ||||
| 			'hcaptcha-response': hCaptchaResponse, | ||||
| 			'g-recaptcha-response': reCaptchaResponse, | ||||
| 			token: user?.twoFactorEnabled ? token : undefined, | ||||
| 			username: username.value, | ||||
| 			password: password.value, | ||||
| 			'hcaptcha-response': hCaptchaResponse.value, | ||||
| 			'g-recaptcha-response': reCaptchaResponse.value, | ||||
| 			token: user.value?.twoFactorEnabled ? token.value : undefined, | ||||
| 		}).then(res => { | ||||
| 			emit('login', res); | ||||
| 			onLogin(res); | ||||
| @@ -218,8 +218,8 @@ function loginFailed(err: any): void { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	totpLogin = false; | ||||
| 	signing = false; | ||||
| 	totpLogin.value = false; | ||||
| 	signing.value = false; | ||||
| } | ||||
|  | ||||
| function resetPassword(): void { | ||||
|   | ||||
| @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { shallowRef } from 'vue'; | ||||
| import MkSignin from '@/components/MkSignin.vue'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| @@ -39,15 +39,15 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'cancelled'): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| function onClose() { | ||||
| 	emit('cancelled'); | ||||
| 	if (dialog) dialog.close(); | ||||
| 	if (dialog.value) dialog.value.close(); | ||||
| } | ||||
|  | ||||
| function onLogin(res) { | ||||
| 	emit('done', res); | ||||
| 	if (dialog) dialog.close(); | ||||
| 	if (dialog.value) dialog.value.close(); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref, computed } from 'vue'; | ||||
| import { toUnicode } from 'punycode/'; | ||||
| import MkButton from './MkButton.vue'; | ||||
| import MkInput from './MkInput.vue'; | ||||
| @@ -101,34 +101,34 @@ const emit = defineEmits<{ | ||||
|  | ||||
| const host = toUnicode(config.host); | ||||
|  | ||||
| let hcaptcha = $ref<Captcha | undefined>(); | ||||
| let recaptcha = $ref<Captcha | undefined>(); | ||||
| let turnstile = $ref<Captcha | undefined>(); | ||||
| const hcaptcha = ref<Captcha | undefined>(); | ||||
| const recaptcha = ref<Captcha | undefined>(); | ||||
| const turnstile = ref<Captcha | undefined>(); | ||||
|  | ||||
| let username: string = $ref(''); | ||||
| let password: string = $ref(''); | ||||
| let retypedPassword: string = $ref(''); | ||||
| let invitationCode: string = $ref(''); | ||||
| let email = $ref(''); | ||||
| let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null); | ||||
| let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null); | ||||
| let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref(''); | ||||
| let passwordRetypeState: null | 'match' | 'not-match' = $ref(null); | ||||
| let submitting: boolean = $ref(false); | ||||
| let hCaptchaResponse = $ref(null); | ||||
| let reCaptchaResponse = $ref(null); | ||||
| let turnstileResponse = $ref(null); | ||||
| let usernameAbortController: null | AbortController = $ref(null); | ||||
| let emailAbortController: null | AbortController = $ref(null); | ||||
| const username = ref<string>(''); | ||||
| const password = ref<string>(''); | ||||
| const retypedPassword = ref<string>(''); | ||||
| const invitationCode = ref<string>(''); | ||||
| const email = ref(''); | ||||
| const usernameState = ref<null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range'>(null); | ||||
| const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error'>(null); | ||||
| const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>(''); | ||||
| const passwordRetypeState = ref<null | 'match' | 'not-match'>(null); | ||||
| const submitting = ref<boolean>(false); | ||||
| const hCaptchaResponse = ref(null); | ||||
| const reCaptchaResponse = ref(null); | ||||
| const turnstileResponse = ref(null); | ||||
| const usernameAbortController = ref<null | AbortController>(null); | ||||
| const emailAbortController = ref<null | AbortController>(null); | ||||
|  | ||||
| const shouldDisableSubmitting = $computed((): boolean => { | ||||
| 	return submitting || | ||||
| 		instance.enableHcaptcha && !hCaptchaResponse || | ||||
| 		instance.enableRecaptcha && !reCaptchaResponse || | ||||
| 		instance.enableTurnstile && !turnstileResponse || | ||||
| 		instance.emailRequiredForSignup && emailState !== 'ok' || | ||||
| 		usernameState !== 'ok' || | ||||
| 		passwordRetypeState !== 'match'; | ||||
| const shouldDisableSubmitting = computed((): boolean => { | ||||
| 	return submitting.value || | ||||
| 		instance.enableHcaptcha && !hCaptchaResponse.value || | ||||
| 		instance.enableRecaptcha && !reCaptchaResponse.value || | ||||
| 		instance.enableTurnstile && !turnstileResponse.value || | ||||
| 		instance.emailRequiredForSignup && emailState.value !== 'ok' || | ||||
| 		usernameState.value !== 'ok' || | ||||
| 		passwordRetypeState.value !== 'match'; | ||||
| }); | ||||
|  | ||||
| function getPasswordStrength(source: string): number { | ||||
| @@ -156,57 +156,57 @@ function getPasswordStrength(source: string): number { | ||||
| } | ||||
|  | ||||
| function onChangeUsername(): void { | ||||
| 	if (username === '') { | ||||
| 		usernameState = null; | ||||
| 	if (username.value === '') { | ||||
| 		usernameState.value = null; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		const err = | ||||
| 			!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : | ||||
| 			username.length < 1 ? 'min-range' : | ||||
| 			username.length > 20 ? 'max-range' : | ||||
| 			!username.value.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : | ||||
| 			username.value.length < 1 ? 'min-range' : | ||||
| 			username.value.length > 20 ? 'max-range' : | ||||
| 			null; | ||||
|  | ||||
| 		if (err) { | ||||
| 			usernameState = err; | ||||
| 			usernameState.value = err; | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (usernameAbortController != null) { | ||||
| 		usernameAbortController.abort(); | ||||
| 	if (usernameAbortController.value != null) { | ||||
| 		usernameAbortController.value.abort(); | ||||
| 	} | ||||
| 	usernameState = 'wait'; | ||||
| 	usernameAbortController = new AbortController(); | ||||
| 	usernameState.value = 'wait'; | ||||
| 	usernameAbortController.value = new AbortController(); | ||||
|  | ||||
| 	os.api('username/available', { | ||||
| 		username, | ||||
| 	}, undefined, usernameAbortController.signal).then(result => { | ||||
| 		usernameState = result.available ? 'ok' : 'unavailable'; | ||||
| 		username: username.value, | ||||
| 	}, undefined, usernameAbortController.value.signal).then(result => { | ||||
| 		usernameState.value = result.available ? 'ok' : 'unavailable'; | ||||
| 	}).catch((err) => { | ||||
| 		if (err.name !== 'AbortError') { | ||||
| 			usernameState = 'error'; | ||||
| 			usernameState.value = 'error'; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function onChangeEmail(): void { | ||||
| 	if (email === '') { | ||||
| 		emailState = null; | ||||
| 	if (email.value === '') { | ||||
| 		emailState.value = null; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (emailAbortController != null) { | ||||
| 		emailAbortController.abort(); | ||||
| 	if (emailAbortController.value != null) { | ||||
| 		emailAbortController.value.abort(); | ||||
| 	} | ||||
| 	emailState = 'wait'; | ||||
| 	emailAbortController = new AbortController(); | ||||
| 	emailState.value = 'wait'; | ||||
| 	emailAbortController.value = new AbortController(); | ||||
|  | ||||
| 	os.api('email-address/available', { | ||||
| 		emailAddress: email, | ||||
| 	}, undefined, emailAbortController.signal).then(result => { | ||||
| 		emailState = result.available ? 'ok' : | ||||
| 		emailAddress: email.value, | ||||
| 	}, undefined, emailAbortController.value.signal).then(result => { | ||||
| 		emailState.value = result.available ? 'ok' : | ||||
| 			result.reason === 'used' ? 'unavailable:used' : | ||||
| 			result.reason === 'format' ? 'unavailable:format' : | ||||
| 			result.reason === 'disposable' ? 'unavailable:disposable' : | ||||
| @@ -215,55 +215,55 @@ function onChangeEmail(): void { | ||||
| 			'unavailable'; | ||||
| 	}).catch((err) => { | ||||
| 		if (err.name !== 'AbortError') { | ||||
| 			emailState = 'error'; | ||||
| 			emailState.value = 'error'; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function onChangePassword(): void { | ||||
| 	if (password === '') { | ||||
| 		passwordStrength = ''; | ||||
| 	if (password.value === '') { | ||||
| 		passwordStrength.value = ''; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const strength = getPasswordStrength(password); | ||||
| 	passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; | ||||
| 	const strength = getPasswordStrength(password.value); | ||||
| 	passwordStrength.value = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; | ||||
| } | ||||
|  | ||||
| function onChangePasswordRetype(): void { | ||||
| 	if (retypedPassword === '') { | ||||
| 		passwordRetypeState = null; | ||||
| 	if (retypedPassword.value === '') { | ||||
| 		passwordRetypeState.value = null; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	passwordRetypeState = password === retypedPassword ? 'match' : 'not-match'; | ||||
| 	passwordRetypeState.value = password.value === retypedPassword.value ? 'match' : 'not-match'; | ||||
| } | ||||
|  | ||||
| async function onSubmit(): Promise<void> { | ||||
| 	if (submitting) return; | ||||
| 	submitting = true; | ||||
| 	if (submitting.value) return; | ||||
| 	submitting.value = true; | ||||
|  | ||||
| 	try { | ||||
| 		await os.api('signup', { | ||||
| 			username, | ||||
| 			password, | ||||
| 			emailAddress: email, | ||||
| 			invitationCode, | ||||
| 			'hcaptcha-response': hCaptchaResponse, | ||||
| 			'g-recaptcha-response': reCaptchaResponse, | ||||
| 			'turnstile-response': turnstileResponse, | ||||
| 			username: username.value, | ||||
| 			password: password.value, | ||||
| 			emailAddress: email.value, | ||||
| 			invitationCode: invitationCode.value, | ||||
| 			'hcaptcha-response': hCaptchaResponse.value, | ||||
| 			'g-recaptcha-response': reCaptchaResponse.value, | ||||
| 			'turnstile-response': turnstileResponse.value, | ||||
| 		}); | ||||
| 		if (instance.emailRequiredForSignup) { | ||||
| 			os.alert({ | ||||
| 				type: 'success', | ||||
| 				title: i18n.ts._signup.almostThere, | ||||
| 				text: i18n.t('_signup.emailSent', { email }), | ||||
| 				text: i18n.t('_signup.emailSent', { email: email.value }), | ||||
| 			}); | ||||
| 			emit('signupEmailPending'); | ||||
| 		} else { | ||||
| 			const res = await os.api('signin', { | ||||
| 				username, | ||||
| 				password, | ||||
| 				username: username.value, | ||||
| 				password: password.value, | ||||
| 			}); | ||||
| 			emit('signup', res); | ||||
|  | ||||
| @@ -272,10 +272,10 @@ async function onSubmit(): Promise<void> { | ||||
| 			} | ||||
| 		} | ||||
| 	} catch { | ||||
| 		submitting = false; | ||||
| 		hcaptcha?.reset?.(); | ||||
| 		recaptcha?.reset?.(); | ||||
| 		turnstile?.reset?.(); | ||||
| 		submitting.value = false; | ||||
| 		hcaptcha.value?.reset?.(); | ||||
| 		recaptcha.value?.reset?.(); | ||||
| 		turnstile.value?.reset?.(); | ||||
|  | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
|   | ||||
| @@ -33,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { $ref } from 'vue/macros'; | ||||
| import { shallowRef, ref } from 'vue'; | ||||
|  | ||||
| import XSignup from '@/components/MkSignupDialog.form.vue'; | ||||
| import XServerRules from '@/components/MkSignupDialog.rules.vue'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| @@ -52,17 +52,17 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| const isAcceptedServerRule = $ref(false); | ||||
| const isAcceptedServerRule = ref(false); | ||||
|  | ||||
| function onSignup(res) { | ||||
| 	emit('done', res); | ||||
| 	dialog.close(); | ||||
| 	dialog.value.close(); | ||||
| } | ||||
|  | ||||
| function onSignupEmailPending() { | ||||
| 	dialog.close(); | ||||
| 	dialog.value.close(); | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkMediaList from '@/components/MkMediaList.vue'; | ||||
| import MkPoll from '@/components/MkPoll.vue'; | ||||
| @@ -44,7 +44,7 @@ const props = defineProps<{ | ||||
|  | ||||
| const isLong = shouldCollapsed(props.note, []); | ||||
|  | ||||
| const collapsed = $ref(isLong); | ||||
| const collapsed = ref(isLong); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
|   | ||||
| @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, watch, onBeforeUnmount } from 'vue'; | ||||
| import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
|  | ||||
| const loaded = !!window.TagCanvas; | ||||
| @@ -23,13 +23,13 @@ const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz'; | ||||
| const computedStyle = getComputedStyle(document.documentElement); | ||||
| const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); | ||||
| const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); | ||||
| let available = $ref(false); | ||||
| let rootEl = $shallowRef<HTMLElement | null>(null); | ||||
| let canvasEl = $shallowRef<HTMLCanvasElement | null>(null); | ||||
| let tagsEl = $shallowRef<HTMLElement | null>(null); | ||||
| let width = $ref(300); | ||||
| const available = ref(false); | ||||
| const rootEl = shallowRef<HTMLElement | null>(null); | ||||
| const canvasEl = shallowRef<HTMLCanvasElement | null>(null); | ||||
| const tagsEl = shallowRef<HTMLElement | null>(null); | ||||
| const width = ref(300); | ||||
|  | ||||
| watch($$(available), () => { | ||||
| watch(available, () => { | ||||
| 	try { | ||||
| 		window.TagCanvas.Start(idForCanvas, idForTags, { | ||||
| 			textColour: '#ffffff', | ||||
| @@ -52,15 +52,15 @@ watch($$(available), () => { | ||||
| }); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	width = rootEl.offsetWidth; | ||||
| 	width.value = rootEl.value.offsetWidth; | ||||
|  | ||||
| 	if (loaded) { | ||||
| 		available = true; | ||||
| 		available.value = true; | ||||
| 	} else { | ||||
| 		document.head.appendChild(Object.assign(document.createElement('script'), { | ||||
| 			async: true, | ||||
| 			src: '/client-assets/tagcanvas.min.js', | ||||
| 		})).addEventListener('load', () => available = true); | ||||
| 		})).addEventListener('load', () => available.value = true); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed, watch, onUnmounted, provide } from 'vue'; | ||||
| import { computed, watch, onUnmounted, provide, ref } from 'vue'; | ||||
| import { Connection } from 'misskey-js/built/streaming.js'; | ||||
| import MkNotes from '@/components/MkNotes.vue'; | ||||
| import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; | ||||
| @@ -62,8 +62,8 @@ type TimelineQueryType = { | ||||
|   roleId?: string | ||||
| } | ||||
|  | ||||
| const prComponent: InstanceType<typeof MkPullToRefresh> = $ref(); | ||||
| const tlComponent: InstanceType<typeof MkNotes> = $ref(); | ||||
| const prComponent = ref<InstanceType<typeof MkPullToRefresh>>(); | ||||
| const tlComponent = ref<InstanceType<typeof MkNotes>>(); | ||||
|  | ||||
| let tlNotesCount = 0; | ||||
|  | ||||
| @@ -74,7 +74,7 @@ const prepend = note => { | ||||
| 		note._shouldInsertAd_ = true; | ||||
| 	} | ||||
|  | ||||
| 	tlComponent.pagingComponent?.prepend(note); | ||||
| 	tlComponent.value.pagingComponent?.prepend(note); | ||||
|  | ||||
| 	emit('note'); | ||||
|  | ||||
| @@ -248,7 +248,7 @@ function reloadTimeline() { | ||||
| 	return new Promise<void>((res) => { | ||||
| 		tlNotesCount = 0; | ||||
|  | ||||
| 		tlComponent.pagingComponent?.reload().then(() => { | ||||
| 		tlComponent.value.pagingComponent?.reload().then(() => { | ||||
| 			res(); | ||||
| 		}); | ||||
| 	}); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
|  | ||||
| @@ -35,11 +35,11 @@ const emit = defineEmits<{ | ||||
| }>(); | ||||
|  | ||||
| const zIndex = os.claimZIndex('high'); | ||||
| let showing = $ref(true); | ||||
| const showing = ref(true); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	window.setTimeout(() => { | ||||
| 		showing = false; | ||||
| 		showing.value = false; | ||||
| 	}, 4000); | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { shallowRef, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkInput from './MkInput.vue'; | ||||
| import MkSwitch from './MkSwitch.vue'; | ||||
| @@ -67,37 +67,37 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'done', result: { name: string | null, permissions: string[] }): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| let name = $ref(props.initialName); | ||||
| let permissions = $ref({}); | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
| const name = ref(props.initialName); | ||||
| const permissions = ref({}); | ||||
|  | ||||
| if (props.initialPermissions) { | ||||
| 	for (const kind of props.initialPermissions) { | ||||
| 		permissions[kind] = true; | ||||
| 		permissions.value[kind] = true; | ||||
| 	} | ||||
| } else { | ||||
| 	for (const kind of Misskey.permissions) { | ||||
| 		permissions[kind] = false; | ||||
| 		permissions.value[kind] = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function ok(): void { | ||||
| 	emit('done', { | ||||
| 		name: name, | ||||
| 		permissions: Object.keys(permissions).filter(p => permissions[p]), | ||||
| 		name: name.value, | ||||
| 		permissions: Object.keys(permissions.value).filter(p => permissions.value[p]), | ||||
| 	}); | ||||
| 	dialog.close(); | ||||
| 	dialog.value.close(); | ||||
| } | ||||
|  | ||||
| function disableAll(): void { | ||||
| 	for (const p in permissions) { | ||||
| 		permissions[p] = false; | ||||
| 	for (const p in permissions.value) { | ||||
| 		permissions.value[p] = false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function enableAll(): void { | ||||
| 	for (const p in permissions) { | ||||
| 		permissions[p] = true; | ||||
| 	for (const p in permissions.value) { | ||||
| 		permissions.value[p] = true; | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { defineAsyncComponent, onUnmounted } from 'vue'; | ||||
| import { defineAsyncComponent, onUnmounted, ref } from 'vue'; | ||||
| import type { summaly } from 'summaly'; | ||||
| import { url as local } from '@/config.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| @@ -107,36 +107,36 @@ const props = withDefaults(defineProps<{ | ||||
| }); | ||||
|  | ||||
| const MOBILE_THRESHOLD = 500; | ||||
| const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); | ||||
| const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); | ||||
|  | ||||
| const self = props.url.startsWith(local); | ||||
| const attr = self ? 'to' : 'href'; | ||||
| const target = self ? null : '_blank'; | ||||
| let fetching = $ref(true); | ||||
| let title = $ref<string | null>(null); | ||||
| let description = $ref<string | null>(null); | ||||
| let thumbnail = $ref<string | null>(null); | ||||
| let icon = $ref<string | null>(null); | ||||
| let sitename = $ref<string | null>(null); | ||||
| let sensitive = $ref<boolean>(false); | ||||
| let player = $ref({ | ||||
| const fetching = ref(true); | ||||
| const title = ref<string | null>(null); | ||||
| const description = ref<string | null>(null); | ||||
| const thumbnail = ref<string | null>(null); | ||||
| const icon = ref<string | null>(null); | ||||
| const sitename = ref<string | null>(null); | ||||
| const sensitive = ref<boolean>(false); | ||||
| const player = ref({ | ||||
| 	url: null, | ||||
| 	width: null, | ||||
| 	height: null, | ||||
| } as SummalyResult['player']); | ||||
| let playerEnabled = $ref(false); | ||||
| let tweetId = $ref<string | null>(null); | ||||
| let tweetExpanded = $ref(props.detail); | ||||
| const playerEnabled = ref(false); | ||||
| const tweetId = ref<string | null>(null); | ||||
| const tweetExpanded = ref(props.detail); | ||||
| const embedId = `embed${Math.random().toString().replace(/\D/, '')}`; | ||||
| let tweetHeight = $ref(150); | ||||
| let unknownUrl = $ref(false); | ||||
| const tweetHeight = ref(150); | ||||
| const unknownUrl = ref(false); | ||||
|  | ||||
| const requestUrl = new URL(props.url); | ||||
| if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url'); | ||||
|  | ||||
| if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com' || requestUrl.hostname === 'x.com' || requestUrl.hostname === 'mobile.x.com') { | ||||
| 	const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); | ||||
| 	if (m) tweetId = m[1]; | ||||
| 	if (m) tweetId.value = m[1]; | ||||
| } | ||||
|  | ||||
| if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { | ||||
| @@ -148,8 +148,8 @@ requestUrl.hash = ''; | ||||
| window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`) | ||||
| 	.then(res => { | ||||
| 		if (!res.ok) { | ||||
| 			fetching = false; | ||||
| 			unknownUrl = true; | ||||
| 			fetching.value = false; | ||||
| 			unknownUrl.value = true; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| @@ -157,21 +157,21 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa | ||||
| 	}) | ||||
| 	.then((info: SummalyResult) => { | ||||
| 		if (info.url == null) { | ||||
| 			fetching = false; | ||||
| 			unknownUrl = true; | ||||
| 			fetching.value = false; | ||||
| 			unknownUrl.value = true; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		fetching = false; | ||||
| 		unknownUrl = false; | ||||
| 		fetching.value = false; | ||||
| 		unknownUrl.value = false; | ||||
|  | ||||
| 		title = info.title; | ||||
| 		description = info.description; | ||||
| 		thumbnail = info.thumbnail; | ||||
| 		icon = info.icon; | ||||
| 		sitename = info.sitename; | ||||
| 		player = info.player; | ||||
| 		sensitive = info.sensitive ?? false; | ||||
| 		title.value = info.title; | ||||
| 		description.value = info.description; | ||||
| 		thumbnail.value = info.thumbnail; | ||||
| 		icon.value = info.icon; | ||||
| 		sitename.value = info.sitename; | ||||
| 		player.value = info.player; | ||||
| 		sensitive.value = info.sensitive ?? false; | ||||
| 	}); | ||||
|  | ||||
| function adjustTweetHeight(message: any) { | ||||
| @@ -180,7 +180,7 @@ function adjustTweetHeight(message: any) { | ||||
| 	if (embed?.method !== 'twttr.private.resize') return; | ||||
| 	if (embed?.id !== embedId) return; | ||||
| 	const height = embed?.params[0]?.height; | ||||
| 	if (height) tweetHeight = height; | ||||
| 	if (height) tweetHeight.value = height; | ||||
| } | ||||
|  | ||||
| const openPlayer = (): void => { | ||||
|   | ||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import MkUrlPreview from '@/components/MkUrlPreview.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -28,16 +28,16 @@ const emit = defineEmits<{ | ||||
| }>(); | ||||
|  | ||||
| const zIndex = os.claimZIndex('middle'); | ||||
| let top = $ref(0); | ||||
| let left = $ref(0); | ||||
| const top = ref(0); | ||||
| const left = ref(0); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	const rect = props.source.getBoundingClientRect(); | ||||
| 	const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.pageXOffset; | ||||
| 	const y = rect.top + props.source.offsetHeight + window.pageYOffset; | ||||
|  | ||||
| 	top = y; | ||||
| 	left = x; | ||||
| 	top.value = y; | ||||
| 	left.value = x; | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| @@ -66,12 +66,12 @@ const props = defineProps<{ | ||||
| 	announcement?: any, | ||||
| }>(); | ||||
|  | ||||
| let dialog = $ref(null); | ||||
| let title: string = $ref(props.announcement ? props.announcement.title : ''); | ||||
| let text: string = $ref(props.announcement ? props.announcement.text : ''); | ||||
| let icon: string = $ref(props.announcement ? props.announcement.icon : 'info'); | ||||
| let display: string = $ref(props.announcement ? props.announcement.display : 'dialog'); | ||||
| let needConfirmationToRead = $ref(props.announcement ? props.announcement.needConfirmationToRead : false); | ||||
| const dialog = ref(null); | ||||
| const title = ref<string>(props.announcement ? props.announcement.title : ''); | ||||
| const text = ref<string>(props.announcement ? props.announcement.text : ''); | ||||
| const icon = ref<string>(props.announcement ? props.announcement.icon : 'info'); | ||||
| const display = ref<string>(props.announcement ? props.announcement.display : 'dialog'); | ||||
| const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, | ||||
| @@ -80,12 +80,12 @@ const emit = defineEmits<{ | ||||
|  | ||||
| async function done() { | ||||
| 	const params = { | ||||
| 		title: title, | ||||
| 		text: text, | ||||
| 		icon: icon, | ||||
| 		title: title.value, | ||||
| 		text: text.value, | ||||
| 		icon: icon.value, | ||||
| 		imageUrl: null, | ||||
| 		display: display, | ||||
| 		needConfirmationToRead: needConfirmationToRead, | ||||
| 		display: display.value, | ||||
| 		needConfirmationToRead: needConfirmationToRead.value, | ||||
| 		userId: props.user.id, | ||||
| 	}; | ||||
|  | ||||
| @@ -102,7 +102,7 @@ async function done() { | ||||
| 			}, | ||||
| 		}); | ||||
|  | ||||
| 		dialog.close(); | ||||
| 		dialog.value.close(); | ||||
| 	} else { | ||||
| 		const created = await os.apiWithDialog('admin/announcements/create', params); | ||||
|  | ||||
| @@ -110,14 +110,14 @@ async function done() { | ||||
| 			created: created, | ||||
| 		}); | ||||
|  | ||||
| 		dialog.close(); | ||||
| 		dialog.value.close(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async function del() { | ||||
| 	const { canceled } = await os.confirm({ | ||||
| 		type: 'warning', | ||||
| 		text: i18n.t('removeAreYouSure', { x: title }), | ||||
| 		text: i18n.t('removeAreYouSure', { x: title.value }), | ||||
| 	}); | ||||
| 	if (canceled) return; | ||||
|  | ||||
| @@ -127,7 +127,7 @@ async function del() { | ||||
| 		emit('done', { | ||||
| 			deleted: true, | ||||
| 		}); | ||||
| 		dialog.close(); | ||||
| 		dialog.value.close(); | ||||
| 	}); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import MkMiniChart from '@/components/MkMiniChart.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { acct } from '@/filters/user.js'; | ||||
| @@ -28,14 +28,14 @@ const props = withDefaults(defineProps<{ | ||||
| 	withChart: true, | ||||
| }); | ||||
|  | ||||
| let chartValues = $ref<number[] | null>(null); | ||||
| const chartValues = ref<number[] | null>(null); | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (props.withChart) { | ||||
| 		os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => { | ||||
| 			// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く | ||||
| 			res.inc.splice(0, 1); | ||||
| 			chartValues = res.inc; | ||||
| 			chartValues.value = res.inc; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { computed } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| @@ -24,7 +24,7 @@ const props = defineProps<{ | ||||
| 	user: Misskey.entities.User; | ||||
| }>(); | ||||
|  | ||||
| const text = $computed(() => { | ||||
| const text = computed(() => { | ||||
| 	switch (props.user.onlineStatus) { | ||||
| 		case 'online': return i18n.ts.online; | ||||
| 		case 'active': return i18n.ts.active; | ||||
|   | ||||
| @@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkFollowButton from '@/components/MkFollowButton.vue'; | ||||
| import { userPage } from '@/filters/user.js'; | ||||
| @@ -80,18 +80,18 @@ const emit = defineEmits<{ | ||||
| }>(); | ||||
|  | ||||
| const zIndex = os.claimZIndex('middle'); | ||||
| let user = $ref<Misskey.entities.UserDetailed | null>(null); | ||||
| let top = $ref(0); | ||||
| let left = $ref(0); | ||||
| const user = ref<Misskey.entities.UserDetailed | null>(null); | ||||
| const top = ref(0); | ||||
| const left = ref(0); | ||||
|  | ||||
| function showMenu(ev: MouseEvent) { | ||||
| 	const { menu, cleanup } = getUserMenu(user); | ||||
| 	const { menu, cleanup } = getUserMenu(user.value); | ||||
| 	os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
| 	if (typeof props.q === 'object') { | ||||
| 		user = props.q; | ||||
| 		user.value = props.q; | ||||
| 	} else { | ||||
| 		const query = props.q.startsWith('@') ? | ||||
| 			Misskey.acct.parse(props.q.substring(1)) : | ||||
| @@ -99,7 +99,7 @@ onMounted(() => { | ||||
|  | ||||
| 		os.api('users/show', query).then(res => { | ||||
| 			if (!props.showing) return; | ||||
| 			user = res; | ||||
| 			user.value = res; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| @@ -107,8 +107,8 @@ onMounted(() => { | ||||
| 	const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.pageXOffset; | ||||
| 	const y = rect.top + props.source.offsetHeight + window.pageYOffset; | ||||
|  | ||||
| 	top = y; | ||||
| 	left = x; | ||||
| 	top.value = y; | ||||
| 	left.value = x; | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| import FormSplit from '@/components/form/split.vue'; | ||||
| @@ -78,43 +78,43 @@ const props = defineProps<{ | ||||
| 	includeSelf?: boolean; | ||||
| }>(); | ||||
|  | ||||
| let username = $ref(''); | ||||
| let host = $ref(''); | ||||
| let users: Misskey.entities.UserDetailed[] = $ref([]); | ||||
| let recentUsers: Misskey.entities.UserDetailed[] = $ref([]); | ||||
| let selected: Misskey.entities.UserDetailed | null = $ref(null); | ||||
| let dialogEl = $ref(); | ||||
| const username = ref(''); | ||||
| const host = ref(''); | ||||
| const users = ref<Misskey.entities.UserDetailed[]>([]); | ||||
| const recentUsers = ref<Misskey.entities.UserDetailed[]>([]); | ||||
| const selected = ref<Misskey.entities.UserDetailed | null>(null); | ||||
| const dialogEl = ref(); | ||||
|  | ||||
| const search = () => { | ||||
| 	if (username === '' && host === '') { | ||||
| 		users = []; | ||||
| 	if (username.value === '' && host.value === '') { | ||||
| 		users.value = []; | ||||
| 		return; | ||||
| 	} | ||||
| 	os.api('users/search-by-username-and-host', { | ||||
| 		username: username, | ||||
| 		host: host, | ||||
| 		username: username.value, | ||||
| 		host: host.value, | ||||
| 		limit: 10, | ||||
| 		detail: false, | ||||
| 	}).then(_users => { | ||||
| 		users = _users; | ||||
| 		users.value = _users; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| const ok = () => { | ||||
| 	if (selected == null) return; | ||||
| 	emit('ok', selected); | ||||
| 	dialogEl.close(); | ||||
| 	if (selected.value == null) return; | ||||
| 	emit('ok', selected.value); | ||||
| 	dialogEl.value.close(); | ||||
|  | ||||
| 	// 最近使ったユーザー更新 | ||||
| 	let recents = defaultStore.state.recentlyUsedUsers; | ||||
| 	recents = recents.filter(x => x !== selected.id); | ||||
| 	recents.unshift(selected.id); | ||||
| 	recents = recents.filter(x => x !== selected.value.id); | ||||
| 	recents.unshift(selected.value.id); | ||||
| 	defaultStore.set('recentlyUsedUsers', recents.splice(0, 16)); | ||||
| }; | ||||
|  | ||||
| const cancel = () => { | ||||
| 	emit('cancel'); | ||||
| 	dialogEl.close(); | ||||
| 	dialogEl.value.close(); | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
| @@ -122,9 +122,9 @@ onMounted(() => { | ||||
| 		userIds: defaultStore.state.recentlyUsedUsers, | ||||
| 	}).then(users => { | ||||
| 		if (props.includeSelf && users.find(x => $i ? x.id === $i.id : true) == null) { | ||||
| 			recentUsers = [$i, ...users]; | ||||
| 			recentUsers.value = [$i, ...users]; | ||||
| 		} else { | ||||
| 			recentUsers = users; | ||||
| 			recentUsers.value = users; | ||||
| 		} | ||||
| 	}); | ||||
| }); | ||||
|   | ||||
| @@ -53,10 +53,10 @@ import MkFolder from '@/components/MkFolder.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { $i } from '@/account.js'; | ||||
|  | ||||
| let isLocked = ref(false); | ||||
| let hideOnlineStatus = ref(false); | ||||
| let noCrawle = ref(false); | ||||
| let preventAiLearning = ref(true); | ||||
| const isLocked = ref(false); | ||||
| const hideOnlineStatus = ref(false); | ||||
| const noCrawle = ref(false); | ||||
| const preventAiLearning = ref(true); | ||||
|  | ||||
| watch([isLocked, hideOnlineStatus, noCrawle, preventAiLearning], () => { | ||||
| 	os.api('i/update', { | ||||
|   | ||||
| @@ -42,12 +42,12 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { nextTick } from 'vue'; | ||||
| import { nextTick, shallowRef, ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| const modal = $shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	currentVisibility: typeof Misskey.noteVisibilities[number]; | ||||
| @@ -62,13 +62,13 @@ const emit = defineEmits<{ | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| let v = $ref(props.currentVisibility); | ||||
| const v = ref(props.currentVisibility); | ||||
|  | ||||
| function choose(visibility: typeof Misskey.noteVisibilities[number]): void { | ||||
| 	v = visibility; | ||||
| 	v.value = visibility; | ||||
| 	emit('changeVisibility', visibility); | ||||
| 	nextTick(() => { | ||||
| 		if (modal) modal.close(); | ||||
| 		if (modal.value) modal.value.close(); | ||||
| 	}); | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from 'vue'; | ||||
| import { onMounted, shallowRef, ref } from 'vue'; | ||||
| import { Chart } from 'chart.js'; | ||||
| import gradient from 'chartjs-plugin-gradient'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| @@ -25,11 +25,11 @@ import { initChart } from '@/scripts/init-chart.js'; | ||||
|  | ||||
| initChart(); | ||||
|  | ||||
| const chartEl = $shallowRef<HTMLCanvasElement>(null); | ||||
| const chartEl = shallowRef<HTMLCanvasElement>(null); | ||||
| const now = new Date(); | ||||
| let chartInstance: Chart = null; | ||||
| const chartLimit = 30; | ||||
| let fetching = $ref(true); | ||||
| const fetching = ref(true); | ||||
|  | ||||
| const { handler: externalTooltipHandler } = useChartTooltip(); | ||||
|  | ||||
| @@ -65,7 +65,7 @@ async function renderChart() { | ||||
|  | ||||
| 	const max = Math.max(...raw.read); | ||||
|  | ||||
| 	chartInstance = new Chart(chartEl, { | ||||
| 	chartInstance = new Chart(chartEl.value, { | ||||
| 		type: 'bar', | ||||
| 		data: { | ||||
| 			datasets: [{ | ||||
| @@ -147,7 +147,7 @@ async function renderChart() { | ||||
| 		plugins: [chartVLine(vLineColor)], | ||||
| 	}); | ||||
|  | ||||
| 	fetching = false; | ||||
| 	fetching.value = false; | ||||
| } | ||||
|  | ||||
| onMounted(async () => { | ||||
|   | ||||
| @@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import XTimeline from './welcome.timeline.vue'; | ||||
| import XSigninDialog from '@/components/MkSigninDialog.vue'; | ||||
| @@ -67,15 +67,15 @@ import number from '@/filters/number.js'; | ||||
| import MkNumber from '@/components/MkNumber.vue'; | ||||
| import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue'; | ||||
|  | ||||
| let meta = $ref<Misskey.entities.MetaResponse | null>(null); | ||||
| let stats = $ref<Misskey.entities.StatsResponse | null>(null); | ||||
| const meta = ref<Misskey.entities.MetaResponse | null>(null); | ||||
| const stats = ref<Misskey.entities.StatsResponse | null>(null); | ||||
|  | ||||
| os.api('meta', { detail: true }).then(_meta => { | ||||
| 	meta = _meta; | ||||
| 	meta.value = _meta; | ||||
| }); | ||||
|  | ||||
| os.api('stats', {}).then((res) => { | ||||
| 	stats = res; | ||||
| 	stats.value = res; | ||||
| }); | ||||
|  | ||||
| function signin() { | ||||
|   | ||||
| @@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onBeforeUnmount, onMounted, provide } from 'vue'; | ||||
| import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue'; | ||||
| import contains from '@/scripts/contains.js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { MenuItem } from '@/types/menu'; | ||||
| @@ -107,18 +107,18 @@ const emit = defineEmits<{ | ||||
|  | ||||
| provide('inWindow', true); | ||||
|  | ||||
| let rootEl = $shallowRef<HTMLElement | null>(); | ||||
| let showing = $ref(true); | ||||
| const rootEl = shallowRef<HTMLElement | null>(); | ||||
| const showing = ref(true); | ||||
| let beforeClickedAt = 0; | ||||
| let maximized = $ref(false); | ||||
| let minimized = $ref(false); | ||||
| const maximized = ref(false); | ||||
| const minimized = ref(false); | ||||
| let unResizedTop = ''; | ||||
| let unResizedLeft = ''; | ||||
| let unResizedWidth = ''; | ||||
| let unResizedHeight = ''; | ||||
|  | ||||
| function close() { | ||||
| 	showing = false; | ||||
| 	showing.value = false; | ||||
| } | ||||
|  | ||||
| function onKeydown(evt) { | ||||
| @@ -137,46 +137,46 @@ function onContextmenu(ev: MouseEvent) { | ||||
|  | ||||
| // 最前面へ移動 | ||||
| function top() { | ||||
| 	if (rootEl) { | ||||
| 		rootEl.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low'); | ||||
| 	if (rootEl.value) { | ||||
| 		rootEl.value.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low'); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function maximize() { | ||||
| 	maximized = true; | ||||
| 	unResizedTop = rootEl.style.top; | ||||
| 	unResizedLeft = rootEl.style.left; | ||||
| 	unResizedWidth = rootEl.style.width; | ||||
| 	unResizedHeight = rootEl.style.height; | ||||
| 	rootEl.style.top = '0'; | ||||
| 	rootEl.style.left = '0'; | ||||
| 	rootEl.style.width = '100%'; | ||||
| 	rootEl.style.height = '100%'; | ||||
| 	maximized.value = true; | ||||
| 	unResizedTop = rootEl.value.style.top; | ||||
| 	unResizedLeft = rootEl.value.style.left; | ||||
| 	unResizedWidth = rootEl.value.style.width; | ||||
| 	unResizedHeight = rootEl.value.style.height; | ||||
| 	rootEl.value.style.top = '0'; | ||||
| 	rootEl.value.style.left = '0'; | ||||
| 	rootEl.value.style.width = '100%'; | ||||
| 	rootEl.value.style.height = '100%'; | ||||
| } | ||||
|  | ||||
| function unMaximize() { | ||||
| 	maximized = false; | ||||
| 	rootEl.style.top = unResizedTop; | ||||
| 	rootEl.style.left = unResizedLeft; | ||||
| 	rootEl.style.width = unResizedWidth; | ||||
| 	rootEl.style.height = unResizedHeight; | ||||
| 	maximized.value = false; | ||||
| 	rootEl.value.style.top = unResizedTop; | ||||
| 	rootEl.value.style.left = unResizedLeft; | ||||
| 	rootEl.value.style.width = unResizedWidth; | ||||
| 	rootEl.value.style.height = unResizedHeight; | ||||
| } | ||||
|  | ||||
| function minimize() { | ||||
| 	minimized = true; | ||||
| 	unResizedWidth = rootEl.style.width; | ||||
| 	unResizedHeight = rootEl.style.height; | ||||
| 	rootEl.style.width = minWidth + 'px'; | ||||
| 	rootEl.style.height = props.mini ? '32px' : '39px'; | ||||
| 	minimized.value = true; | ||||
| 	unResizedWidth = rootEl.value.style.width; | ||||
| 	unResizedHeight = rootEl.value.style.height; | ||||
| 	rootEl.value.style.width = minWidth + 'px'; | ||||
| 	rootEl.value.style.height = props.mini ? '32px' : '39px'; | ||||
| } | ||||
|  | ||||
| function unMinimize() { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	minimized = false; | ||||
| 	rootEl.style.width = unResizedWidth; | ||||
| 	rootEl.style.height = unResizedHeight; | ||||
| 	minimized.value = false; | ||||
| 	rootEl.value.style.width = unResizedWidth; | ||||
| 	rootEl.value.style.height = unResizedHeight; | ||||
| 	const browserWidth = window.innerWidth; | ||||
| 	const browserHeight = window.innerHeight; | ||||
| 	const windowWidth = main.offsetWidth; | ||||
| @@ -192,7 +192,7 @@ function onBodyMousedown() { | ||||
| } | ||||
|  | ||||
| function onDblClick() { | ||||
| 	if (minimized) { | ||||
| 	if (minimized.value) { | ||||
| 		unMinimize(); | ||||
| 	} else { | ||||
| 		maximize(); | ||||
| @@ -205,7 +205,7 @@ function onHeaderMousedown(evt: MouseEvent) { | ||||
|  | ||||
| 	let beforeMaximized = false; | ||||
|  | ||||
| 	if (maximized) { | ||||
| 	if (maximized.value) { | ||||
| 		beforeMaximized = true; | ||||
| 		unMaximize(); | ||||
| 	} | ||||
| @@ -219,7 +219,7 @@ function onHeaderMousedown(evt: MouseEvent) { | ||||
|  | ||||
| 	beforeClickedAt = Date.now(); | ||||
|  | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	if (!contains(main, document.activeElement)) main.focus(); | ||||
| @@ -251,8 +251,8 @@ function onHeaderMousedown(evt: MouseEvent) { | ||||
| 		// 右はみ出し | ||||
| 		if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth; | ||||
|  | ||||
| 		rootEl.style.left = moveLeft + 'px'; | ||||
| 		rootEl.style.top = moveTop + 'px'; | ||||
| 		rootEl.value.style.left = moveLeft + 'px'; | ||||
| 		rootEl.value.style.top = moveTop + 'px'; | ||||
| 	} | ||||
|  | ||||
| 	if (beforeMaximized) { | ||||
| @@ -270,7 +270,7 @@ function onHeaderMousedown(evt: MouseEvent) { | ||||
|  | ||||
| // 上ハンドル掴み時 | ||||
| function onTopHandleMousedown(evt) { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	// どういうわけかnullになることがある | ||||
| 	if (main == null) return; | ||||
|  | ||||
| @@ -298,7 +298,7 @@ function onTopHandleMousedown(evt) { | ||||
|  | ||||
| // 右ハンドル掴み時 | ||||
| function onRightHandleMousedown(evt) { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	const base = evt.clientX; | ||||
| @@ -323,7 +323,7 @@ function onRightHandleMousedown(evt) { | ||||
|  | ||||
| // 下ハンドル掴み時 | ||||
| function onBottomHandleMousedown(evt) { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	const base = evt.clientY; | ||||
| @@ -348,7 +348,7 @@ function onBottomHandleMousedown(evt) { | ||||
|  | ||||
| // 左ハンドル掴み時 | ||||
| function onLeftHandleMousedown(evt) { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	const base = evt.clientX; | ||||
| @@ -400,27 +400,27 @@ function onBottomLeftHandleMousedown(evt) { | ||||
| // 高さを適用 | ||||
| function applyTransformHeight(height) { | ||||
| 	if (height > window.innerHeight) height = window.innerHeight; | ||||
| 	rootEl.style.height = height + 'px'; | ||||
| 	rootEl.value.style.height = height + 'px'; | ||||
| } | ||||
|  | ||||
| // 幅を適用 | ||||
| function applyTransformWidth(width) { | ||||
| 	if (width > window.innerWidth) width = window.innerWidth; | ||||
| 	rootEl.style.width = width + 'px'; | ||||
| 	rootEl.value.style.width = width + 'px'; | ||||
| } | ||||
|  | ||||
| // Y座標を適用 | ||||
| function applyTransformTop(top) { | ||||
| 	rootEl.style.top = top + 'px'; | ||||
| 	rootEl.value.style.top = top + 'px'; | ||||
| } | ||||
|  | ||||
| // X座標を適用 | ||||
| function applyTransformLeft(left) { | ||||
| 	rootEl.style.left = left + 'px'; | ||||
| 	rootEl.value.style.left = left + 'px'; | ||||
| } | ||||
|  | ||||
| function onBrowserResize() { | ||||
| 	const main = rootEl; | ||||
| 	const main = rootEl.value; | ||||
| 	if (main == null) return; | ||||
|  | ||||
| 	const position = main.getBoundingClientRect(); | ||||
| @@ -438,8 +438,8 @@ onMounted(() => { | ||||
| 	applyTransformWidth(props.initialWidth); | ||||
| 	if (props.initialHeight) applyTransformHeight(props.initialHeight); | ||||
|  | ||||
| 	applyTransformTop((window.innerHeight / 2) - (rootEl.offsetHeight / 2)); | ||||
| 	applyTransformLeft((window.innerWidth / 2) - (rootEl.offsetWidth / 2)); | ||||
| 	applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2)); | ||||
| 	applyTransformLeft((window.innerWidth / 2) - (rootEl.value.offsetWidth / 2)); | ||||
|  | ||||
| 	// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする | ||||
| 	top(); | ||||
|   | ||||
| @@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| import MkWindow from '@/components/MkWindow.vue'; | ||||
| import { versatileLang } from '@/scripts/intl-const.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| @@ -35,22 +36,22 @@ const props = defineProps<{ | ||||
| const requestUrl = new URL(props.url); | ||||
| if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url'); | ||||
|  | ||||
| let fetching = $ref(true); | ||||
| let title = $ref<string | null>(null); | ||||
| let player = $ref({ | ||||
| const fetching = ref(true); | ||||
| const title = ref<string | null>(null); | ||||
| const player = ref({ | ||||
| 	url: null, | ||||
| 	width: null, | ||||
| 	height: null, | ||||
| }); | ||||
|  | ||||
| const ytFetch = (): void => { | ||||
| 	fetching = true; | ||||
| 	fetching.value = true; | ||||
| 	window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => { | ||||
| 		res.json().then(info => { | ||||
| 			if (info.url == null) return; | ||||
| 			title = info.title; | ||||
| 			fetching = false; | ||||
| 			player = info.player; | ||||
| 			title.value = info.title; | ||||
| 			fetching.value = false; | ||||
| 			player.value = info.player; | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard.js'; | ||||
| import { url } from '@/config.js'; | ||||
| @@ -28,7 +29,7 @@ const props = withDefaults(defineProps<{ | ||||
|  | ||||
| const router = useRouter(); | ||||
|  | ||||
| const active = $computed(() => { | ||||
| const active = computed(() => { | ||||
| 	if (props.activeClass == null) return false; | ||||
| 	const resolved = router.resolve(props.to); | ||||
| 	if (resolved == null) return false; | ||||
|   | ||||
| @@ -96,7 +96,7 @@ const choseAd = (): Ad | null => { | ||||
| }; | ||||
|  | ||||
| const chosen = ref(choseAd()); | ||||
| const shouldHide = $ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null)); | ||||
| const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null)); | ||||
|  | ||||
| function reduceFrequency(): void { | ||||
| 	if (chosen.value == null) return; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { watch } from 'vue'; | ||||
| import { watch, ref, computed } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkImgWithBlurhash from '../MkImgWithBlurhash.vue'; | ||||
| import MkA from './MkA.vue'; | ||||
| @@ -47,9 +47,9 @@ import { acct, userPage } from '@/filters/user.js'; | ||||
| import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
|  | ||||
| const animation = $ref(defaultStore.state.animation); | ||||
| const squareAvatars = $ref(defaultStore.state.squareAvatars); | ||||
| const useBlurEffect = $ref(defaultStore.state.useBlurEffect); | ||||
| const animation = ref(defaultStore.state.animation); | ||||
| const squareAvatars = ref(defaultStore.state.squareAvatars); | ||||
| const useBlurEffect = ref(defaultStore.state.useBlurEffect); | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	user: Misskey.entities.User; | ||||
| @@ -79,11 +79,11 @@ const emit = defineEmits<{ | ||||
|  | ||||
| const showDecoration = props.forceShowDecoration || defaultStore.state.showAvatarDecorations; | ||||
|  | ||||
| const bound = $computed(() => props.link | ||||
| const bound = computed(() => props.link | ||||
| 	? { to: userPage(props.user), target: props.target } | ||||
| 	: {}); | ||||
|  | ||||
| const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) | ||||
| const url = computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) | ||||
| 	? getStaticImageUrl(props.user.avatarUrl) | ||||
| 	: props.user.avatarUrl); | ||||
|  | ||||
| @@ -116,10 +116,10 @@ function getDecorationScale() { | ||||
| 	return scaleX === 1 ? undefined : `${scaleX} 1`; | ||||
| } | ||||
|  | ||||
| let color = $ref<string | undefined>(); | ||||
| const color = ref<string | undefined>(); | ||||
|  | ||||
| watch(() => props.user.avatarBlurhash, () => { | ||||
| 	color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); | ||||
| 	color.value = extractAvgColorFromBlurhash(props.user.avatarBlurhash); | ||||
| }, { | ||||
| 	immediate: true, | ||||
| }); | ||||
|   | ||||
| @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { computed, inject } from 'vue'; | ||||
| import { computed, inject, ref } from 'vue'; | ||||
| import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| import { customEmojisMap } from '@/custom-emojis.js'; | ||||
| @@ -71,7 +71,7 @@ const url = computed(() => { | ||||
| }); | ||||
|  | ||||
| const alt = computed(() => `:${customEmojiName.value}:`); | ||||
| let errored = $ref(url.value == null); | ||||
| const errored = ref(url.value == null); | ||||
|  | ||||
| function onClick(ev: MouseEvent) { | ||||
| 	if (props.menu) { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, ref, inject } from 'vue'; | ||||
| import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| import XTabs, { Tab } from './MkPageHeader.tabs.vue'; | ||||
| import { scrollToTop } from '@/scripts/scroll.js'; | ||||
| @@ -69,13 +69,13 @@ const metadata = injectPageMetadata(); | ||||
| const hideTitle = inject('shouldOmitHeaderTitle', false); | ||||
| const thin_ = props.thin || inject('shouldHeaderThin', false); | ||||
|  | ||||
| let el = $shallowRef<HTMLElement | undefined>(undefined); | ||||
| const el = shallowRef<HTMLElement | undefined>(undefined); | ||||
| const bg = ref<string | undefined>(undefined); | ||||
| let narrow = $ref(false); | ||||
| const hasTabs = $computed(() => props.tabs.length > 0); | ||||
| const hasActions = $computed(() => props.actions && props.actions.length > 0); | ||||
| const show = $computed(() => { | ||||
| 	return !hideTitle || hasTabs || hasActions; | ||||
| const narrow = ref(false); | ||||
| const hasTabs = computed(() => props.tabs.length > 0); | ||||
| const hasActions = computed(() => props.actions && props.actions.length > 0); | ||||
| const show = computed(() => { | ||||
| 	return !hideTitle || hasTabs.value || hasActions.value; | ||||
| }); | ||||
|  | ||||
| const preventDrag = (ev: TouchEvent) => { | ||||
| @@ -83,8 +83,8 @@ const preventDrag = (ev: TouchEvent) => { | ||||
| }; | ||||
|  | ||||
| const top = () => { | ||||
| 	if (el) { | ||||
| 		scrollToTop(el as HTMLElement, { behavior: 'smooth' }); | ||||
| 	if (el.value) { | ||||
| 		scrollToTop(el.value as HTMLElement, { behavior: 'smooth' }); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -111,14 +111,14 @@ onMounted(() => { | ||||
| 	calcBg(); | ||||
| 	globalEvents.on('themeChanged', calcBg); | ||||
|  | ||||
| 	if (el && el.parentElement) { | ||||
| 		narrow = el.parentElement.offsetWidth < 500; | ||||
| 	if (el.value && el.value.parentElement) { | ||||
| 		narrow.value = el.value.parentElement.offsetWidth < 500; | ||||
| 		ro = new ResizeObserver((entries, observer) => { | ||||
| 			if (el && el.parentElement && document.body.contains(el as HTMLElement)) { | ||||
| 				narrow = el.parentElement.offsetWidth < 500; | ||||
| 			if (el.value && el.value.parentElement && document.body.contains(el.value as HTMLElement)) { | ||||
| 				narrow.value = el.value.parentElement.offsetWidth < 500; | ||||
| 			} | ||||
| 		}); | ||||
| 		ro.observe(el.parentElement as HTMLElement); | ||||
| 		ro.observe(el.value.parentElement as HTMLElement); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -18,36 +18,36 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue'; | ||||
| import { $$ } from 'vue/macros'; | ||||
| import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue'; | ||||
|  | ||||
| import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const'; | ||||
|  | ||||
| const rootEl = $shallowRef<HTMLElement>(); | ||||
| const headerEl = $shallowRef<HTMLElement>(); | ||||
| const footerEl = $shallowRef<HTMLElement>(); | ||||
| const bodyEl = $shallowRef<HTMLElement>(); | ||||
| const rootEl = shallowRef<HTMLElement>(); | ||||
| const headerEl = shallowRef<HTMLElement>(); | ||||
| const footerEl = shallowRef<HTMLElement>(); | ||||
| const bodyEl = shallowRef<HTMLElement>(); | ||||
|  | ||||
| let headerHeight = $ref<string | undefined>(); | ||||
| let childStickyTop = $ref(0); | ||||
| const headerHeight = ref<string | undefined>(); | ||||
| const childStickyTop = ref(0); | ||||
| const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0)); | ||||
| provide(CURRENT_STICKY_TOP, $$(childStickyTop)); | ||||
| provide(CURRENT_STICKY_TOP, childStickyTop); | ||||
|  | ||||
| let footerHeight = $ref<string | undefined>(); | ||||
| let childStickyBottom = $ref(0); | ||||
| const footerHeight = ref<string | undefined>(); | ||||
| const childStickyBottom = ref(0); | ||||
| const parentStickyBottom = inject<Ref<number>>(CURRENT_STICKY_BOTTOM, ref(0)); | ||||
| provide(CURRENT_STICKY_BOTTOM, $$(childStickyBottom)); | ||||
| provide(CURRENT_STICKY_BOTTOM, childStickyBottom); | ||||
|  | ||||
| const calc = () => { | ||||
| 	// コンポーネントが表示されてないけどKeepAliveで残ってる場合などは null になる | ||||
| 	if (headerEl != null) { | ||||
| 		childStickyTop = parentStickyTop.value + headerEl.offsetHeight; | ||||
| 		headerHeight = headerEl.offsetHeight.toString(); | ||||
| 	if (headerEl.value != null) { | ||||
| 		childStickyTop.value = parentStickyTop.value + headerEl.value.offsetHeight; | ||||
| 		headerHeight.value = headerEl.value.offsetHeight.toString(); | ||||
| 	} | ||||
|  | ||||
| 	// コンポーネントが表示されてないけどKeepAliveで残ってる場合などは null になる | ||||
| 	if (footerEl != null) { | ||||
| 		childStickyBottom = parentStickyBottom.value + footerEl.offsetHeight; | ||||
| 		footerHeight = footerEl.offsetHeight.toString(); | ||||
| 	if (footerEl.value != null) { | ||||
| 		childStickyBottom.value = parentStickyBottom.value + footerEl.value.offsetHeight; | ||||
| 		footerHeight.value = footerEl.value.offsetHeight.toString(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -62,28 +62,28 @@ onMounted(() => { | ||||
|  | ||||
| 	watch([parentStickyTop, parentStickyBottom], calc); | ||||
|  | ||||
| 	watch($$(childStickyTop), () => { | ||||
| 		bodyEl.style.setProperty('--stickyTop', `${childStickyTop}px`); | ||||
| 	watch(childStickyTop, () => { | ||||
| 		bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`); | ||||
| 	}, { | ||||
| 		immediate: true, | ||||
| 	}); | ||||
|  | ||||
| 	watch($$(childStickyBottom), () => { | ||||
| 		bodyEl.style.setProperty('--stickyBottom', `${childStickyBottom}px`); | ||||
| 	watch(childStickyBottom, () => { | ||||
| 		bodyEl.value.style.setProperty('--stickyBottom', `${childStickyBottom.value}px`); | ||||
| 	}, { | ||||
| 		immediate: true, | ||||
| 	}); | ||||
|  | ||||
| 	headerEl.style.position = 'sticky'; | ||||
| 	headerEl.style.top = 'var(--stickyTop, 0)'; | ||||
| 	headerEl.style.zIndex = '1000'; | ||||
| 	headerEl.value.style.position = 'sticky'; | ||||
| 	headerEl.value.style.top = 'var(--stickyTop, 0)'; | ||||
| 	headerEl.value.style.zIndex = '1000'; | ||||
|  | ||||
| 	footerEl.style.position = 'sticky'; | ||||
| 	footerEl.style.bottom = 'var(--stickyBottom, 0)'; | ||||
| 	footerEl.style.zIndex = '1000'; | ||||
| 	footerEl.value.style.position = 'sticky'; | ||||
| 	footerEl.value.style.bottom = 'var(--stickyBottom, 0)'; | ||||
| 	footerEl.value.style.zIndex = '1000'; | ||||
|  | ||||
| 	observer.observe(headerEl); | ||||
| 	observer.observe(footerEl); | ||||
| 	observer.observe(headerEl.value); | ||||
| 	observer.observe(footerEl.value); | ||||
| }); | ||||
|  | ||||
| onUnmounted(() => { | ||||
| @@ -91,6 +91,6 @@ onUnmounted(() => { | ||||
| }); | ||||
|  | ||||
| defineExpose({ | ||||
| 	rootEl: $$(rootEl), | ||||
| 	rootEl: rootEl, | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import isChromatic from 'chromatic/isChromatic'; | ||||
| import { onMounted, onUnmounted } from 'vue'; | ||||
| import { onMounted, onUnmounted, ref, computed } from 'vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { dateTimeFormat } from '@/scripts/intl-const.js'; | ||||
|  | ||||
| @@ -47,29 +47,29 @@ const invalid = Number.isNaN(_time); | ||||
| const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; | ||||
|  | ||||
| // eslint-disable-next-line vue/no-setup-props-destructure | ||||
| let now = $ref((props.origin ?? new Date()).getTime()); | ||||
| const ago = $computed(() => (now - _time) / 1000/*ms*/); | ||||
| const now = ref((props.origin ?? new Date()).getTime()); | ||||
| const ago = computed(() => (now.value - _time) / 1000/*ms*/); | ||||
|  | ||||
| const relative = $computed<string>(() => { | ||||
| const relative = computed<string>(() => { | ||||
| 	if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない | ||||
| 	if (invalid) return i18n.ts._ago.invalid; | ||||
|  | ||||
| 	return ( | ||||
| 		ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) : | ||||
| 		ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) : | ||||
| 		ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) : | ||||
| 		ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) : | ||||
| 		ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : | ||||
| 		ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : | ||||
| 		ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : | ||||
| 		ago >= -3 ? i18n.ts._ago.justNow : | ||||
| 		ago < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago / 31536000).toString() }) : | ||||
| 		ago < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago / 2592000).toString() }) : | ||||
| 		ago < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago / 604800).toString() }) : | ||||
| 		ago < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago / 86400).toString() }) : | ||||
| 		ago < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago / 3600).toString() }) : | ||||
| 		ago < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago / 60)).toString() }) : | ||||
| 		i18n.t('_timeIn.seconds', { n: (~~(-ago % 60)).toString() }) | ||||
| 		ago.value >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) : | ||||
| 		ago.value >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) : | ||||
| 		ago.value >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) : | ||||
| 		ago.value >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) : | ||||
| 		ago.value >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) : | ||||
| 		ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) : | ||||
| 		ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) : | ||||
| 		ago.value >= -3 ? i18n.ts._ago.justNow : | ||||
| 		ago.value < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) : | ||||
| 		ago.value < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) : | ||||
| 		ago.value < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) : | ||||
| 		ago.value < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) : | ||||
| 		ago.value < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) : | ||||
| 		ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) : | ||||
| 		i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() }) | ||||
| 	); | ||||
| }); | ||||
|  | ||||
| @@ -77,8 +77,8 @@ let tickId: number; | ||||
| let currentInterval: number; | ||||
|  | ||||
| function tick() { | ||||
| 	now = (new Date()).getTime(); | ||||
| 	const nextInterval = ago < 60 ? 10000 : ago < 3600 ? 60000 : 180000; | ||||
| 	now.value = (new Date()).getTime(); | ||||
| 	const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000; | ||||
|  | ||||
| 	if (currentInterval !== nextInterval) { | ||||
| 		if (tickId) window.clearInterval(tickId); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { inject, onBeforeUnmount, provide } from 'vue'; | ||||
| import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue'; | ||||
| import { Resolved, Router } from '@/nirax'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
|  | ||||
| @@ -46,16 +46,16 @@ function resolveNested(current: Resolved, d = 0): Resolved | null { | ||||
| } | ||||
|  | ||||
| const current = resolveNested(router.current)!; | ||||
| let currentPageComponent = $shallowRef(current.route.component); | ||||
| let currentPageProps = $ref(current.props); | ||||
| let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props))); | ||||
| const currentPageComponent = shallowRef(current.route.component); | ||||
| const currentPageProps = ref(current.props); | ||||
| const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props))); | ||||
|  | ||||
| function onChange({ resolved, key: newKey }) { | ||||
| 	const current = resolveNested(resolved); | ||||
| 	if (current == null) return; | ||||
| 	currentPageComponent = current.route.component; | ||||
| 	currentPageProps = current.props; | ||||
| 	key = current.route.path + JSON.stringify(Object.fromEntries(current.props)); | ||||
| 	currentPageComponent.value = current.route.component; | ||||
| 	currentPageProps.value = current.props; | ||||
| 	key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props)); | ||||
| } | ||||
|  | ||||
| router.addListener('change', onChange); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 zyoshoka
					zyoshoka