diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
| commit | 9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch) | |
| tree | ce5959571a981b9c4047da3c7b3fd080aa44222c /packages/client/src/components/global | |
| parent | wip: retention for dashboard (diff) | |
| download | sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2 sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip | |
rename: client -> frontend
Diffstat (limited to 'packages/client/src/components/global')
17 files changed, 0 insertions, 1694 deletions
diff --git a/packages/client/src/components/global/MkA.vue b/packages/client/src/components/global/MkA.vue deleted file mode 100644 index 5a0ba0d8d3..0000000000 --- a/packages/client/src/components/global/MkA.vue +++ /dev/null @@ -1,102 +0,0 @@ -<template> -<a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu"> - <slot></slot> -</a> -</template> - -<script lang="ts" setup> -import { inject } from 'vue'; -import * as os from '@/os'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { url } from '@/config'; -import { popout as popout_ } from '@/scripts/popout'; -import { i18n } from '@/i18n'; -import { useRouter } from '@/router'; - -const props = withDefaults(defineProps<{ - to: string; - activeClass?: null | string; - behavior?: null | 'window' | 'browser' | 'modalWindow'; -}>(), { - activeClass: null, - behavior: null, -}); - -const router = useRouter(); - -const active = $computed(() => { - if (props.activeClass == null) return false; - const resolved = router.resolve(props.to); - if (resolved == null) return false; - if (resolved.route.path === router.currentRoute.value.path) return true; - if (resolved.route.name == null) return false; - if (router.currentRoute.value.name == null) return false; - return resolved.route.name === router.currentRoute.value.name; -}); - -function onContextmenu(ev) { - const selection = window.getSelection(); - if (selection && selection.toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: props.to, - }, { - icon: 'ti ti-app-window', - text: i18n.ts.openInWindow, - action: () => { - os.pageWindow(props.to); - }, - }, { - icon: 'ti ti-player-eject', - text: i18n.ts.showInPage, - action: () => { - router.push(props.to, 'forcePage'); - }, - }, null, { - icon: 'ti ti-external-link', - text: i18n.ts.openInNewTab, - action: () => { - window.open(props.to, '_blank'); - }, - }, { - icon: 'ti ti-link', - text: i18n.ts.copyLink, - action: () => { - copyToClipboard(`${url}${props.to}`); - }, - }], ev); -} - -function openWindow() { - os.pageWindow(props.to); -} - -function modalWindow() { - os.modalPageWindow(props.to); -} - -function popout() { - popout_(props.to); -} - -function nav(ev: MouseEvent) { - if (props.behavior === 'browser') { - location.href = props.to; - return; - } - - if (props.behavior) { - if (props.behavior === 'window') { - return openWindow(); - } else if (props.behavior === 'modalWindow') { - return modalWindow(); - } - } - - if (ev.shiftKey) { - return openWindow(); - } - - router.push(props.to, ev.ctrlKey ? 'forcePage' : null); -} -</script> diff --git a/packages/client/src/components/global/MkAcct.vue b/packages/client/src/components/global/MkAcct.vue deleted file mode 100644 index c3e806b5fb..0000000000 --- a/packages/client/src/components/global/MkAcct.vue +++ /dev/null @@ -1,27 +0,0 @@ -<template> -<span class="mk-acct"> - <span class="name">@{{ user.username }}</span> - <span v-if="user.host || detail || $store.state.showFullAcct" class="host">@{{ user.host || host }}</span> -</span> -</template> - -<script lang="ts" setup> -import * as misskey from 'misskey-js'; -import { toUnicode } from 'punycode/'; -import { host as hostRaw } from '@/config'; - -defineProps<{ - user: misskey.entities.UserDetailed; - detail?: boolean; -}>(); - -const host = toUnicode(hostRaw); -</script> - -<style lang="scss" scoped> -.mk-acct { - > .host { - opacity: 0.5; - } -} -</style> diff --git a/packages/client/src/components/global/MkAd.vue b/packages/client/src/components/global/MkAd.vue deleted file mode 100644 index a80efb142c..0000000000 --- a/packages/client/src/components/global/MkAd.vue +++ /dev/null @@ -1,186 +0,0 @@ -<template> -<div v-if="chosen" class="qiivuoyo"> - <div v-if="!showMenu" class="main" :class="chosen.place"> - <a :href="chosen.url" target="_blank"> - <img :src="chosen.imageUrl"> - <button class="_button menu" @click.prevent.stop="toggleMenu"><span class="ti ti-info-circle info-circle"></span></button> - </a> - </div> - <div v-else class="menu"> - <div class="body"> - <div>Ads by {{ host }}</div> - <!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>--> - <MkButton v-if="chosen.ratio !== 0" class="button" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton> - <button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button> - </div> - </div> -</div> -<div v-else></div> -</template> - -<script lang="ts" setup> -import { ref } from 'vue'; -import { instance } from '@/instance'; -import { host } from '@/config'; -import MkButton from '@/components/MkButton.vue'; -import { defaultStore } from '@/store'; -import * as os from '@/os'; - -type Ad = (typeof instance)['ads'][number]; - -const props = defineProps<{ - prefer: string[]; - specify?: Ad; -}>(); - -const showMenu = ref(false); -const toggleMenu = (): void => { - showMenu.value = !showMenu.value; -}; - -const choseAd = (): Ad | null => { - if (props.specify) { - return props.specify; - } - - const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { - ...ad, - ratio: 0, - } : ad); - - let ads = allAds.filter(ad => props.prefer.includes(ad.place)); - - if (ads.length === 0) { - ads = allAds.filter(ad => ad.place === 'square'); - } - - const lowPriorityAds = ads.filter(ad => ad.ratio === 0); - ads = ads.filter(ad => ad.ratio !== 0); - - if (ads.length === 0) { - if (lowPriorityAds.length !== 0) { - return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; - } else { - return null; - } - } - - const totalFactor = ads.reduce((a, b) => a + b.ratio, 0); - const r = Math.random() * totalFactor; - - let stackedFactor = 0; - for (const ad of ads) { - if (r >= stackedFactor && r <= stackedFactor + ad.ratio) { - return ad; - } else { - stackedFactor += ad.ratio; - } - } - - return null; -}; - -const chosen = ref(choseAd()); - -function reduceFrequency(): void { - if (chosen.value == null) return; - if (defaultStore.state.mutedAds.includes(chosen.value.id)) return; - defaultStore.push('mutedAds', chosen.value.id); - os.success(); - chosen.value = choseAd(); - showMenu.value = false; -} -</script> - -<style lang="scss" scoped> -.qiivuoyo { - background-size: auto auto; - background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--ad) 8px, var(--ad) 14px ); - - > .main { - text-align: center; - - > a { - display: inline-block; - position: relative; - vertical-align: bottom; - - &:hover { - > img { - filter: contrast(120%); - } - } - - > img { - display: block; - object-fit: contain; - margin: auto; - border-radius: 5px; - } - - > .menu { - position: absolute; - top: 1px; - right: 1px; - - > .info-circle { - border: 3px solid var(--panel); - border-radius: 50%; - background: var(--panel); - } - } - } - - &.square { - > a , - > a > img { - max-width: min(300px, 100%); - max-height: 300px; - } - } - - &.horizontal { - padding: 8px; - - > a , - > a > img { - max-width: min(600px, 100%); - max-height: 80px; - } - } - - &.horizontal-big { - padding: 8px; - - > a , - > a > img { - max-width: min(600px, 100%); - max-height: 250px; - } - } - - &.vertical { - > a , - > a > img { - max-width: min(100px, 100%); - } - } - } - - > .menu { - padding: 8px; - text-align: center; - - > .body { - padding: 8px; - margin: 0 auto; - max-width: 400px; - border: solid 1px var(--divider); - - > .button { - margin: 8px auto; - } - } - } -} -</style> diff --git a/packages/client/src/components/global/MkAvatar.vue b/packages/client/src/components/global/MkAvatar.vue deleted file mode 100644 index 5f3e3c176d..0000000000 --- a/packages/client/src/components/global/MkAvatar.vue +++ /dev/null @@ -1,143 +0,0 @@ -<template> -<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick"> - <img class="inner" :src="url" decoding="async"/> - <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> -</span> -<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target"> - <img class="inner" :src="url" decoding="async"/> - <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> -</MkA> -</template> - -<script lang="ts" setup> -import { onMounted, watch } from 'vue'; -import * as misskey from 'misskey-js'; -import { getStaticImageUrl } from '@/scripts/get-static-image-url'; -import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; -import { acct, userPage } from '@/filters/user'; -import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; -import { defaultStore } from '@/store'; - -const props = withDefaults(defineProps<{ - user: misskey.entities.User; - target?: string | null; - disableLink?: boolean; - disablePreview?: boolean; - showIndicator?: boolean; -}>(), { - target: null, - disableLink: false, - disablePreview: false, - showIndicator: false, -}); - -const emit = defineEmits<{ - (ev: 'click', v: MouseEvent): void; -}>(); - -const url = $computed(() => defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(props.user.avatarUrl) - : props.user.avatarUrl); - -function onClick(ev: MouseEvent) { - emit('click', ev); -} - -let color = $ref(); - -watch(() => props.user.avatarBlurhash, () => { - color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); -}, { - immediate: true, -}); -</script> - -<style lang="scss" scoped> -@keyframes earwiggleleft { - from { transform: rotate(37.6deg) skew(30deg); } - 25% { transform: rotate(10deg) skew(30deg); } - 50% { transform: rotate(20deg) skew(30deg); } - 75% { transform: rotate(0deg) skew(30deg); } - to { transform: rotate(37.6deg) skew(30deg); } -} - -@keyframes earwiggleright { - from { transform: rotate(-37.6deg) skew(-30deg); } - 30% { transform: rotate(-10deg) skew(-30deg); } - 55% { transform: rotate(-20deg) skew(-30deg); } - 75% { transform: rotate(0deg) skew(-30deg); } - to { transform: rotate(-37.6deg) skew(-30deg); } -} - -.eiwwqkts { - position: relative; - display: inline-block; - vertical-align: bottom; - flex-shrink: 0; - border-radius: 100%; - line-height: 16px; - - > .inner { - position: absolute; - bottom: 0; - left: 0; - right: 0; - top: 0; - border-radius: 100%; - z-index: 1; - overflow: hidden; - object-fit: cover; - width: 100%; - height: 100%; - } - - > .indicator { - position: absolute; - z-index: 1; - bottom: 0; - left: 0; - width: 20%; - height: 20%; - } - - &.square { - border-radius: 20%; - - > .inner { - border-radius: 20%; - } - } - - &.cat { - &:before, &:after { - background: #df548f; - border: solid 4px currentColor; - box-sizing: border-box; - content: ''; - display: inline-block; - height: 50%; - width: 50%; - } - - &:before { - border-radius: 0 75% 75%; - transform: rotate(37.5deg) skew(30deg); - } - - &:after { - border-radius: 75% 0 75% 75%; - transform: rotate(-37.5deg) skew(-30deg); - } - - &:hover { - &:before { - animation: earwiggleleft 1s infinite; - } - - &:after { - animation: earwiggleright 1s infinite; - } - } - } -} -</style> diff --git a/packages/client/src/components/global/MkEllipsis.vue b/packages/client/src/components/global/MkEllipsis.vue deleted file mode 100644 index 0a46f486d6..0000000000 --- a/packages/client/src/components/global/MkEllipsis.vue +++ /dev/null @@ -1,34 +0,0 @@ -<template> - <span class="mk-ellipsis"> - <span>.</span><span>.</span><span>.</span> - </span> -</template> - -<style lang="scss" scoped> -.mk-ellipsis { - > span { - animation: ellipsis 1.4s infinite ease-in-out both; - - &:nth-child(1) { - animation-delay: 0s; - } - - &:nth-child(2) { - animation-delay: 0.16s; - } - - &:nth-child(3) { - animation-delay: 0.32s; - } - } -} - -@keyframes ellipsis { - 0%, 80%, 100% { - opacity: 1; - } - 40% { - opacity: 0; - } -} -</style> diff --git a/packages/client/src/components/global/MkEmoji.vue b/packages/client/src/components/global/MkEmoji.vue deleted file mode 100644 index ce1299a39f..0000000000 --- a/packages/client/src/components/global/MkEmoji.vue +++ /dev/null @@ -1,81 +0,0 @@ -<template> -<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/> -<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/> -<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> -<span v-else>{{ emoji }}</span> -</template> - -<script lang="ts" setup> -import { computed } from 'vue'; -import { CustomEmoji } from 'misskey-js/built/entities'; -import { getStaticImageUrl } from '@/scripts/get-static-image-url'; -import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; -import { defaultStore } from '@/store'; -import { instance } from '@/instance'; -import { getEmojiName } from '@/scripts/emojilist'; - -const props = defineProps<{ - emoji: string; - normal?: boolean; - noStyle?: boolean; - customEmojis?: CustomEmoji[]; - isReaction?: boolean; -}>(); - -const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; - -const isCustom = computed(() => props.emoji.startsWith(':')); -const char = computed(() => isCustom.value ? undefined : props.emoji); -const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction); -const ce = computed(() => props.customEmojis ?? instance.emojis ?? []); -const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : undefined); -const url = computed(() => { - if (char.value) { - return char2path(char.value); - } else { - const rawUrl = (customEmoji.value as CustomEmoji).url; - return defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(rawUrl) - : rawUrl; - } -}); -const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value); - -// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter -function computeTitle(event: PointerEvent): void { - const title = customEmoji.value - ? `:${customEmoji.value.name}:` - : (getEmojiName(char.value as string) ?? char.value as string); - (event.target as HTMLElement).title = title; -} -</script> - -<style lang="scss" scoped> -.mk-emoji { - height: 1.25em; - vertical-align: -0.25em; - - &.custom { - height: 2.5em; - vertical-align: middle; - transition: transform 0.2s ease; - - &:hover { - transform: scale(1.2); - } - - &.normal { - height: 1.25em; - vertical-align: -0.25em; - - &:hover { - transform: none; - } - } - } - - &.noStyle { - height: auto !important; - } -} -</style> diff --git a/packages/client/src/components/global/MkError.vue b/packages/client/src/components/global/MkError.vue deleted file mode 100644 index e135d4184b..0000000000 --- a/packages/client/src/components/global/MkError.vue +++ /dev/null @@ -1,36 +0,0 @@ -<template> -<transition :name="$store.state.animation ? 'zoom' : ''" appear> - <div class="mjndxjcg"> - <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> - <MkButton class="button" @click="() => $emit('retry')">{{ i18n.ts.retry }}</MkButton> - </div> -</transition> -</template> - -<script lang="ts" setup> -import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; -</script> - -<style lang="scss" scoped> -.mjndxjcg { - padding: 32px; - text-align: center; - - > p { - margin: 0 0 8px 0; - } - - > .button { - margin: 0 auto; - } - - > img { - vertical-align: bottom; - height: 128px; - margin-bottom: 16px; - border-radius: 16px; - } -} -</style> diff --git a/packages/client/src/components/global/MkLoading.vue b/packages/client/src/components/global/MkLoading.vue deleted file mode 100644 index 64e12e3b44..0000000000 --- a/packages/client/src/components/global/MkLoading.vue +++ /dev/null @@ -1,101 +0,0 @@ -<template> -<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini, [$style.em]: em }]"> - <div :class="$style.container"> - <svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> - <g transform="matrix(1.125,0,0,1.125,12,12)"> - <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> - </g> - </svg> - <svg :class="[$style.spinner, $style.fg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> - <g transform="matrix(1.125,0,0,1.125,12,12)"> - <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> - </g> - </svg> - </div> -</div> -</template> - -<script lang="ts" setup> -import { } from 'vue'; - -const props = withDefaults(defineProps<{ - inline?: boolean; - colored?: boolean; - mini?: boolean; - em?: boolean; -}>(), { - inline: false, - colored: true, - mini: false, - em: false, -}); -</script> - -<style lang="scss" module> -@keyframes spinner { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -.root { - padding: 32px; - text-align: center; - cursor: wait; - - --size: 38px; - - &.colored { - color: var(--accent); - } - - &.inline { - display: inline; - padding: 0; - --size: 32px; - } - - &.mini { - padding: 16px; - --size: 32px; - } - - &.em { - display: inline-block; - vertical-align: middle; - padding: 0; - --size: 1em; - } -} - -.container { - position: relative; - width: var(--size); - height: var(--size); - margin: 0 auto; -} - -.spinner { - position: absolute; - top: 0; - left: 0; - width: var(--size); - height: var(--size); - fill-rule: evenodd; - clip-rule: evenodd; - stroke-linecap: round; - stroke-linejoin: round; - stroke-miterlimit: 1.5; -} - -.bg { - opacity: 0.275; -} - -.fg { - animation: spinner 0.5s linear infinite; -} -</style> diff --git a/packages/client/src/components/global/MkMisskeyFlavoredMarkdown.vue b/packages/client/src/components/global/MkMisskeyFlavoredMarkdown.vue deleted file mode 100644 index 70d0108e9f..0000000000 --- a/packages/client/src/components/global/MkMisskeyFlavoredMarkdown.vue +++ /dev/null @@ -1,191 +0,0 @@ -<template> -<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import MfmCore from '@/components/mfm'; - -const props = withDefaults(defineProps<{ - text: string; - plain?: boolean; - nowrap?: boolean; - author?: any; - customEmojis?: any; - isNote?: boolean; -}>(), { - plain: false, - nowrap: false, - author: null, - isNote: true, -}); -</script> - -<style lang="scss"> -._mfm_blur_ { - filter: blur(6px); - transition: filter 0.3s; - - &:hover { - filter: blur(0px); - } -} - -.mfm-x2 { - --mfm-zoom-size: 200%; -} - -.mfm-x3 { - --mfm-zoom-size: 400%; -} - -.mfm-x4 { - --mfm-zoom-size: 600%; -} - -.mfm-x2, .mfm-x3, .mfm-x4 { - font-size: var(--mfm-zoom-size); - - .mfm-x2, .mfm-x3, .mfm-x4 { - /* only half effective */ - font-size: calc(var(--mfm-zoom-size) / 2 + 50%); - - .mfm-x2, .mfm-x3, .mfm-x4 { - /* disabled */ - font-size: 100%; - } - } -} - -@keyframes mfm-spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -@keyframes mfm-spinX { - 0% { transform: perspective(128px) rotateX(0deg); } - 100% { transform: perspective(128px) rotateX(360deg); } -} - -@keyframes mfm-spinY { - 0% { transform: perspective(128px) rotateY(0deg); } - 100% { transform: perspective(128px) rotateY(360deg); } -} - -@keyframes mfm-jump { - 0% { transform: translateY(0); } - 25% { transform: translateY(-16px); } - 50% { transform: translateY(0); } - 75% { transform: translateY(-8px); } - 100% { transform: translateY(0); } -} - -@keyframes mfm-bounce { - 0% { transform: translateY(0) scale(1, 1); } - 25% { transform: translateY(-16px) scale(1, 1); } - 50% { transform: translateY(0) scale(1, 1); } - 75% { transform: translateY(0) scale(1.5, 0.75); } - 100% { transform: translateY(0) scale(1, 1); } -} - -// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; -// let css = ''; -// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } -@keyframes mfm-twitch { - 0% { transform: translate(7px, -2px) } - 5% { transform: translate(-3px, 1px) } - 10% { transform: translate(-7px, -1px) } - 15% { transform: translate(0px, -1px) } - 20% { transform: translate(-8px, 6px) } - 25% { transform: translate(-4px, -3px) } - 30% { transform: translate(-4px, -6px) } - 35% { transform: translate(-8px, -8px) } - 40% { transform: translate(4px, 6px) } - 45% { transform: translate(-3px, 1px) } - 50% { transform: translate(2px, -10px) } - 55% { transform: translate(-7px, 0px) } - 60% { transform: translate(-2px, 4px) } - 65% { transform: translate(3px, -8px) } - 70% { transform: translate(6px, 7px) } - 75% { transform: translate(-7px, -2px) } - 80% { transform: translate(-7px, -8px) } - 85% { transform: translate(9px, 3px) } - 90% { transform: translate(-3px, -2px) } - 95% { transform: translate(-10px, 2px) } - 100% { transform: translate(-2px, -6px) } -} - -// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; -// let css = ''; -// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } -@keyframes mfm-shake { - 0% { transform: translate(-3px, -1px) rotate(-8deg) } - 5% { transform: translate(0px, -1px) rotate(-10deg) } - 10% { transform: translate(1px, -3px) rotate(0deg) } - 15% { transform: translate(1px, 1px) rotate(11deg) } - 20% { transform: translate(-2px, 1px) rotate(1deg) } - 25% { transform: translate(-1px, -2px) rotate(-2deg) } - 30% { transform: translate(-1px, 2px) rotate(-3deg) } - 35% { transform: translate(2px, 1px) rotate(6deg) } - 40% { transform: translate(-2px, -3px) rotate(-9deg) } - 45% { transform: translate(0px, -1px) rotate(-12deg) } - 50% { transform: translate(1px, 2px) rotate(10deg) } - 55% { transform: translate(0px, -3px) rotate(8deg) } - 60% { transform: translate(1px, -1px) rotate(8deg) } - 65% { transform: translate(0px, -1px) rotate(-7deg) } - 70% { transform: translate(-1px, -3px) rotate(6deg) } - 75% { transform: translate(0px, -2px) rotate(4deg) } - 80% { transform: translate(-2px, -1px) rotate(3deg) } - 85% { transform: translate(1px, -3px) rotate(-10deg) } - 90% { transform: translate(1px, 0px) rotate(3deg) } - 95% { transform: translate(-2px, 0px) rotate(-3deg) } - 100% { transform: translate(2px, 1px) rotate(2deg) } -} - -@keyframes mfm-rubberBand { - from { transform: scale3d(1, 1, 1); } - 30% { transform: scale3d(1.25, 0.75, 1); } - 40% { transform: scale3d(0.75, 1.25, 1); } - 50% { transform: scale3d(1.15, 0.85, 1); } - 65% { transform: scale3d(0.95, 1.05, 1); } - 75% { transform: scale3d(1.05, 0.95, 1); } - to { transform: scale3d(1, 1, 1); } -} - -@keyframes mfm-rainbow { - 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } - 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } -} -</style> - -<style lang="scss" scoped> -.havbbuyv { - white-space: pre-wrap; - - &.nowrap { - white-space: pre; - word-wrap: normal; // https://codeday.me/jp/qa/20190424/690106.html - overflow: hidden; - text-overflow: ellipsis; - } - - ::v-deep(.quote) { - display: block; - margin: 8px; - padding: 6px 0 6px 12px; - color: var(--fg); - border-left: solid 3px var(--fg); - opacity: 0.7; - } - - ::v-deep(pre) { - font-size: 0.8em; - } - - > ::v-deep(code) { - font-size: 0.8em; - word-break: break-all; - padding: 4px 6px; - } -} -</style> diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue deleted file mode 100644 index a228dfe883..0000000000 --- a/packages/client/src/components/global/MkPageHeader.vue +++ /dev/null @@ -1,368 +0,0 @@ -<template> -<div v-if="show" ref="el" class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick"> - <div v-if="narrow" class="buttons left"> - <MkAvatar v-if="props.displayMyAvatar && $i" class="avatar" :user="$i" :disable-preview="true"/> - </div> - <template v-if="metadata"> - <div v-if="!hideTitle" class="titleContainer" @click="showTabsPopup"> - <MkAvatar v-if="metadata.avatar" class="avatar" :user="metadata.avatar" :disable-preview="true" :show-indicator="true"/> - <i v-else-if="metadata.icon" class="icon" :class="metadata.icon"></i> - - <div class="title"> - <MkUserName v-if="metadata.userName" :user="metadata.userName" :nowrap="true" class="title"/> - <div v-else-if="metadata.title" class="title">{{ metadata.title }}</div> - <div v-if="!narrow && metadata.subtitle" class="subtitle"> - {{ metadata.subtitle }} - </div> - <div v-if="narrow && hasTabs" class="subtitle activeTab"> - {{ tabs.find(tab => tab.key === props.tab)?.title }} - <i class="chevron ti ti-chevron-down"></i> - </div> - </div> - </div> - <div v-if="!narrow || hideTitle" class="tabs"> - <button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = (el as HTMLElement)" v-tooltip.noDelay="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)"> - <i v-if="tab.icon" class="icon" :class="tab.icon"></i> - <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> - </button> - <div ref="tabHighlightEl" class="highlight"></div> - </div> - </template> - <div class="buttons right"> - <template v-for="action in actions"> - <button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button> - </template> - </div> -</div> -</template> - -<script lang="ts" setup> -import { onMounted, onUnmounted, ref, inject, watch, nextTick } from 'vue'; -import tinycolor from 'tinycolor2'; -import { popupMenu } from '@/os'; -import { scrollToTop } from '@/scripts/scroll'; -import { globalEvents } from '@/events'; -import { injectPageMetadata } from '@/scripts/page-metadata'; -import { $i } from '@/account'; - -type Tab = { - key: string; - title: string; - icon?: string; - iconOnly?: boolean; - onClick?: (ev: MouseEvent) => void; -}; - -const props = withDefaults(defineProps<{ - tabs?: Tab[]; - tab?: string; - actions?: { - text: string; - icon: string; - highlighted?: boolean; - handler: (ev: MouseEvent) => void; - }[]; - thin?: boolean; - displayMyAvatar?: boolean; -}>(), { - tabs: () => ([] as Tab[]) -}); - -const emit = defineEmits<{ - (ev: 'update:tab', key: string); -}>(); - -const metadata = injectPageMetadata(); - -const hideTitle = inject('shouldOmitHeaderTitle', false); -const thin_ = props.thin || inject('shouldHeaderThin', false); - -const el = $ref<HTMLElement | undefined>(undefined); -const tabRefs: Record<string, HTMLElement | null> = {}; -const tabHighlightEl = $ref<HTMLElement | null>(null); -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 showTabsPopup = (ev: MouseEvent) => { - if (!hasTabs) return; - if (!narrow) return; - ev.preventDefault(); - ev.stopPropagation(); - const menu = props.tabs.map(tab => ({ - text: tab.title, - icon: tab.icon, - active: tab.key != null && tab.key === props.tab, - action: (ev) => { - onTabClick(tab, ev); - }, - })); - popupMenu(menu, (ev.currentTarget ?? ev.target) as HTMLElement); -}; - -const preventDrag = (ev: TouchEvent) => { - ev.stopPropagation(); -}; - -const onClick = () => { - if (el) { - scrollToTop(el as HTMLElement, { behavior: 'smooth' }); - } -}; - -function onTabMousedown(tab: Tab, ev: MouseEvent): void { - // ユーザビリティの観点からmousedown時にはonClickは呼ばない - if (tab.key) { - emit('update:tab', tab.key); - } -} - -function onTabClick(tab: Tab, ev: MouseEvent): void { - if (tab.onClick) { - ev.preventDefault(); - ev.stopPropagation(); - tab.onClick(ev); - } - if (tab.key) { - emit('update:tab', tab.key); - } -} - -const calcBg = () => { - const rawBg = metadata?.bg || 'var(--bg)'; - const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); - tinyBg.setAlpha(0.85); - bg.value = tinyBg.toRgbString(); -}; - -let ro: ResizeObserver | null; - -onMounted(() => { - calcBg(); - globalEvents.on('themeChanged', calcBg); - - watch(() => [props.tab, props.tabs], () => { - nextTick(() => { - const tabEl = props.tab ? tabRefs[props.tab] : undefined; - if (tabEl && tabHighlightEl && tabEl.parentElement) { - // offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある - // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4 - const parentRect = tabEl.parentElement.getBoundingClientRect(); - const rect = tabEl.getBoundingClientRect(); - tabHighlightEl.style.width = rect.width + 'px'; - tabHighlightEl.style.left = (rect.left - parentRect.left) + 'px'; - } - }); - }, { - immediate: true, - }); - - if (el && el.parentElement) { - narrow = el.parentElement.offsetWidth < 500; - ro = new ResizeObserver((entries, observer) => { - if (el.parentElement && document.body.contains(el as HTMLElement)) { - narrow = el.parentElement.offsetWidth < 500; - } - }); - ro.observe(el.parentElement as HTMLElement); - } -}); - -onUnmounted(() => { - globalEvents.off('themeChanged', calcBg); - if (ro) ro.disconnect(); -}); -</script> - -<style lang="scss" scoped> -.fdidabkb { - --height: 52px; - display: flex; - width: 100%; - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-bottom: solid 0.5px var(--divider); - contain: strict; - height: var(--height); - - &.thin { - --height: 42px; - - > .buttons { - > .button { - font-size: 0.9em; - } - } - } - - &.slim { - text-align: center; - - > .titleContainer { - flex: 1; - margin: 0 auto; - - > *:first-child { - margin-left: auto; - } - - > *:last-child { - margin-right: auto; - } - } - } - - > .buttons { - --margin: 8px; - display: flex; - align-items: center; - min-width: var(--height); - height: var(--height); - margin: 0 var(--margin); - - &.left { - margin-right: auto; - - > .avatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: bottom; - margin: 0 8px; - pointer-events: none; - } - } - - &.right { - margin-left: auto; - } - - &:empty { - width: var(--height); - } - - > .button { - display: flex; - align-items: center; - justify-content: center; - height: calc(var(--height) - (var(--margin) * 2)); - width: calc(var(--height) - (var(--margin) * 2)); - box-sizing: border-box; - position: relative; - border-radius: 5px; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - &.highlighted { - color: var(--accent); - } - } - - > .fullButton { - & + .fullButton { - margin-left: 12px; - } - } - } - - > .titleContainer { - display: flex; - align-items: center; - max-width: 400px; - overflow: auto; - white-space: nowrap; - text-align: left; - font-weight: bold; - flex-shrink: 0; - margin-left: 24px; - - > .avatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: bottom; - margin: 0 8px; - pointer-events: none; - } - - > .icon { - margin-right: 8px; - width: 16px; - text-align: center; - } - - > .title { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - line-height: 1.1; - - > .subtitle { - opacity: 0.6; - font-size: 0.8em; - font-weight: normal; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &.activeTab { - text-align: center; - - > .chevron { - display: inline-block; - margin-left: 6px; - } - } - } - } - } - - > .tabs { - position: relative; - margin-left: 16px; - font-size: 0.8em; - overflow: auto; - white-space: nowrap; - - > .tab { - display: inline-block; - position: relative; - padding: 0 10px; - height: 100%; - font-weight: normal; - opacity: 0.7; - - &:hover { - opacity: 1; - } - - &.active { - opacity: 1; - } - - > .icon + .title { - margin-left: 8px; - } - } - - > .highlight { - position: absolute; - bottom: 0; - height: 3px; - background: var(--accent); - border-radius: 999px; - transition: all 0.2s ease; - pointer-events: none; - } - } -} -</style> diff --git a/packages/client/src/components/global/MkSpacer.vue b/packages/client/src/components/global/MkSpacer.vue deleted file mode 100644 index b3a42d77e7..0000000000 --- a/packages/client/src/components/global/MkSpacer.vue +++ /dev/null @@ -1,96 +0,0 @@ -<template> -<div ref="root" :class="$style.root" :style="{ padding: margin + 'px' }"> - <div ref="content" :class="$style.content"> - <slot></slot> - </div> -</div> -</template> - -<script lang="ts" setup> -import { inject, onMounted, onUnmounted, ref } from 'vue'; -import { deviceKind } from '@/scripts/device-kind'; - -const props = withDefaults(defineProps<{ - contentMax?: number | null; - marginMin?: number; - marginMax?: number; -}>(), { - contentMax: null, - marginMin: 12, - marginMax: 24, -}); - -let ro: ResizeObserver; -let root = $ref<HTMLElement>(); -let content = $ref<HTMLElement>(); -let margin = $ref(0); -const widthHistory = [null, null] as [number | null, number | null]; -const heightHistory = [null, null] as [number | null, number | null]; -const shouldSpacerMin = inject('shouldSpacerMin', false); - -const adjust = (rect: { width: number; height: number; }) => { - if (shouldSpacerMin || deviceKind === 'smartphone') { - margin = props.marginMin; - return; - } - - if (rect.width > (props.contentMax ?? 0) || (rect.width > 360 && window.innerWidth > 400)) { - margin = props.marginMax; - } else { - margin = props.marginMin; - } -}; - -onMounted(() => { - ro = new ResizeObserver((entries) => { - /* iOSが対応していない - adjust({ - width: entries[0].borderBoxSize[0].inlineSize, - height: entries[0].borderBoxSize[0].blockSize, - }); - */ - - const width = root!.offsetWidth; - const height = root!.offsetHeight; - - //#region Prevent infinite resizing - // https://github.com/misskey-dev/misskey/issues/9076 - const pastWidth = widthHistory.pop(); - widthHistory.unshift(width); - const pastHeight = heightHistory.pop(); - heightHistory.unshift(height); - - - if (pastWidth === width && pastHeight === height) { - return; - } - //#endregion - - adjust({ - width, - height, - }); - }); - ro.observe(root!); - - if (props.contentMax) { - content!.style.maxWidth = `${props.contentMax}px`; - } -}); - -onUnmounted(() => { - ro.disconnect(); -}); -</script> - -<style lang="scss" module> -.root { - box-sizing: border-box; - width: 100%; -} - -.content { - margin: 0 auto; - container-type: inline-size; -} -</style> diff --git a/packages/client/src/components/global/MkStickyContainer.vue b/packages/client/src/components/global/MkStickyContainer.vue deleted file mode 100644 index 44f4f065a6..0000000000 --- a/packages/client/src/components/global/MkStickyContainer.vue +++ /dev/null @@ -1,66 +0,0 @@ -<template> -<div ref="rootEl"> - <div ref="headerEl"> - <slot name="header"></slot> - </div> - <div ref="bodyEl" :data-sticky-container-header-height="headerHeight"> - <slot></slot> - </div> -</div> -</template> - -<script lang="ts"> -// なんか動かない -//const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP'); -const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP'; -</script> - -<script lang="ts" setup> -import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue'; - -const rootEl = $ref<HTMLElement>(); -const headerEl = $ref<HTMLElement>(); -const bodyEl = $ref<HTMLElement>(); - -let headerHeight = $ref<string | undefined>(); -let childStickyTop = $ref(0); -const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0)); -provide(CURRENT_STICKY_TOP, $$(childStickyTop)); - -const calc = () => { - childStickyTop = parentStickyTop.value + headerEl.offsetHeight; - headerHeight = headerEl.offsetHeight.toString(); -}; - -const observer = new ResizeObserver(() => { - window.setTimeout(() => { - calc(); - }, 100); -}); - -onMounted(() => { - calc(); - - watch(parentStickyTop, calc); - - watch($$(childStickyTop), () => { - bodyEl.style.setProperty('--stickyTop', `${childStickyTop}px`); - }, { - immediate: true, - }); - - headerEl.style.position = 'sticky'; - headerEl.style.top = 'var(--stickyTop, 0)'; - headerEl.style.zIndex = '1000'; - - observer.observe(headerEl); -}); - -onUnmounted(() => { - observer.disconnect(); -}); -</script> - -<style lang="scss" module> - -</style> diff --git a/packages/client/src/components/global/MkTime.vue b/packages/client/src/components/global/MkTime.vue deleted file mode 100644 index f72b153f56..0000000000 --- a/packages/client/src/components/global/MkTime.vue +++ /dev/null @@ -1,56 +0,0 @@ -<template> -<time :title="absolute"> - <template v-if="mode === 'relative'">{{ relative }}</template> - <template v-else-if="mode === 'absolute'">{{ absolute }}</template> - <template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> -</time> -</template> - -<script lang="ts" setup> -import { onUnmounted } from 'vue'; -import { i18n } from '@/i18n'; - -const props = withDefaults(defineProps<{ - time: Date | string; - mode?: 'relative' | 'absolute' | 'detail'; -}>(), { - mode: 'relative', -}); - -const _time = typeof props.time === 'string' ? new Date(props.time) : props.time; -const absolute = _time.toLocaleString(); - -let now = $shallowRef(new Date()); -const relative = $computed(() => { - const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; - 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 >= -1 ? i18n.ts._ago.justNow : - i18n.ts._ago.future); -}); - -function tick() { - // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する - now = new Date(); - - tickId = window.setTimeout(() => { - window.requestAnimationFrame(tick); - }, 10000); -} - -let tickId: number; - -if (props.mode === 'relative' || props.mode === 'detail') { - tickId = window.requestAnimationFrame(tick); - - onUnmounted(() => { - window.cancelAnimationFrame(tickId); - }); -} -</script> diff --git a/packages/client/src/components/global/MkUrl.vue b/packages/client/src/components/global/MkUrl.vue deleted file mode 100644 index 9f5be96224..0000000000 --- a/packages/client/src/components/global/MkUrl.vue +++ /dev/null @@ -1,89 +0,0 @@ -<template> -<component - :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel" :target="target" - @contextmenu.stop="() => {}" -> - <template v-if="!self"> - <span class="schema">{{ schema }}//</span> - <span class="hostname">{{ hostname }}</span> - <span v-if="port != ''" class="port">:{{ port }}</span> - </template> - <template v-if="pathname === '/' && self"> - <span class="self">{{ hostname }}</span> - </template> - <span v-if="pathname != ''" class="pathname">{{ self ? pathname.substring(1) : pathname }}</span> - <span class="query">{{ query }}</span> - <span class="hash">{{ hash }}</span> - <i v-if="target === '_blank'" class="ti ti-external-link icon"></i> -</component> -</template> - -<script lang="ts" setup> -import { defineAsyncComponent, ref } from 'vue'; -import { toUnicode as decodePunycode } from 'punycode/'; -import { url as local } from '@/config'; -import * as os from '@/os'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { safeURIDecode } from '@/scripts/safe-uri-decode'; - -const props = defineProps<{ - url: string; - rel?: string; -}>(); - -const self = props.url.startsWith(local); -const url = new URL(props.url); -const el = ref(); - -useTooltip(el, (showing) => { - os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { - showing, - url: props.url, - source: el.value, - }, {}, 'closed'); -}); - -const schema = url.protocol; -const hostname = decodePunycode(url.hostname); -const port = url.port; -const pathname = safeURIDecode(url.pathname); -const query = safeURIDecode(url.search); -const hash = safeURIDecode(url.hash); -const attr = self ? 'to' : 'href'; -const target = self ? null : '_blank'; -</script> - -<style lang="scss" scoped> -.ieqqeuvs { - word-break: break-all; - - > .icon { - padding-left: 2px; - font-size: .9em; - } - - > .self { - font-weight: bold; - } - - > .schema { - opacity: 0.5; - } - - > .hostname { - font-weight: bold; - } - - > .pathname { - opacity: 0.8; - } - - > .query { - opacity: 0.5; - } - - > .hash { - font-style: italic; - } -} -</style> diff --git a/packages/client/src/components/global/MkUserName.vue b/packages/client/src/components/global/MkUserName.vue deleted file mode 100644 index 090de3df30..0000000000 --- a/packages/client/src/components/global/MkUserName.vue +++ /dev/null @@ -1,15 +0,0 @@ -<template> -<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import * as misskey from 'misskey-js'; - -const props = withDefaults(defineProps<{ - user: misskey.entities.User; - nowrap?: boolean; -}>(), { - nowrap: true, -}); -</script> diff --git a/packages/client/src/components/global/RouterView.vue b/packages/client/src/components/global/RouterView.vue deleted file mode 100644 index e21a57471c..0000000000 --- a/packages/client/src/components/global/RouterView.vue +++ /dev/null @@ -1,61 +0,0 @@ -<template> -<KeepAlive :max="defaultStore.state.numberOfPageCache"> - <Suspense> - <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/> - - <template #fallback> - <MkLoading/> - </template> - </Suspense> -</KeepAlive> -</template> - -<script lang="ts" setup> -import { inject, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, watch } from 'vue'; -import { Resolved, Router } from '@/nirax'; -import { defaultStore } from '@/store'; - -const props = defineProps<{ - router?: Router; -}>(); - -const router = props.router ?? inject('router'); - -if (router == null) { - throw new Error('no router provided'); -} - -const currentDepth = inject('routerCurrentDepth', 0); -provide('routerCurrentDepth', currentDepth + 1); - -function resolveNested(current: Resolved, d = 0): Resolved | null { - if (d === currentDepth) { - return current; - } else { - if (current.child) { - return resolveNested(current.child, d + 1); - } else { - return 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))); - -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)); -} - -router.addListener('change', onChange); - -onBeforeUnmount(() => { - router.removeListener('change', onChange); -}); -</script> diff --git a/packages/client/src/components/global/i18n.ts b/packages/client/src/components/global/i18n.ts deleted file mode 100644 index 1fd293ba10..0000000000 --- a/packages/client/src/components/global/i18n.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { h, defineComponent } from 'vue'; - -export default defineComponent({ - props: { - src: { - type: String, - required: true, - }, - tag: { - type: String, - required: false, - default: 'span', - }, - textTag: { - type: String, - required: false, - default: null, - }, - }, - render() { - let str = this.src; - const parsed = [] as (string | { arg: string; })[]; - while (true) { - const nextBracketOpen = str.indexOf('{'); - const nextBracketClose = str.indexOf('}'); - - if (nextBracketOpen === -1) { - parsed.push(str); - break; - } else { - if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen)); - parsed.push({ - arg: str.substring(nextBracketOpen + 1, nextBracketClose), - }); - } - - str = str.substr(nextBracketClose + 1); - } - - return h(this.tag, parsed.map(x => typeof x === 'string' ? (this.textTag ? h(this.textTag, x) : x) : this.$slots[x.arg]())); - }, -}); |