diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
| commit | 0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch) | |
| tree | 40874799472fa07416f17b50a398ac33b7771905 /src/client/components/global | |
| parent | update deps (diff) | |
| download | misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2 misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip | |
refactoring
Resolve #7779
Diffstat (limited to 'src/client/components/global')
| -rw-r--r-- | src/client/components/global/a.vue | 138 | ||||
| -rw-r--r-- | src/client/components/global/acct.vue | 38 | ||||
| -rw-r--r-- | src/client/components/global/ad.vue | 200 | ||||
| -rw-r--r-- | src/client/components/global/avatar.vue | 163 | ||||
| -rw-r--r-- | src/client/components/global/ellipsis.vue | 34 | ||||
| -rw-r--r-- | src/client/components/global/emoji.vue | 125 | ||||
| -rw-r--r-- | src/client/components/global/error.vue | 46 | ||||
| -rw-r--r-- | src/client/components/global/header.vue | 360 | ||||
| -rw-r--r-- | src/client/components/global/i18n.ts | 42 | ||||
| -rw-r--r-- | src/client/components/global/loading.vue | 92 | ||||
| -rw-r--r-- | src/client/components/global/misskey-flavored-markdown.vue | 157 | ||||
| -rw-r--r-- | src/client/components/global/spacer.vue | 76 | ||||
| -rw-r--r-- | src/client/components/global/sticky-container.vue | 74 | ||||
| -rw-r--r-- | src/client/components/global/time.vue | 73 | ||||
| -rw-r--r-- | src/client/components/global/url.vue | 142 | ||||
| -rw-r--r-- | src/client/components/global/user-name.vue | 20 |
16 files changed, 0 insertions, 1780 deletions
diff --git a/src/client/components/global/a.vue b/src/client/components/global/a.vue deleted file mode 100644 index 952dfb1841..0000000000 --- a/src/client/components/global/a.vue +++ /dev/null @@ -1,138 +0,0 @@ -<template> -<a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu"> - <slot></slot> -</a> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@client/os'; -import copyToClipboard from '@client/scripts/copy-to-clipboard'; -import { router } from '@client/router'; -import { url } from '@client/config'; -import { popout } from '@client/scripts/popout'; -import { ColdDeviceStorage } from '@client/store'; - -export default defineComponent({ - inject: { - navHook: { - default: null - }, - sideViewHook: { - default: null - } - }, - - props: { - to: { - type: String, - required: true, - }, - activeClass: { - type: String, - required: false, - }, - behavior: { - type: String, - required: false, - }, - }, - - computed: { - active() { - if (this.activeClass == null) return false; - const resolved = router.resolve(this.to); - if (resolved.path == this.$route.path) return true; - if (resolved.name == null) return false; - if (this.$route.name == null) return false; - return resolved.name == this.$route.name; - } - }, - - methods: { - onContextmenu(e) { - if (window.getSelection().toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: this.to, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.to); - } - }, this.sideViewHook ? { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.sideViewHook(this.to); - } - } : undefined, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.to); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.to, '_blank'); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(`${url}${this.to}`); - } - }], e); - }, - - window() { - os.pageWindow(this.to); - }, - - modalWindow() { - os.modalPageWindow(this.to); - }, - - popout() { - popout(this.to); - }, - - nav() { - if (this.behavior === 'browser') { - location.href = this.to; - return; - } - - if (this.to.startsWith('/my/messaging')) { - if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window(); - if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout(); - } - - if (this.behavior) { - if (this.behavior === 'window') { - return this.window(); - } else if (this.behavior === 'modalWindow') { - return this.modalWindow(); - } - } - - if (this.navHook) { - this.navHook(this.to); - } else { - if (this.$store.state.defaultSideView && this.sideViewHook && this.to !== '/') { - return this.sideViewHook(this.to); - } - - if (this.$router.currentRoute.value.path === this.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - this.$router.push(this.to); - } - } - } - } -}); -</script> diff --git a/src/client/components/global/acct.vue b/src/client/components/global/acct.vue deleted file mode 100644 index 70f2954cb0..0000000000 --- a/src/client/components/global/acct.vue +++ /dev/null @@ -1,38 +0,0 @@ -<template> -<span class="mk-acct"> - <span class="name">@{{ user.username }}</span> - <span class="host" v-if="user.host || detail || $store.state.showFullAcct">@{{ user.host || host }}</span> -</span> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { toUnicode } from 'punycode/'; -import { host } from '@client/config'; - -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - detail: { - type: Boolean, - default: false - }, - }, - data() { - return { - host: toUnicode(host), - }; - } -}); -</script> - -<style lang="scss" scoped> -.mk-acct { - > .host { - opacity: 0.5; - } -} -</style> diff --git a/src/client/components/global/ad.vue b/src/client/components/global/ad.vue deleted file mode 100644 index 8397b2229e..0000000000 --- a/src/client/components/global/ad.vue +++ /dev/null @@ -1,200 +0,0 @@ -<template> -<div class="qiivuoyo" v-if="ad"> - <div class="main" :class="ad.place" v-if="!showMenu"> - <a :href="ad.url" target="_blank"> - <img :src="ad.imageUrl"> - <button class="_button menu" @click.prevent.stop="toggleMenu"><span class="fas fa-info-circle"></span></button> - </a> - </div> - <div class="menu" v-else> - <div class="body"> - <div>Ads by {{ host }}</div> - <!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>--> - <MkButton v-if="ad.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"> -import { defineComponent, ref } from 'vue'; -import { Instance, instance } from '@client/instance'; -import { host } from '@client/config'; -import MkButton from '@client/components/ui/button.vue'; -import { defaultStore } from '@client/store'; -import * as os from '@client/os'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - prefer: { - type: Array, - required: true - }, - specify: { - type: Object, - required: false - }, - }, - - setup(props) { - const showMenu = ref(false); - const toggleMenu = () => { - showMenu.value = !showMenu.value; - }; - - const choseAd = (): Instance['ads'][number] | null => { - if (props.specify) { - return props.specify as Instance['ads'][number]; - } - - 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()); - - const reduceFrequency = () => { - 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; - }; - - return { - ad: chosen, - showMenu, - toggleMenu, - host, - reduceFrequency, - }; - } -}); -</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; - } - - > .menu { - position: absolute; - top: 0; - right: 0; - 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/src/client/components/global/avatar.vue b/src/client/components/global/avatar.vue deleted file mode 100644 index 395ed5d8ce..0000000000 --- a/src/client/components/global/avatar.vue +++ /dev/null @@ -1,163 +0,0 @@ -<template> -<span class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick"> - <img class="inner" :src="url" decoding="async"/> - <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> -</span> -<MkA class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> - <img class="inner" :src="url" decoding="async"/> - <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> -</MkA> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { getStaticImageUrl } from '@client/scripts/get-static-image-url'; -import { extractAvgColorFromBlurhash } from '@client/scripts/extract-avg-color-from-blurhash'; -import { acct, userPage } from '@client/filters/user'; -import MkUserOnlineIndicator from '@client/components/user-online-indicator.vue'; - -export default defineComponent({ - components: { - MkUserOnlineIndicator - }, - props: { - user: { - type: Object, - required: true - }, - target: { - required: false, - default: null - }, - disableLink: { - required: false, - default: false - }, - disablePreview: { - required: false, - default: false - }, - showIndicator: { - required: false, - default: false - } - }, - emits: ['click'], - computed: { - cat(): boolean { - return this.user.isCat; - }, - url(): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(this.user.avatarUrl) - : this.user.avatarUrl; - }, - }, - watch: { - 'user.avatarBlurhash'() { - if (this.$el == null) return; - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - } - }, - mounted() { - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - }, - methods: { - onClick(e) { - this.$emit('click', e); - }, - acct, - userPage - } -}); -</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/src/client/components/global/ellipsis.vue b/src/client/components/global/ellipsis.vue deleted file mode 100644 index 0a46f486d6..0000000000 --- a/src/client/components/global/ellipsis.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/src/client/components/global/emoji.vue b/src/client/components/global/emoji.vue deleted file mode 100644 index f92e35c38f..0000000000 --- a/src/client/components/global/emoji.vue +++ /dev/null @@ -1,125 +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" :title="alt" decoding="async"/> -<span v-else-if="char && useOsNativeEmojis">{{ char }}</span> -<span v-else>{{ emoji }}</span> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { getStaticImageUrl } from '@client/scripts/get-static-image-url'; -import { twemojiSvgBase } from '@client/../misc/twemoji-base'; - -export default defineComponent({ - props: { - emoji: { - type: String, - required: true - }, - normal: { - type: Boolean, - required: false, - default: false - }, - noStyle: { - type: Boolean, - required: false, - default: false - }, - customEmojis: { - required: false - }, - isReaction: { - type: Boolean, - default: false - }, - }, - - data() { - return { - url: null, - char: null, - customEmoji: null - } - }, - - computed: { - isCustom(): boolean { - return this.emoji.startsWith(':'); - }, - - alt(): string { - return this.customEmoji ? `:${this.customEmoji.name}:` : this.char; - }, - - useOsNativeEmojis(): boolean { - return this.$store.state.useOsNativeEmojis && !this.isReaction; - }, - - ce() { - return this.customEmojis || this.$instance?.emojis || []; - } - }, - - watch: { - ce: { - handler() { - if (this.isCustom) { - const customEmoji = this.ce.find(x => x.name === this.emoji.substr(1, this.emoji.length - 2)); - if (customEmoji) { - this.customEmoji = customEmoji; - this.url = this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(customEmoji.url) - : customEmoji.url; - } - } - }, - immediate: true - }, - }, - - created() { - if (!this.isCustom) { - this.char = this.emoji; - } - - if (this.char) { - let codes = Array.from(this.char).map(x => x.codePointAt(0).toString(16)); - if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); - codes = codes.filter(x => x && x.length); - - this.url = `${twemojiSvgBase}/${codes.join('-')}.svg`; - } - }, -}); -</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/src/client/components/global/error.vue b/src/client/components/global/error.vue deleted file mode 100644 index 05a508a653..0000000000 --- a/src/client/components/global/error.vue +++ /dev/null @@ -1,46 +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="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</p> - <MkButton @click="() => $emit('retry')" class="button">{{ $ts.retry }}</MkButton> - </div> -</transition> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@client/components/ui/button.vue'; - -export default defineComponent({ - components: { - MkButton, - }, - data() { - return { - }; - }, -}); -</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/src/client/components/global/header.vue b/src/client/components/global/header.vue deleted file mode 100644 index 526db07fd3..0000000000 --- a/src/client/components/global/header.vue +++ /dev/null @@ -1,360 +0,0 @@ -<template> -<div class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick" ref="el"> - <template v-if="info"> - <div class="titleContainer" @click="showTabsPopup" v-if="!hideTitle"> - <MkAvatar v-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> - <i v-else-if="info.icon" class="icon" :class="info.icon"></i> - - <div class="title"> - <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> - <div v-else-if="info.title" class="title">{{ info.title }}</div> - <div class="subtitle" v-if="!narrow && info.subtitle"> - {{ info.subtitle }} - </div> - <div class="subtitle activeTab" v-if="narrow && hasTabs"> - {{ info.tabs.find(tab => tab.active)?.title }} - <i class="chevron fas fa-chevron-down"></i> - </div> - </div> - </div> - <div class="tabs" v-if="!narrow || hideTitle"> - <button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title"> - <i v-if="tab.icon" class="icon" :class="tab.icon"></i> - <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> - </button> - </div> - </template> - <div class="buttons right"> - <template v-if="info && info.actions && !narrow"> - <template v-for="action in info.actions"> - <MkButton class="fullButton" v-if="action.asFullButton" @click.stop="action.handler" primary><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton> - <button v-else class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button> - </template> - </template> - <button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue'; -import * as tinycolor from 'tinycolor2'; -import { popupMenu } from '@client/os'; -import { url } from '@client/config'; -import { scrollToTop } from '@client/scripts/scroll'; -import MkButton from '@client/components/ui/button.vue'; -import { i18n } from '@client/i18n'; -import { globalEvents } from '@client/events'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - info: { - type: Object as PropType<{ - actions?: {}[]; - tabs?: {}[]; - }>, - required: true - }, - menu: { - required: false - }, - thin: { - required: false, - default: false - }, - }, - - setup(props) { - const el = ref<HTMLElement>(null); - const bg = ref(null); - const narrow = ref(false); - const height = ref(0); - const hasTabs = computed(() => { - return props.info.tabs && props.info.tabs.length > 0; - }); - const shouldShowMenu = computed(() => { - if (props.info == null) return false; - if (props.info.actions != null && narrow.value) return true; - if (props.info.menu != null) return true; - if (props.info.share != null) return true; - if (props.menu != null) return true; - return false; - }); - - const share = () => { - navigator.share({ - url: url + props.info.path, - ...props.info.share, - }); - }; - - const showMenu = (ev: MouseEvent) => { - let menu = props.info.menu ? props.info.menu() : []; - if (narrow.value && props.info.actions) { - menu = [...props.info.actions.map(x => ({ - text: x.text, - icon: x.icon, - action: x.handler - })), menu.length > 0 ? null : undefined, ...menu]; - } - if (props.info.share) { - if (menu.length > 0) menu.push(null); - menu.push({ - text: i18n.locale.share, - icon: 'fas fa-share-alt', - action: share - }); - } - if (props.menu) { - if (menu.length > 0) menu.push(null); - menu = menu.concat(props.menu); - } - popupMenu(menu, ev.currentTarget || ev.target); - }; - - const showTabsPopup = (ev: MouseEvent) => { - if (!hasTabs.value) return; - if (!narrow.value) return; - ev.preventDefault(); - ev.stopPropagation(); - const menu = props.info.tabs.map(tab => ({ - text: tab.title, - icon: tab.icon, - action: tab.onClick, - })); - popupMenu(menu, ev.currentTarget || ev.target); - }; - - const preventDrag = (ev: TouchEvent) => { - ev.stopPropagation(); - }; - - const onClick = () => { - scrollToTop(el.value, { behavior: 'smooth' }); - }; - - const calcBg = () => { - const rawBg = props.info?.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(); - }; - - onMounted(() => { - calcBg(); - globalEvents.on('themeChanged', calcBg); - onUnmounted(() => { - globalEvents.off('themeChanged', calcBg); - }); - - if (el.value.parentElement) { - narrow.value = el.value.parentElement.offsetWidth < 500; - const ro = new ResizeObserver((entries, observer) => { - if (el.value) { - narrow.value = el.value.parentElement.offsetWidth < 500; - } - }); - ro.observe(el.value.parentElement); - onUnmounted(() => { - ro.disconnect(); - }); - } - }); - - return { - el, - bg, - narrow, - height, - hasTabs, - shouldShowMenu, - share, - showMenu, - showTabsPopup, - preventDrag, - onClick, - hideTitle: inject('shouldOmitHeaderTitle', false), - thin_: props.thin || inject('shouldHeaderThin', false) - }; - }, -}); -</script> - -<style lang="scss" scoped> -.fdidabkb { - --height: 60px; - display: flex; - position: sticky; - top: var(--stickyTop, 0); - z-index: 1000; - width: 100%; - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-bottom: solid 0.5px var(--divider); - - &.thin { - --height: 50px; - - > .buttons { - > .button { - font-size: 0.9em; - } - } - } - - &.slim { - text-align: center; - - > .titleContainer { - flex: 1; - margin: 0 auto; - margin-left: var(--height); - - > *:first-child { - margin-left: auto; - } - - > *:last-child { - margin-right: auto; - } - } - } - - > .buttons { - --margin: 8px; - display: flex; - align-items: center; - height: var(--height); - margin: 0 var(--margin); - - &.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; - 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; - } - - > .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 { - 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; - - &:after { - content: ""; - display: block; - position: absolute; - bottom: 0; - left: 0; - right: 0; - margin: 0 auto; - width: 100%; - height: 3px; - background: var(--accent); - } - } - - > .icon + .title { - margin-left: 8px; - } - } - } -} -</style> diff --git a/src/client/components/global/i18n.ts b/src/client/components/global/i18n.ts deleted file mode 100644 index abf0c96856..0000000000 --- a/src/client/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]())); - } -}); diff --git a/src/client/components/global/loading.vue b/src/client/components/global/loading.vue deleted file mode 100644 index 7bde53c12e..0000000000 --- a/src/client/components/global/loading.vue +++ /dev/null @@ -1,92 +0,0 @@ -<template> -<div class="yxspomdl" :class="{ inline, colored, mini }"> - <div class="ring"></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - inline: { - type: Boolean, - required: false, - default: false - }, - colored: { - type: Boolean, - required: false, - default: true - }, - mini: { - type: Boolean, - required: false, - default: false - }, - } -}); -</script> - -<style lang="scss" scoped> -@keyframes ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -.yxspomdl { - padding: 32px; - text-align: center; - cursor: wait; - - --size: 48px; - - &.colored { - color: var(--accent); - } - - &.inline { - display: inline; - padding: 0; - --size: 32px; - } - - &.mini { - padding: 16px; - --size: 32px; - } - - > .ring { - position: relative; - display: inline-block; - vertical-align: middle; - - &:before, - &:after { - content: " "; - display: block; - box-sizing: border-box; - width: var(--size); - height: var(--size); - border-radius: 50%; - border: solid 4px; - } - - &:before { - border-color: currentColor; - opacity: 0.3; - } - - &:after { - position: absolute; - top: 0; - border-color: currentColor transparent transparent transparent; - animation: ring 0.5s linear infinite; - } - } -} -</style> diff --git a/src/client/components/global/misskey-flavored-markdown.vue b/src/client/components/global/misskey-flavored-markdown.vue deleted file mode 100644 index c4f75bee93..0000000000 --- a/src/client/components/global/misskey-flavored-markdown.vue +++ /dev/null @@ -1,157 +0,0 @@ -<template> -<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MfmCore from '@client/components/mfm'; - -export default defineComponent({ - components: { - MfmCore - } -}); -</script> - -<style lang="scss"> -._mfm_blur_ { - filter: blur(6px); - transition: filter 0.3s; - - &:hover { - filter: blur(0px); - } -} - -@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/src/client/components/global/spacer.vue b/src/client/components/global/spacer.vue deleted file mode 100644 index 1129d54c71..0000000000 --- a/src/client/components/global/spacer.vue +++ /dev/null @@ -1,76 +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"> -import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; - -export default defineComponent({ - props: { - contentMax: { - type: Number, - required: false, - default: null, - } - }, - - setup(props, context) { - let ro: ResizeObserver; - const root = ref<HTMLElement>(null); - const content = ref<HTMLElement>(null); - const margin = ref(0); - const adjust = (rect: { width: number; height: number; }) => { - if (rect.width > (props.contentMax || 500)) { - margin.value = 32; - } else { - margin.value = 12; - } - }; - - onMounted(() => { - ro = new ResizeObserver((entries) => { - /* iOSが対応していない - adjust({ - width: entries[0].borderBoxSize[0].inlineSize, - height: entries[0].borderBoxSize[0].blockSize, - }); - */ - adjust({ - width: root.value.offsetWidth, - height: root.value.offsetHeight, - }); - }); - ro.observe(root.value); - - if (props.contentMax) { - content.value.style.maxWidth = `${props.contentMax}px`; - } - }); - - onUnmounted(() => { - ro.disconnect(); - }); - - return { - root, - content, - margin, - }; - }, -}); -</script> - -<style lang="scss" module> -.root { - box-sizing: border-box; - width: 100%; -} - -.content { - margin: 0 auto; -} -</style> diff --git a/src/client/components/global/sticky-container.vue b/src/client/components/global/sticky-container.vue deleted file mode 100644 index 859b2c1d73..0000000000 --- a/src/client/components/global/sticky-container.vue +++ /dev/null @@ -1,74 +0,0 @@ -<template> -<div ref="rootEl"> - <slot name="header"></slot> - <div ref="bodyEl"> - <slot></slot> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; - -export default defineComponent({ - props: { - autoSticky: { - type: Boolean, - required: false, - default: false, - }, - }, - - setup(props, context) { - const rootEl = ref<HTMLElement>(null); - const bodyEl = ref<HTMLElement>(null); - - const calc = () => { - const currentStickyTop = getComputedStyle(rootEl.value).getPropertyValue('--stickyTop') || '0px'; - - const header = rootEl.value.children[0]; - if (header === bodyEl.value) { - bodyEl.value.style.setProperty('--stickyTop', currentStickyTop); - } else { - bodyEl.value.style.setProperty('--stickyTop', `calc(${currentStickyTop} + ${header.offsetHeight}px)`); - - if (props.autoSticky) { - header.style.setProperty('--stickyTop', currentStickyTop); - header.style.position = 'sticky'; - header.style.top = 'var(--stickyTop)'; - header.style.zIndex = '1'; - } - } - }; - - onMounted(() => { - calc(); - - const observer = new MutationObserver(() => { - setTimeout(() => { - calc(); - }, 100); - }); - - observer.observe(rootEl.value, { - attributes: false, - childList: true, - subtree: false, - }); - - onUnmounted(() => { - observer.disconnect(); - }); - }); - - return { - rootEl, - bodyEl, - }; - }, -}); -</script> - -<style lang="scss" module> - -</style> diff --git a/src/client/components/global/time.vue b/src/client/components/global/time.vue deleted file mode 100644 index 6a330a2307..0000000000 --- a/src/client/components/global/time.vue +++ /dev/null @@ -1,73 +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"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - time: { - type: [Date, String], - required: true - }, - mode: { - type: String, - default: 'relative' - } - }, - data() { - return { - tickId: null, - now: new Date() - }; - }, - computed: { - _time(): Date { - return typeof this.time == 'string' ? new Date(this.time) : this.time; - }, - absolute(): string { - return this._time.toLocaleString(); - }, - relative(): string { - const time = this._time; - const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/; - return ( - ago >= 31536000 ? this.$t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : - ago >= 2592000 ? this.$t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : - ago >= 604800 ? this.$t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : - ago >= 86400 ? this.$t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : - ago >= 3600 ? this.$t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : - ago >= 60 ? this.$t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : - ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? this.$ts._ago.justNow : - ago < -1 ? this.$ts._ago.future : - this.$ts._ago.unknown); - } - }, - created() { - if (this.mode == 'relative' || this.mode == 'detail') { - this.tickId = window.requestAnimationFrame(this.tick); - } - }, - unmounted() { - if (this.mode === 'relative' || this.mode === 'detail') { - window.clearTimeout(this.tickId); - } - }, - methods: { - tick() { - // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する - this.now = new Date(); - - this.tickId = setTimeout(() => { - window.requestAnimationFrame(this.tick); - }, 10000); - } - } -}); -</script> diff --git a/src/client/components/global/url.vue b/src/client/components/global/url.vue deleted file mode 100644 index 218729882d..0000000000 --- a/src/client/components/global/url.vue +++ /dev/null @@ -1,142 +0,0 @@ -<template> -<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" - @mouseover="onMouseover" - @mouseleave="onMouseleave" - @contextmenu.stop="() => {}" -> - <template v-if="!self"> - <span class="schema">{{ schema }}//</span> - <span class="hostname">{{ hostname }}</span> - <span class="port" v-if="port != ''">:{{ port }}</span> - </template> - <template v-if="pathname === '/' && self"> - <span class="self">{{ hostname }}</span> - </template> - <span class="pathname" v-if="pathname != ''">{{ self ? pathname.substr(1) : pathname }}</span> - <span class="query">{{ query }}</span> - <span class="hash">{{ hash }}</span> - <i v-if="target === '_blank'" class="fas fa-external-link-square-alt icon"></i> -</component> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { toUnicode as decodePunycode } from 'punycode/'; -import { url as local } from '@client/config'; -import { isDeviceTouch } from '@client/scripts/is-device-touch'; -import * as os from '@client/os'; - -export default defineComponent({ - props: { - url: { - type: String, - required: true, - }, - rel: { - type: String, - required: false, - } - }, - data() { - const self = this.url.startsWith(local); - return { - local, - schema: null as string | null, - hostname: null as string | null, - port: null as string | null, - pathname: null as string | null, - query: null as string | null, - hash: null as string | null, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - showTimer: null, - hideTimer: null, - checkTimer: null, - close: null, - }; - }, - created() { - const url = new URL(this.url); - this.schema = url.protocol; - this.hostname = decodePunycode(url.hostname); - this.port = url.port; - this.pathname = decodeURIComponent(url.pathname); - this.query = decodeURIComponent(url.search); - this.hash = decodeURIComponent(url.hash); - }, - methods: { - async showPreview() { - if (!document.body.contains(this.$el)) return; - if (this.close) return; - - const { dispose } = await os.popup(import('@client/components/url-preview-popup.vue'), { - url: this.url, - source: this.$el - }); - - this.close = () => { - dispose(); - }; - - this.checkTimer = setInterval(() => { - if (!document.body.contains(this.$el)) this.closePreview(); - }, 1000); - }, - closePreview() { - if (this.close) { - clearInterval(this.checkTimer); - this.close(); - this.close = null; - } - }, - onMouseover() { - if (isDeviceTouch) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.showPreview, 500); - }, - onMouseleave() { - if (isDeviceTouch) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.closePreview, 500); - } - } -}); -</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/src/client/components/global/user-name.vue b/src/client/components/global/user-name.vue deleted file mode 100644 index bc93a8ea30..0000000000 --- a/src/client/components/global/user-name.vue +++ /dev/null @@ -1,20 +0,0 @@ -<template> -<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - nowrap: { - type: Boolean, - default: true - }, - } -}); -</script> |