自前ルーティング (#6759)
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip
This commit is contained in:
		
							parent
							
								
									d4da5a1eea
								
							
						
					
					
						commit
						254cfaea28
					
				
					 59 changed files with 625 additions and 220 deletions
				
			
		|  | @ -593,6 +593,9 @@ fillAbuseReportDescription: "通報理由の詳細を記入してください。 | |||
| abuseReported: "内容が送信されました。ご報告ありがとうございました。" | ||||
| send: "送信" | ||||
| abuseMarkAsResolved: "対応済みにする" | ||||
| openInNewTab: "新しいタブで開く" | ||||
| openInSideView: "サイドビューで開く" | ||||
| defaultNavigationBehaviour: "デフォルトのナビゲーション" | ||||
| 
 | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自動でリロード" | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| <span class="eiwwqkts" :class="{ cat }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick"> | ||||
| 	<img class="inner" :src="url"/> | ||||
| </span> | ||||
| <router-link class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> | ||||
| <MkA class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> | ||||
| 	<img class="inner" :src="url"/> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <router-link :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> | ||||
| <MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> | ||||
| 	<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`"> | ||||
| 		<div class="fade"></div> | ||||
| 		<div class="name"><Fa :icon="faSatelliteDish"/> {{ channel.name }}</div> | ||||
|  | @ -30,7 +30,7 @@ | |||
| 			{{ $t('updatedAt') }}: <MkTime :time="channel.lastNotedAt"/> | ||||
| 		</span> | ||||
| 	</footer> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { App } from 'vue'; | ||||
| 
 | ||||
| import mfm from './misskey-flavored-markdown.vue'; | ||||
| import a from './ui/a.vue'; | ||||
| import acct from './acct.vue'; | ||||
| import avatar from './avatar.vue'; | ||||
| import emoji from './emoji.vue'; | ||||
|  | @ -10,10 +11,10 @@ import time from './time.vue'; | |||
| import url from './url.vue'; | ||||
| import loading from './loading.vue'; | ||||
| import error from './error.vue'; | ||||
| import streamIndicator from './stream-indicator.vue'; | ||||
| 
 | ||||
| export default function(app: App) { | ||||
| 	app.component('Mfm', mfm); | ||||
| 	app.component('MkA', a); | ||||
| 	app.component('MkAcct', acct); | ||||
| 	app.component('MkAvatar', avatar); | ||||
| 	app.component('MkEmoji', emoji); | ||||
|  | @ -23,5 +24,4 @@ export default function(app: App) { | |||
| 	app.component('MkUrl', url); | ||||
| 	app.component('MkLoading', loading); | ||||
| 	app.component('MkError', error); | ||||
| 	app.component('StreamIndicator', streamIndicator); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <component :is="self ? 'router-link' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| <component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseleave="onMouseleave" | ||||
| 	:title="url" | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| <template> | ||||
| <router-link class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> | ||||
| <MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> | ||||
| 	<span class="me" v-if="isMe">{{ $t('you') }}</span> | ||||
| 	<span class="main"> | ||||
| 		<span class="username">@{{ username }}</span> | ||||
| 		<span class="host" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span> | ||||
| 	</span> | ||||
| </router-link> | ||||
| </MkA> | ||||
| <a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else> | ||||
| 	<span class="main"> | ||||
| 		<span class="username">@{{ username }}</span> | ||||
|  |  | |||
|  | @ -9,8 +9,8 @@ import { concat } from '../../prelude/array'; | |||
| import MkFormula from './formula.vue'; | ||||
| import MkCode from './code.vue'; | ||||
| import MkGoogle from './google.vue'; | ||||
| import MkA from './ui/a.vue'; | ||||
| import { host } from '@/config'; | ||||
| import { RouterLink } from 'vue-router'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
|  | @ -150,7 +150,7 @@ export default defineComponent({ | |||
| 				} | ||||
| 
 | ||||
| 				case 'hashtag': { | ||||
| 					return [h(RouterLink, { | ||||
| 					return [h(MkA, { | ||||
| 						key: Math.random(), | ||||
| 						to: this.isNote ? `/tags/${encodeURIComponent(token.node.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.node.props.hashtag)}`, | ||||
| 						style: 'color:var(--hashtag);' | ||||
|  |  | |||
|  | @ -1,17 +1,17 @@ | |||
| <template> | ||||
| <header class="kkwtjztg"> | ||||
| 	<router-link class="name" :to="userPage(note.user)" v-user-preview="note.user.id"> | ||||
| 	<MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id"> | ||||
| 		<MkUserName :user="note.user"/> | ||||
| 	</router-link> | ||||
| 	</MkA> | ||||
| 	<span class="is-bot" v-if="note.user.isBot">bot</span> | ||||
| 	<span class="username"><MkAcct :user="note.user"/></span> | ||||
| 	<span class="admin" v-if="note.user.isAdmin"><Fa :icon="faBookmark"/></span> | ||||
| 	<span class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><Fa :icon="farBookmark"/></span> | ||||
| 	<div class="info"> | ||||
| 		<span class="mobile" v-if="note.viaMobile"><Fa :icon="faMobileAlt"/></span> | ||||
| 		<router-link class="created-at" :to="notePage(note)"> | ||||
| 		<MkA class="created-at" :to="notePage(note)"> | ||||
| 			<MkTime :time="note.createdAt"/> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<span class="visibility" v-if="note.visibility !== 'public'"> | ||||
| 			<Fa v-if="note.visibility === 'home'" :icon="faHome"/> | ||||
| 			<Fa v-if="note.visibility === 'followers'" :icon="faUnlock"/> | ||||
|  |  | |||
|  | @ -18,9 +18,9 @@ | |||
| 		<Fa :icon="faRetweet"/> | ||||
| 		<i18n-t keypath="renotedBy" tag="span"> | ||||
| 			<template #user> | ||||
| 				<router-link class="name" :to="userPage(note.user)" v-user-preview="note.userId"> | ||||
| 				<MkA class="name" :to="userPage(note.user)" v-user-preview="note.userId"> | ||||
| 					<MkUserName :user="note.user"/> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 			</template> | ||||
| 		</i18n-t> | ||||
| 		<div class="info"> | ||||
|  | @ -48,7 +48,7 @@ | |||
| 				<div class="content" v-show="appearNote.cw == null || showContent"> | ||||
| 					<div class="text"> | ||||
| 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> | ||||
| 						<router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></router-link> | ||||
| 						<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></MkA> | ||||
| 						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/> | ||||
| 						<a class="rp" v-if="appearNote.renote != null">RN:</a> | ||||
| 					</div> | ||||
|  | @ -59,7 +59,7 @@ | |||
| 					<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/> | ||||
| 					<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div> | ||||
| 				</div> | ||||
| 				<router-link v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</router-link> | ||||
| 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</MkA> | ||||
| 			</div> | ||||
| 			<footer class="footer"> | ||||
| 				<XReactionsViewer :note="appearNote" ref="reactionsViewer"/> | ||||
|  | @ -91,9 +91,9 @@ | |||
| <div v-else class="_panel muted" @click="muted = false"> | ||||
| 	<i18n-t keypath="userSaysSomething" tag="small"> | ||||
| 		<template #name> | ||||
| 			<router-link class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId"> | ||||
| 			<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId"> | ||||
| 				<MkUserName :user="appearNote.user"/> | ||||
| 			</router-link> | ||||
| 			</MkA> | ||||
| 		</template> | ||||
| 	</i18n-t> | ||||
| </div> | ||||
|  | @ -144,7 +144,7 @@ export default defineComponent({ | |||
| 	inject: { | ||||
| 		inChannel: { | ||||
| 			default: null | ||||
| 		} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
|  | @ -581,11 +581,6 @@ export default defineComponent({ | |||
| 				}); | ||||
| 
 | ||||
| 				menu = [{ | ||||
| 					type: 'link', | ||||
| 					icon: faInfoCircle, | ||||
| 					text: this.$t('details'), | ||||
| 					to: '/notes/' + this.appearNote.id | ||||
| 				}, null, { | ||||
| 					icon: faCopy, | ||||
| 					text: this.$t('copyContent'), | ||||
| 					action: this.copyContent | ||||
|  |  | |||
|  | @ -18,34 +18,34 @@ | |||
| 	</div> | ||||
| 	<div class="tail"> | ||||
| 		<header> | ||||
| 			<router-link v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></router-link> | ||||
| 			<MkA v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></MkA> | ||||
| 			<span v-else>{{ notification.header }}</span> | ||||
| 			<MkTime :time="notification.createdAt" v-if="withTime"/> | ||||
| 		</header> | ||||
| 		<router-link v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span> | ||||
| 		<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span> | ||||
| 		<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> | ||||
| <MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> | ||||
| 	<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div> | ||||
| 	<article> | ||||
| 		<header> | ||||
|  | @ -11,7 +11,7 @@ | |||
| 			<p>{{ userName(page.user) }}</p> | ||||
| 		</footer> | ||||
| 	</article> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|  |  | |||
|  | @ -1,11 +1,18 @@ | |||
| <template> | ||||
| <XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> | ||||
| <XWindow ref="window" | ||||
| 	:initial-width="400" | ||||
| 	:initial-height="500" | ||||
| 	:can-resize="true" | ||||
| 	:close-right="true" | ||||
| 	:contextmenu="contextmenu" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<template #header> | ||||
| 		<XHeader :info="pageInfo" :with-back="false"/> | ||||
| 	</template> | ||||
| 	<template #buttons> | ||||
| 		<button class="_button" @click="expand" v-tooltip="$t('showInPage')"><Fa :icon="faExpandAlt"/></button> | ||||
| 		<button class="_button" @click="popout" v-tooltip="$t('popout')"><Fa :icon="faExternalLinkAlt"/></button> | ||||
| 		<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button> | ||||
| 		<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> | ||||
| 	</template> | ||||
| 	<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);"> | ||||
| 		<component :is="component" v-bind="props" :ref="changePage"/> | ||||
|  | @ -14,11 +21,13 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { faExternalLinkAlt, faExpandAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import XHeader from '@/ui/_common_/header.vue'; | ||||
| import { popout } from '@/scripts/popout'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import { resolve } from '@/router'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -26,6 +35,14 @@ export default defineComponent({ | |||
| 		XHeader, | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (url) => { | ||||
| 				this.navigate(url); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		initialUrl: { | ||||
| 			type: String, | ||||
|  | @ -38,7 +55,7 @@ export default defineComponent({ | |||
| 		initialProps: { | ||||
| 			type: Object, | ||||
| 			required: false, | ||||
| 			default: {}, | ||||
| 			default: () => {}, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -50,18 +67,39 @@ export default defineComponent({ | |||
| 			url: this.initialUrl, | ||||
| 			component: this.initialComponent, | ||||
| 			props: this.initialProps, | ||||
| 			faExternalLinkAlt, faExpandAlt, | ||||
| 			history: [], | ||||
| 			faChevronLeft, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (url, component, props) => { | ||||
| 				this.url = url; | ||||
| 				this.component = markRaw(component); | ||||
| 				this.props = props; | ||||
| 	computed: { | ||||
| 		contextmenu() { | ||||
| 			return [{ | ||||
| 				type: 'label', | ||||
| 				text: this.url, | ||||
| 			}, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$t('showInPage'), | ||||
| 				action: this.expand | ||||
| 			}, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('popout'), | ||||
| 				action: this.popout | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('openInNewTab'), | ||||
| 				action: () => { | ||||
| 					window.open(this.url, '_blank'); | ||||
| 					this.$refs.window.close(); | ||||
| 				} | ||||
| 		}; | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$t('copyLink'), | ||||
| 				action: () => { | ||||
| 					copyToClipboard(this.url); | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
|  | @ -72,6 +110,18 @@ export default defineComponent({ | |||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		navigate(url, record = true) { | ||||
| 			if (record) this.history.push(this.url); | ||||
| 			this.url = url; | ||||
| 			const { component, props } = resolve(url); | ||||
| 			this.component = component; | ||||
| 			this.props = props; | ||||
| 		}, | ||||
| 
 | ||||
| 		back() { | ||||
| 			this.navigate(this.history.pop(), false); | ||||
| 		}, | ||||
| 
 | ||||
| 		expand() { | ||||
| 			this.$router.push(this.url); | ||||
| 			this.$refs.window.close(); | ||||
|  |  | |||
|  | @ -17,12 +17,12 @@ | |||
| 				<button class="item _button index active" @click="top()" v-if="$route.name === 'index'"> | ||||
| 					<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span> | ||||
| 				</button> | ||||
| 				<router-link class="item index" active-class="active" to="/" exact v-else> | ||||
| 				<MkA class="item index" active-class="active" to="/" exact v-else> | ||||
| 					<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 				<template v-for="item in menu"> | ||||
| 					<div v-if="item === '-'" class="divider"></div> | ||||
| 					<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'router-link' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to"> | ||||
| 					<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to"> | ||||
| 						<Fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $t(menuDef[item].title) }}</span> | ||||
| 						<i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i> | ||||
| 					</component> | ||||
|  | @ -35,9 +35,9 @@ | |||
| 					<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span> | ||||
| 					<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i> | ||||
| 				</button> | ||||
| 				<router-link class="item" active-class="active" to="/settings"> | ||||
| 				<MkA class="item" active-class="active" to="/settings"> | ||||
| 					<Fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 			</div> | ||||
| 		</nav> | ||||
| 	</transition> | ||||
|  |  | |||
|  | @ -3,9 +3,9 @@ | |||
| 	<div class="body"> | ||||
| 		<span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> | ||||
| 		<span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span> | ||||
| 		<router-link class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></router-link> | ||||
| 		<MkA class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></MkA> | ||||
| 		<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/> | ||||
| 		<router-link class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</router-link> | ||||
| 		<MkA class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</MkA> | ||||
| 	</div> | ||||
| 	<details v-if="note.files.length > 0"> | ||||
| 		<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary> | ||||
|  |  | |||
							
								
								
									
										104
									
								
								src/client/components/ui/a.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/client/components/ui/a.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| <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 { faExpandAlt, faColumns, faExternalLinkAlt, faLink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; | ||||
| import * as os from '@/os'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import { router } from '@/router'; | ||||
| import { deckmode } from '@/config'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	inject: { | ||||
| 		navHook: { | ||||
| 			default: null | ||||
| 		}, | ||||
| 		sideViewHook: { | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		to: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		activeClass: { | ||||
| 			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: faWindowMaximize, | ||||
| 				text: this.$t('openInWindow'), | ||||
| 				action: () => { | ||||
| 					os.pageWindow(this.to); | ||||
| 				} | ||||
| 			}, !this.navHook && this.sideViewHook ? { | ||||
| 				icon: faColumns, | ||||
| 				text: this.$t('openInSideView'), | ||||
| 				action: () => { | ||||
| 					this.sideViewHook(this.to); | ||||
| 				} | ||||
| 			} : undefined, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$t('showInPage'), | ||||
| 				action: () => { | ||||
| 					this.$router.push(this.to); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('openInNewTab'), | ||||
| 				action: () => { | ||||
| 					window.open(this.to, '_blank'); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$t('copyLink'), | ||||
| 				action: () => { | ||||
| 					copyToClipboard(this.to); | ||||
| 				} | ||||
| 			}], e); | ||||
| 		}, | ||||
| 
 | ||||
| 		nav() { | ||||
| 			if (this.navHook) { | ||||
| 				this.navHook(this.to); | ||||
| 			} else { | ||||
| 				if (this.$store.state.device.defaultSideView && this.sideViewHook && this.to !== '/') { | ||||
| 					this.sideViewHook(this.to); | ||||
| 					return; | ||||
| 				} | ||||
| 				if (this.$store.state.device.deckNavWindow && deckmode && this.to !== '/') { | ||||
| 					os.pageWindow(this.to); | ||||
| 					return; | ||||
| 				} | ||||
| 
 | ||||
| 				this.$router.push(this.to); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <div class="nvlagfpb"> | ||||
| <div class="nvlagfpb" @contextmenu.prevent.stop="() => {}"> | ||||
| 	<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/> | ||||
| </div> | ||||
| </template> | ||||
|  |  | |||
|  | @ -12,12 +12,12 @@ | |||
| 		<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item"> | ||||
| 			<span><MkEllipsis/></span> | ||||
| 		</span> | ||||
| 		<router-link v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item"> | ||||
| 		<MkA v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item"> | ||||
| 			<Fa v-if="item.icon" :icon="item.icon" fixed-width/> | ||||
| 			<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> | ||||
| 			<span>{{ item.text }}</span> | ||||
| 			<i v-if="item.indicate"><Fa :icon="faCircle"/></i> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item"> | ||||
| 			<Fa v-if="item.icon" :icon="item.icon" fixed-width/> | ||||
| 			<span>{{ item.text }}</span> | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ export default defineComponent({ | |||
| .novjtctn { | ||||
| 	position: relative; | ||||
| 	display: inline-block; | ||||
| 	margin: 0 32px 0 0; | ||||
| 	margin: 16px 32px 0 0; | ||||
| 	cursor: pointer; | ||||
| 	transition: all 0.3s; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,14 +2,16 @@ | |||
| <transition :name="$store.state.device.animation ? 'window' : ''" appear @after-leave="$emit('closed')"> | ||||
| 	<div class="ebkgocck" v-if="showing"> | ||||
| 		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> | ||||
| 			<div class="header"> | ||||
| 				<button class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 			<div class="header" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 				<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | ||||
| 				<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 
 | ||||
| 				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> | ||||
| 					<slot name="header"></slot> | ||||
| 				</span> | ||||
| 				<slot name="buttons"> | ||||
| 					<button class="_button" style="pointer-events: none;"></button> | ||||
| 				</slot> | ||||
| 
 | ||||
| 				<button v-if="closeRight" class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 				<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | ||||
| 			</div> | ||||
| 			<div class="body" v-if="padding"> | ||||
| 				<div class="_section"> | ||||
|  | @ -85,6 +87,15 @@ export default defineComponent({ | |||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		closeRight: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		contextmenu: { | ||||
| 			type: Array, | ||||
| 			required: false, | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['closed'], | ||||
|  | @ -129,6 +140,12 @@ export default defineComponent({ | |||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onContextmenu(e) { | ||||
| 			if (this.contextmenu) { | ||||
| 				os.contextMenu(this.contextmenu, e); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		// 最前面へ移動 | ||||
| 		top() { | ||||
| 			let z = 0; | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
| </div> | ||||
| <div v-else class="mk-url-preview" v-size="{ max: [400, 350] }"> | ||||
| 	<transition name="zoom" mode="out-in"> | ||||
| 		<component :is="self ? 'router-link' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching"> | ||||
| 		<component :is="self ? 'MkA' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching"> | ||||
| 			<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> | ||||
| 				<button class="_button" v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enablePlayer')"><Fa :icon="faPlayCircle"/></button> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <component :is="self ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| <component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseleave="onMouseleave" | ||||
| > | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 	<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> | ||||
| 	<MkAvatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 	<div class="title"> | ||||
| 		<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link> | ||||
| 		<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> | ||||
| 		<p class="username"><MkAcct :user="user"/></p> | ||||
| 	</div> | ||||
| 	<div class="description"> | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 			<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> | ||||
| 			<MkAvatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 			<div class="title"> | ||||
| 				<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link> | ||||
| 				<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> | ||||
| 				<p class="username"><MkAcct :user="user"/></p> | ||||
| 			</div> | ||||
| 			<div class="description"> | ||||
|  |  | |||
|  | @ -6,13 +6,13 @@ | |||
| 	</div> | ||||
| 
 | ||||
| 	<div class="users"> | ||||
| 		<router-link v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)"> | ||||
| 		<MkA v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)"> | ||||
| 			<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/> | ||||
| 			<div class="body"> | ||||
| 				<MkUserName :user="extract ? extract(item) : item" class="name"/> | ||||
| 				<MkAcct :user="extract ? extract(item) : item" class="acct"/> | ||||
| 			</div> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 	</div> | ||||
| 	<button class="more _button" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching"> | ||||
| 		<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
|  |  | |||
|  | @ -4,14 +4,13 @@ | |||
| 
 | ||||
| import '@/style.scss'; | ||||
| 
 | ||||
| import { createApp } from 'vue'; | ||||
| import { createApp, defineAsyncComponent } from 'vue'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; | ||||
| 
 | ||||
| import Root from './root.vue'; | ||||
| import widgets from './widgets'; | ||||
| import directives from './directives'; | ||||
| import components from '@/components'; | ||||
| import { version, apiUrl } from '@/config'; | ||||
| import { version, apiUrl, deckmode } from '@/config'; | ||||
| import { store } from './store'; | ||||
| import { router } from './router'; | ||||
| import { applyTheme } from '@/scripts/theme'; | ||||
|  | @ -152,7 +151,12 @@ store.dispatch('instance/fetch').then(() => { | |||
| 
 | ||||
| stream.init(store.state.i); | ||||
| 
 | ||||
| const app = createApp(Root); | ||||
| const app = createApp(await ( | ||||
| 	window.location.search === '?zen' ? import('@/ui/zen.vue') : | ||||
| 	!store.getters.isSignedIn         ? import('@/ui/visitor.vue') : | ||||
| 	deckmode                          ? import('@/ui/deck.vue') : | ||||
| 	import('@/ui/default.vue') | ||||
| ).then(x => x.default)); | ||||
| 
 | ||||
| if (_DEV_) { | ||||
| 	app.config.performance = true; | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { store } from '@/store'; | |||
| import { apiUrl } from '@/config'; | ||||
| import MkPostFormDialog from '@/components/post-form-dialog.vue'; | ||||
| import MkWaitingDialog from '@/components/waiting-dialog.vue'; | ||||
| import { resolve } from '@/router'; | ||||
| 
 | ||||
| const ua = navigator.userAgent.toLowerCase(); | ||||
| export const isMobile = /mobile|iphone|ipad|android/.test(ua); | ||||
|  | @ -162,7 +163,8 @@ export function popup(component: Component | typeof import('*.vue'), props: Reco | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function pageWindow(url: string, component: Component | typeof import('*.vue'), props: Record<string, any>) { | ||||
| export function pageWindow(url: string) { | ||||
| 	const { component, props } = resolve(url); | ||||
| 	popup(defineAsyncComponent(() => import('@/components/page-window.vue')), { | ||||
| 		initialUrl: url, | ||||
| 		initialComponent: markRaw(component), | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 		<div class="_content"> | ||||
| 			<ul> | ||||
| 				<li v-for="doc in docs" :key="doc.path"> | ||||
| 					<router-link :to="`/docs/${doc.path}`">{{ doc.title }}</router-link> | ||||
| 					<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA> | ||||
| 				</li> | ||||
| 			</ul> | ||||
| 		</div> | ||||
|  |  | |||
|  | @ -38,8 +38,8 @@ | |||
| 			<template #header><Fa :icon="faHashtag" fixed-width style="margin-right: 0.5em;"/>{{ $t('popularTags') }}</template> | ||||
| 
 | ||||
| 			<div class="vxjfqztj"> | ||||
| 				<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link> | ||||
| 				<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link> | ||||
| 				<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA> | ||||
| 				<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA> | ||||
| 			</div> | ||||
| 		</MkFolder> | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| 				<MkAvatar class="avatar" :user="req.follower"/> | ||||
| 				<div class="body"> | ||||
| 					<div class="name"> | ||||
| 						<router-link class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></router-link> | ||||
| 						<MkA class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></MkA> | ||||
| 						<p class="acct">@{{ acct(req.follower) }}</p> | ||||
| 					</div> | ||||
| 					<div class="description" v-if="req.follower.description" :title="req.follower.description"> | ||||
|  |  | |||
|  | @ -4,13 +4,12 @@ | |||
| 		<MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $t('startMessaging') }}</MkButton> | ||||
| 
 | ||||
| 		<div class="history" v-if="messages.length > 0"> | ||||
| 			<router-link v-for="(message, i) in messages" | ||||
| 			<MkA v-for="(message, i) in messages" | ||||
| 				class="message _panel" | ||||
| 				:class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($store.state.i.id) : message.isRead }" | ||||
| 				:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" | ||||
| 				:data-index="i" | ||||
| 				:key="message.id" | ||||
| 				@click.prevent="go(message)" | ||||
| 			> | ||||
| 				<div> | ||||
| 					<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/> | ||||
|  | @ -27,7 +26,7 @@ | |||
| 						<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</router-link> | ||||
| 			</MkA> | ||||
| 		</div> | ||||
| 		<div class="_fullinfo" v-if="!fetching && messages.length == 0"> | ||||
| 			<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> | ||||
|  | @ -90,18 +89,6 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		go(message) { | ||||
| 			const url = message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(this.isMe(message) ? message.recipient : message.user)}`; | ||||
| 			if (this.navHook) { | ||||
| 				this.navHook(url, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), { | ||||
| 					userAcct: message.groupId ? null : getAcct(this.isMe(message) ? message.recipient : message.user), | ||||
| 					groupId: message.groupId | ||||
| 				}); | ||||
| 			} else { | ||||
| 				this.$router.push(url); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		getAcct, | ||||
| 
 | ||||
| 		isMe(message) { | ||||
|  |  | |||
|  | @ -317,7 +317,7 @@ const Component = defineComponent({ | |||
| 				text: this.$t('openInWindow'), | ||||
| 				icon: faWindowMaximize, | ||||
| 				action: () => { | ||||
| 					os.pageWindow(url, Component, this.$props); | ||||
| 					os.pageWindow(url); | ||||
| 					this.$router.back(); | ||||
| 				}, | ||||
| 			}, this.inWindow ? undefined : { | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| 
 | ||||
| 			<MkPagination :pagination="ownedPagination" #default="{items}" ref="owned"> | ||||
| 				<div class="_card" v-for="group in items" :key="group.id"> | ||||
| 					<div class="_title"><router-link :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</router-link></div> | ||||
| 					<div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></div> | ||||
| 					<div class="_content"><MkAvatars :user-ids="group.userIds"/></div> | ||||
| 				</div> | ||||
| 			</MkPagination> | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 
 | ||||
| 	<MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list"> | ||||
| 		<div class="list _panel" v-for="(list, i) in items" :key="list.id"> | ||||
| 			<router-link :to="`/my/lists/${ list.id }`">{{ list.name }}</router-link> | ||||
| 			<MkA :to="`/my/lists/${ list.id }`">{{ list.name }}</MkA> | ||||
| 		</div> | ||||
| 	</MkPagination> | ||||
| </div> | ||||
|  |  | |||
|  | @ -42,6 +42,12 @@ export default defineComponent({ | |||
| 		MkRemoteCaution, | ||||
| 		MkButton, | ||||
| 	}, | ||||
| 	props: { | ||||
| 		noteId: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: computed(() => this.note ? { | ||||
|  | @ -77,7 +83,7 @@ export default defineComponent({ | |||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		$route: 'fetch' | ||||
| 		noteId: 'fetch' | ||||
| 	}, | ||||
| 	created() { | ||||
| 		this.fetch(); | ||||
|  | @ -86,7 +92,7 @@ export default defineComponent({ | |||
| 		fetch() { | ||||
| 			Progress.start(); | ||||
| 			os.api('notes/show', { | ||||
| 				noteId: this.$route.params.note | ||||
| 				noteId: this.noteId | ||||
| 			}).then(note => { | ||||
| 				Promise.all([ | ||||
| 					os.api('users/notes', { | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| 			</header> | ||||
| 
 | ||||
| 			<section> | ||||
| 				<router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</router-link> | ||||
| 				<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA> | ||||
| 
 | ||||
| 				<MkInput v-model:value="title"> | ||||
| 					<span>{{ $t('_pages.title') }}</span> | ||||
|  |  | |||
|  | @ -20,9 +20,9 @@ | |||
| 	</div> | ||||
| 	<div class="_section links"> | ||||
| 		<div class="_content"> | ||||
| 			<router-link :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</router-link> | ||||
| 			<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA> | ||||
| 			<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId"> | ||||
| 				<router-link :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</router-link> | ||||
| 				<MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA> | ||||
| 				<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button> | ||||
| 				<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button> | ||||
| 			</template> | ||||
|  |  | |||
|  | @ -2,6 +2,10 @@ | |||
| <div class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faCog"/> {{ $t('general') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<div>{{ $t('defaultNavigationBehaviour') }}</div> | ||||
| 			<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch> | ||||
| 		</div> | ||||
| 		<div class="_content"> | ||||
| 			<div>{{ $t('whenServerDisconnected') }}</div> | ||||
| 			<MkRadio v-model="serverDisconnectedBehavior" value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</MkRadio> | ||||
|  | @ -51,6 +55,10 @@ | |||
| 
 | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faColumns"/> {{ $t('deck') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<div>{{ $t('defaultNavigationBehaviour') }}</div> | ||||
| 			<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch> | ||||
| 		</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="deckAlwaysShowMainColumn"> | ||||
| 				{{ $t('_deck.alwaysShowMainColumn') }} | ||||
|  | @ -146,6 +154,16 @@ export default defineComponent({ | |||
| 			set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		defaultSideView: { | ||||
| 			get() { return this.$store.state.device.defaultSideView; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'defaultSideView', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		deckNavWindow: { | ||||
| 			get() { return this.$store.state.device.deckNavWindow; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		chatOpenBehavior: { | ||||
| 			get() { return this.$store.state.device.chatOpenBehavior; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'chatOpenBehavior', value }); } | ||||
|  |  | |||
|  | @ -1,52 +1,57 @@ | |||
| <template> | ||||
| <div class="vvcocwet" :class="{ wide: !narrow }" ref="el"> | ||||
| 	<div class="nav" v-if="!narrow || $route.name === 'settings'"> | ||||
| 	<div class="nav" v-if="!narrow || page == null"> | ||||
| 		<div class="menu"> | ||||
| 			<div class="label">{{ $t('basicSettings') }}</div> | ||||
| 			<router-link class="item" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</router-link> | ||||
| 			<MkA class="item" :class="{ active: page === 'profile' }" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'privacy' }" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'reaction' }" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'notifications' }" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'integration' }" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'security' }" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</MkA> | ||||
| 		</div> | ||||
| 		<div class="menu"> | ||||
| 			<div class="label">{{ $t('clientSettings') }}</div> | ||||
| 			<router-link class="item" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</router-link> | ||||
| 			<MkA class="item" :class="{ active: page === 'general' }" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'theme' }" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'sidebar' }" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'sounds' }" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'plugins' }" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</MkA> | ||||
| 		</div> | ||||
| 		<div class="menu"> | ||||
| 			<div class="label">{{ $t('otherSettings') }}</div> | ||||
| 			<router-link class="item" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</router-link> | ||||
| 			<router-link class="item" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</router-link> | ||||
| 			<router-link class="item" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</router-link> | ||||
| 			<MkA class="item" :class="{ active: page === 'mute-block' }" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'word-mute' }" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'api' }" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</MkA> | ||||
| 			<MkA class="item" :class="{ active: page === 'other' }" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</MkA> | ||||
| 		</div> | ||||
| 		<div class="menu"> | ||||
| 			<button class="_button item" @click="logout">{{ $t('logout') }}</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="main"> | ||||
| 		<router-view v-slot="{ Component }"> | ||||
| 		<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in"> | ||||
| 				<component :is="Component" @info="onInfo"/> | ||||
| 			<component :is="component" @info="onInfo"/> | ||||
| 		</transition> | ||||
| 		</router-view> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, onMounted, ref } from 'vue'; | ||||
| import { computed, defineAsyncComponent, defineComponent, onMounted, ref } from 'vue'; | ||||
| import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faLaugh, faBell } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { store } from '@/store'; | ||||
| import { i18n } from '@/i18n'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		page: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	setup(props, context) { | ||||
| 		const INFO = ref({ | ||||
| 			header: [{ | ||||
|  | @ -60,6 +65,27 @@ export default defineComponent({ | |||
| 		const onInfo = (viewInfo) => { | ||||
| 			INFO.value = viewInfo; | ||||
| 		}; | ||||
| 		const component = computed(() => { | ||||
| 			switch (props.page) { | ||||
| 				case 'profile': return defineAsyncComponent(() => import('./profile.vue')); | ||||
| 				case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); | ||||
| 				case 'reaction': return defineAsyncComponent(() => import('./reaction.vue')); | ||||
| 				case 'notifications': return defineAsyncComponent(() => import('./notifications.vue')); | ||||
| 				case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue')); | ||||
| 				case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue')); | ||||
| 				case 'integration': return defineAsyncComponent(() => import('./integration.vue')); | ||||
| 				case 'security': return defineAsyncComponent(() => import('./security.vue')); | ||||
| 				case 'api': return defineAsyncComponent(() => import('./api.vue')); | ||||
| 				case 'other': return defineAsyncComponent(() => import('./other.vue')); | ||||
| 				case 'general': return defineAsyncComponent(() => import('./general.vue')); | ||||
| 				case 'theme': return defineAsyncComponent(() => import('./theme.vue')); | ||||
| 				case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue')); | ||||
| 				case 'sounds': return defineAsyncComponent(() => import('./sounds.vue')); | ||||
| 				case 'plugins': return defineAsyncComponent(() => import('./plugins.vue')); | ||||
| 				case 'import-export': return defineAsyncComponent(() => import('./import-export.vue')); | ||||
| 				default: return null; | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		onMounted(() => { | ||||
| 			narrow.value = el.value.offsetWidth < 650; | ||||
|  | @ -71,6 +97,7 @@ export default defineComponent({ | |||
| 			view, | ||||
| 			el, | ||||
| 			onInfo, | ||||
| 			component, | ||||
| 			logout: () => { | ||||
| 				store.dispatch('logout'); | ||||
| 				location.href = '/'; | ||||
|  | @ -121,7 +148,7 @@ export default defineComponent({ | |||
| 					//border-top: solid 1px var(--divider); | ||||
| 				} | ||||
| 
 | ||||
| 				&.router-link-active { | ||||
| 				&.active { | ||||
| 					color: var(--accent); | ||||
| 					padding-left: 42px; | ||||
| 				} | ||||
|  |  | |||
|  | @ -6,9 +6,9 @@ | |||
| 			<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template> | ||||
| 			<template #default="{items}"> | ||||
| 				<div class="user" v-for="mute in items" :key="mute.id"> | ||||
| 					<router-link class="name" :to="userPage(mute.mutee)"> | ||||
| 					<MkA class="name" :to="userPage(mute.mutee)"> | ||||
| 						<MkAcct :user="mute.mutee"/> | ||||
| 					</router-link> | ||||
| 					</MkA> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 		</MkPagination> | ||||
|  | @ -18,9 +18,9 @@ | |||
| 			<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template> | ||||
| 			<template #default="{items}"> | ||||
| 				<div class="user" v-for="block in items" :key="block.id"> | ||||
| 					<router-link class="name" :to="userPage(block.blockee)"> | ||||
| 					<MkA class="name" :to="userPage(block.blockee)"> | ||||
| 						<MkAcct :user="block.blockee"/> | ||||
| 					</router-link> | ||||
| 					</MkA> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 		</MkPagination> | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ | |||
| 					<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> | ||||
| 				</optgroup> | ||||
| 			</MkSelect> | ||||
| 			<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a>・<router-link to="/theme-editor" class="_link">{{ $t('_theme.make') }}</router-link> | ||||
| 			<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a>・<MkA to="/theme-editor" class="_link">{{ $t('_theme.make') }}</MkA> | ||||
| 		</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</MkButton> | ||||
|  |  | |||
|  | @ -15,11 +15,18 @@ export default defineComponent({ | |||
| 		XNotes | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		tag: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$route.params.tag, | ||||
| 					title: this.tag, | ||||
| 					icon: faHashtag | ||||
| 				}], | ||||
| 			}, | ||||
|  | @ -27,7 +34,7 @@ export default defineComponent({ | |||
| 				endpoint: 'notes/search-by-tag', | ||||
| 				limit: 10, | ||||
| 				params: () => ({ | ||||
| 					tag: this.$route.params.tag, | ||||
| 					tag: this.tag, | ||||
| 				}) | ||||
| 			}, | ||||
| 			faHashtag | ||||
|  | @ -35,7 +42,7 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	watch: { | ||||
| 		$route() { | ||||
| 		tag() { | ||||
| 			(this.$refs.notes as any).reload(); | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -229,7 +229,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 
 | ||||
| 		messagingWindowOpen() { | ||||
| 			os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue'))); | ||||
| 			os.pageWindow('/my/messaging'); | ||||
| 		}, | ||||
| 
 | ||||
| 		openWaitingDialog(text?) { | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 	<div class="_content" v-else-if="tutorial === 1"> | ||||
| 		<div>{{ $t('_tutorial.step2_1') }}</div> | ||||
| 		<div>{{ $t('_tutorial.step2_2') }}</div> | ||||
| 		<router-link class="_link" to="/settings/profile">{{ $t('editProfile') }}</router-link> | ||||
| 		<MkA class="_link" to="/settings/profile">{{ $t('editProfile') }}</MkA> | ||||
| 	</div> | ||||
| 	<div class="_content" v-else-if="tutorial === 2"> | ||||
| 		<div>{{ $t('_tutorial.step3_1') }}</div> | ||||
|  | @ -25,10 +25,10 @@ | |||
| 		<div>{{ $t('_tutorial.step5_1') }}</div> | ||||
| 		<i18n-t keypath="_tutorial.step5_2" tag="div"> | ||||
| 			<template #featured> | ||||
| 				<router-link class="_link" to="/featured">{{ $t('featured') }}</router-link> | ||||
| 				<MkA class="_link" to="/featured">{{ $t('featured') }}</MkA> | ||||
| 			</template> | ||||
| 			<template #explore> | ||||
| 				<router-link class="_link" to="/explore">{{ $t('explore') }}</router-link> | ||||
| 				<MkA class="_link" to="/explore">{{ $t('explore') }}</MkA> | ||||
| 			</template> | ||||
| 		</i18n-t> | ||||
| 		<div>{{ $t('_tutorial.step5_3') }}</div> | ||||
|  | @ -43,7 +43,7 @@ | |||
| 		<div>{{ $t('_tutorial.step7_1') }}</div> | ||||
| 		<i18n-t keypath="_tutorial.step7_2" tag="div"> | ||||
| 			<template #help> | ||||
| 				<router-link class="_link" to="/docs">{{ $t('help') }}</router-link> | ||||
| 				<MkA class="_link" to="/docs">{{ $t('help') }}</MkA> | ||||
| 			</template> | ||||
| 		</i18n-t> | ||||
| 		<div>{{ $t('_tutorial.step7_3') }}</div> | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import parseAcct from '../../../misc/acct/parse'; | ||||
| import MkUserInfo from '@/components/user-info.vue'; | ||||
| import MkPagination from '@/components/ui/pagination.vue'; | ||||
| import { userPage, acct } from '../../filters/user'; | ||||
|  | @ -22,10 +21,14 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
|  | @ -34,7 +37,7 @@ export default defineComponent({ | |||
| 				endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers', | ||||
| 				limit: 20, | ||||
| 				params: { | ||||
| 					...parseAcct(this.$route.params.user), | ||||
| 					userId: this.user.id, | ||||
| 				} | ||||
| 			}, | ||||
| 		}; | ||||
|  | @ -45,7 +48,7 @@ export default defineComponent({ | |||
| 			this.$refs.list.reload(); | ||||
| 		}, | ||||
| 
 | ||||
| 		'$route'() { | ||||
| 		user() { | ||||
| 			this.$refs.list.reload(); | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -2,11 +2,11 @@ | |||
| <div class="ujigsodd"> | ||||
| 	<MkLoading v-if="fetching"/> | ||||
| 	<div class="stream" v-if="!fetching && images.length > 0"> | ||||
| 		<router-link v-for="(image, i) in images" :key="i" | ||||
| 		<MkA v-for="image in images" | ||||
| 			class="img" | ||||
| 			:style="`background-image: url(${thumbnail(image.file)})`" | ||||
| 			:to="notePage(image.note)" | ||||
| 		></router-link> | ||||
| 		></MkA> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p> | ||||
| </div> | ||||
|  |  | |||
|  | @ -67,24 +67,23 @@ | |||
| 				</dl> | ||||
| 			</div> | ||||
| 			<div class="status"> | ||||
| 				<router-link :to="userPage(user)" :class="{ active: $route.name === 'user' }"> | ||||
| 				<MkA :to="userPage(user)" :class="{ active: page === 'index' }"> | ||||
| 					<b>{{ number(user.notesCount) }}</b> | ||||
| 					<span>{{ $t('notes') }}</span> | ||||
| 				</router-link> | ||||
| 				<router-link :to="userPage(user, 'following')" :class="{ active: $route.name === 'userFollowing' }"> | ||||
| 				</MkA> | ||||
| 				<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> | ||||
| 					<b>{{ number(user.followingCount) }}</b> | ||||
| 					<span>{{ $t('following') }}</span> | ||||
| 				</router-link> | ||||
| 				<router-link :to="userPage(user, 'followers')" :class="{ active: $route.name === 'userFollowers' }"> | ||||
| 				</MkA> | ||||
| 				<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> | ||||
| 					<b>{{ number(user.followersCount) }}</b> | ||||
| 					<span>{{ $t('followers') }}</span> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<router-view :user="user"></router-view> | ||||
| 	<template v-if="$route.name == 'user'"> | ||||
| 	<template v-if="page === 'index'"> | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content _vMargin" v-if="user.pinnedNotes.length > 0"> | ||||
| 				<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/> | ||||
|  | @ -106,6 +105,8 @@ | |||
| 			<XUserTimeline :user="user" class="_content"/> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 	<XFollowList v-else-if="page === 'following'" type="following" :user="user"/> | ||||
| 	<XFollowList v-else-if="page === 'followers'" type="followers" :user="user"/> | ||||
| </div> | ||||
| <div v-else-if="error"> | ||||
| 	<MkError @retry="fetch()"/> | ||||
|  | @ -128,7 +129,7 @@ import parseAcct from '../../../misc/acct/parse'; | |||
| import { getScrollPosition } from '@/scripts/scroll'; | ||||
| import { getUserMenu } from '@/scripts/get-user-menu'; | ||||
| import number from '../../filters/number'; | ||||
| import { userPage, acct } from '../../filters/user'; | ||||
| import { userPage, acct as getAcct } from '../../filters/user'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|  | @ -139,10 +140,23 @@ export default defineComponent({ | |||
| 		MkContainer, | ||||
| 		MkRemoteCaution, | ||||
| 		MkFolder, | ||||
| 		XFollowList: defineAsyncComponent(() => import('./follow-list.vue')), | ||||
| 		XPhotos: defineAsyncComponent(() => import('./index.photos.vue')), | ||||
| 		XActivity: defineAsyncComponent(() => import('./index.activity.vue')), | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		acct: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		page: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 			default: 'index' | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: computed(() => this.user ? { | ||||
|  | @ -176,7 +190,7 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	watch: { | ||||
| 		$route: 'fetch' | ||||
| 		acct: 'fetch' | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
|  | @ -192,10 +206,12 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 
 | ||||
| 		fetch() { | ||||
| 			if (this.$route.params.user == null) return; | ||||
| 			if (this.acct == null) return; | ||||
| 			Progress.start(); | ||||
| 			os.api('users/show', parseAcct(this.$route.params.user)).then(user => { | ||||
| 			os.api('users/show', parseAcct(this.acct)).then(user => { | ||||
| 				this.user = user; | ||||
| 			}).catch(e => { | ||||
| 				this.error = e; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { defineAsyncComponent } from 'vue'; | ||||
| import { defineAsyncComponent, markRaw } from 'vue'; | ||||
| import { createRouter, createWebHistory } from 'vue-router'; | ||||
| import MkLoading from '@/pages/_loading_.vue'; | ||||
| import MkError from '@/pages/_error_.vue'; | ||||
|  | @ -18,30 +18,11 @@ export const router = createRouter({ | |||
| 	routes: [ | ||||
| 		// NOTE: MkTimelineをdynamic importするとAsyncComponentWrapperが間に入るせいでkeep-aliveのコンポーネント指定が効かなくなる
 | ||||
| 		{ path: '/', name: 'index', component: store.getters.isSignedIn ? MkTimeline : page('welcome') }, | ||||
| 		{ path: '/@:user', name: 'user', component: page('user/index'), children: [ | ||||
| 			{ path: 'following', name: 'userFollowing', component: page('user/follow-list'), props: { type: 'following' } }, | ||||
| 			{ path: 'followers', name: 'userFollowers', component: page('user/follow-list'), props: { type: 'followers' } }, | ||||
| 		]}, | ||||
| 		{ path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, | ||||
| 		{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, | ||||
| 		{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||
| 		{ path: '/@:acct/room', props: true, component: page('room/room') }, | ||||
| 		{ path: '/settings', name: 'settings', component: page('settings/index'), children: [ | ||||
| 			{ path: 'profile', component: page('settings/profile') }, | ||||
| 			{ path: 'privacy', component: page('settings/privacy') }, | ||||
| 			{ path: 'reaction', component: page('settings/reaction') }, | ||||
| 			{ path: 'notifications', component: page('settings/notifications') }, | ||||
| 			{ path: 'mute-block', component: page('settings/mute-block') }, | ||||
| 			{ path: 'word-mute', component: page('settings/word-mute') }, | ||||
| 			{ path: 'integration', component: page('settings/integration') }, | ||||
| 			{ path: 'security', component: page('settings/security') }, | ||||
| 			{ path: 'api', component: page('settings/api') }, | ||||
| 			{ path: 'other', component: page('settings/other') }, | ||||
| 			{ path: 'general', component: page('settings/general') }, | ||||
| 			{ path: 'theme', component: page('settings/theme') }, | ||||
| 			{ path: 'sidebar', component: page('settings/sidebar') }, | ||||
| 			{ path: 'sounds', component: page('settings/sounds') }, | ||||
| 			{ path: 'plugins', component: page('settings/plugins') }, | ||||
| 		]}, | ||||
| 		{ path: '/settings/:page?', name: 'settings', component: page('settings/index'), props: route => ({ page: route.params.page || null }) }, | ||||
| 		{ path: '/announcements', component: page('announcements') }, | ||||
| 		{ path: '/about', component: page('about') }, | ||||
| 		{ path: '/about-misskey', component: page('about-misskey') }, | ||||
|  | @ -87,8 +68,8 @@ export const router = createRouter({ | |||
| 		{ path: '/instance/relays', component: page('instance/relays') }, | ||||
| 		{ path: '/instance/announcements', component: page('instance/announcements') }, | ||||
| 		{ path: '/instance/abuses', component: page('instance/abuses') }, | ||||
| 		{ path: '/notes/:note', name: 'note', component: page('note') }, | ||||
| 		{ path: '/tags/:tag', component: page('tag') }, | ||||
| 		{ path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) }, | ||||
| 		{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, | ||||
| 		{ path: '/auth/:token', component: page('auth') }, | ||||
| 		{ path: '/miauth/:session', component: page('miauth') }, | ||||
| 		{ path: '/authorize-follow', component: page('follow') }, | ||||
|  | @ -120,3 +101,13 @@ router.afterEach((to, from) => { | |||
| 		indexScrollPos = window.scrollY; | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| export function resolve(path: string) { | ||||
| 	const resolved = router.resolve(path); | ||||
| 	const route = resolved.matched[0]; | ||||
| 	return { | ||||
| 		component: markRaw(route.components.default), | ||||
| 		// TODO: route.propsには関数以外も入る可能性があるのでよしなにハンドリングする
 | ||||
| 		props: route.props?.default ? route.props.default(resolved) : resolved.params | ||||
| 	}; | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import getAcct from '../../misc/acct/render'; | |||
| import * as os from '@/os'; | ||||
| import { store, userActions } from '@/store'; | ||||
| import { router } from '@/router'; | ||||
| import { defineAsyncComponent } from 'vue'; | ||||
| import { popout } from './popout'; | ||||
| 
 | ||||
| export function getUserMenu(user) { | ||||
|  | @ -137,7 +136,7 @@ export function getUserMenu(user) { | |||
| 		action: () => { | ||||
| 			const acct = getAcct(user); | ||||
| 			switch (store.state.device.chatOpenBehavior) { | ||||
| 				case 'window': { os.pageWindow('/my/messaging/' + acct, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), { userAcct: acct }); break; } | ||||
| 				case 'window': { os.pageWindow('/my/messaging/' + acct); break; } | ||||
| 				case 'popout': { popout('/my/messaging'); break; } | ||||
| 				default: { router.push('/my/messaging'); break; } | ||||
| 			} | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { faBell, faComments, faEnvelope } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { faAt, faBroadcastTower, faCloud, faColumns, faDoorClosed, faFileAlt, faFireAlt, faGamepad, faHashtag, faListUl, faSatellite, faSatelliteDish, faSearch, faStar, faTerminal, faUserClock, faUsers } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { computed, defineAsyncComponent } from 'vue'; | ||||
| import { computed } from 'vue'; | ||||
| import { store } from '@/store'; | ||||
| import { deckmode } from '@/config'; | ||||
| import { search } from '@/scripts/search'; | ||||
|  | @ -23,7 +23,7 @@ export const sidebarDef = { | |||
| 		indicated: computed(() => store.getters.isSignedIn && store.state.i.hasUnreadMessagingMessage), | ||||
| 		action: () => { | ||||
| 			switch (store.state.device.chatOpenBehavior) { | ||||
| 				case 'window': { os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue'))); break; } | ||||
| 				case 'window': { os.pageWindow('/my/messaging'); break; } | ||||
| 				case 'popout': { popout('/my/messaging'); break; } | ||||
| 				default: { router.push('/my/messaging'); break; } | ||||
| 			} | ||||
|  |  | |||
|  | @ -70,6 +70,8 @@ export const defaultDeviceSettings = { | |||
| 	animatedMfm: true, | ||||
| 	imageNewTab: false, | ||||
| 	chatOpenBehavior: 'page', | ||||
| 	defaultSideView: false, | ||||
| 	deckNavWindow: true, | ||||
| 	showFixedPostForm: false, | ||||
| 	disablePagesScript: false, | ||||
| 	enableInfiniteScroll: true, | ||||
|  |  | |||
|  | @ -1,9 +1,4 @@ | |||
| <template> | ||||
| <ZenUI v-if="zen"/> | ||||
| <VisitorUI v-else-if="!$store.getters.isSignedIn"/> | ||||
| <DeckUI v-else-if="deckmode"/> | ||||
| <DefaultUI v-else/> | ||||
| 
 | ||||
| <component v-for="popup in popups" | ||||
| 	:key="popup.id" | ||||
| 	:is="popup.component" | ||||
|  | @ -13,27 +8,23 @@ | |||
| 
 | ||||
| <XUpload v-if="uploads.length > 0"/> | ||||
| 
 | ||||
| <XStreamIndicator/> | ||||
| 
 | ||||
| <div id="wait" v-if="pendingApiRequestsCount > 0"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineAsyncComponent, defineComponent } from 'vue'; | ||||
| import { deckmode } from '@/config'; | ||||
| import { popups, uploads, pendingApiRequestsCount } from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		DefaultUI: defineAsyncComponent(() => import('@/ui/default.vue')), | ||||
| 		DeckUI: defineAsyncComponent(() => import('@/ui/deck.vue')), | ||||
| 		ZenUI: defineAsyncComponent(() => import('@/ui/zen.vue')), | ||||
| 		VisitorUI: defineAsyncComponent(() => import('@/ui/visitor.vue')), | ||||
| 		XUpload: defineAsyncComponent(() => import('@/components/upload.vue')), | ||||
| 		XStreamIndicator: defineAsyncComponent(() => import('./stream-indicator.vue')), | ||||
| 		XUpload: defineAsyncComponent(() => import('./upload.vue')), | ||||
| 	}, | ||||
| 
 | ||||
| 	setup() { | ||||
| 		return { | ||||
| 			zen: window.location.search === '?zen', | ||||
| 			deckmode, | ||||
| 			uploads, | ||||
| 			popups, | ||||
| 			pendingApiRequestsCount, | ||||
|  | @ -29,7 +29,7 @@ | |||
| 	<button v-if="$store.getters.isSignedIn" class="nav _button" @click="showNav()"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button> | ||||
| 	<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><Fa :icon="faPencilAlt"/></button> | ||||
| 
 | ||||
| 	<StreamIndicator v-if="$store.getters.isSignedIn"/> | ||||
| 	<XCommon/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -47,9 +47,11 @@ import XHeader from './_common_/header.vue'; | |||
| import { getScrollContainer } from '@/scripts/scroll'; | ||||
| import * as os from '@/os'; | ||||
| import { sidebarDef } from '@/sidebar'; | ||||
| import XCommon from './_common_/common.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XCommon, | ||||
| 		XSidebar, | ||||
| 		XHeader, | ||||
| 		DeckColumn, | ||||
|  |  | |||
							
								
								
									
										157
									
								
								src/client/ui/default.side.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/client/ui/default.side.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,157 @@ | |||
| <template> | ||||
| <div class="qvzfzxam _narrow_" v-if="component"> | ||||
| 	<div class="container"> | ||||
| 		<header class="header" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 			<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button> | ||||
| 			<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> | ||||
| 			<XHeader class="title" :info="pageInfo" :with-back="false"/> | ||||
| 			<button class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 		</header> | ||||
| 		<component :is="component" v-bind="props" :ref="changePage"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faTimes, faChevronLeft, faExpandAlt, faWindowMaximize, faExternalLinkAlt, faLink } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XHeader from './_common_/header.vue'; | ||||
| import * as os from '@/os'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import { resolve } from '@/router'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XHeader | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (url) => { | ||||
| 				this.navigate(url); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			url: null, | ||||
| 			component: null, | ||||
| 			props: {}, | ||||
| 			pageInfo: null, | ||||
| 			history: [], | ||||
| 			faTimes, faChevronLeft, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		changePage(page) { | ||||
| 			if (page == null) return; | ||||
| 			if (page.INFO) { | ||||
| 				this.pageInfo = page.INFO; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		navigate(url, record = true) { | ||||
| 			if (record && this.url) this.history.push(this.url); | ||||
| 			this.url = url; | ||||
| 			const { component, props } = resolve(url); | ||||
| 			this.component = component; | ||||
| 			this.props = props; | ||||
| 		}, | ||||
| 
 | ||||
| 		back() { | ||||
| 			this.navigate(this.history.pop(), false); | ||||
| 		}, | ||||
| 
 | ||||
| 		close() { | ||||
| 			this.url = null; | ||||
| 			this.component = null; | ||||
| 			this.props = {}; | ||||
| 		}, | ||||
| 
 | ||||
| 		onContextmenu(e) { | ||||
| 			os.contextMenu([{ | ||||
| 				type: 'label', | ||||
| 				text: this.url, | ||||
| 			}, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$t('showInPage'), | ||||
| 				action: () => { | ||||
| 					this.$router.push(this.url); | ||||
| 					this.close(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faWindowMaximize, | ||||
| 				text: this.$t('openInWindow'), | ||||
| 				action: () => { | ||||
| 					os.pageWindow(this.url); | ||||
| 					this.close(); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('openInNewTab'), | ||||
| 				action: () => { | ||||
| 					window.open(this.url, '_blank'); | ||||
| 					this.close(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$t('copyLink'), | ||||
| 				action: () => { | ||||
| 					copyToClipboard(this.url); | ||||
| 				} | ||||
| 			}], e); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .qvzfzxam { | ||||
| 	$header-height: 58px; // TODO: どこかに集約したい | ||||
| 
 | ||||
| 	--section-padding: 16px; | ||||
| 	--margin: var(--marginHalf); | ||||
| 
 | ||||
| 	> .container { | ||||
| 		position: fixed; | ||||
| 		width: 370px; | ||||
| 		height: 100vh; | ||||
| 		overflow: auto; | ||||
| 		box-sizing: border-box; | ||||
| 
 | ||||
| 		> .header { | ||||
| 			display: flex; | ||||
| 			position: sticky; | ||||
| 			z-index: 1000; | ||||
| 			top: 0; | ||||
| 			height: $header-height; | ||||
| 			width: 100%; | ||||
| 			line-height: $header-height; | ||||
| 			text-align: center; | ||||
| 			font-weight: bold; | ||||
| 			//background-color: var(--panel); | ||||
| 			-webkit-backdrop-filter: blur(32px); | ||||
| 			backdrop-filter: blur(32px); | ||||
| 			background-color: var(--header); | ||||
| 			border-bottom: solid 1px var(--divider); | ||||
| 
 | ||||
| 			> ._button { | ||||
| 				height: $header-height; | ||||
| 				width: $header-height; | ||||
| 
 | ||||
| 				&:hover { | ||||
| 					color: var(--fgHighlighted); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			> .title { | ||||
| 				flex: 1; | ||||
| 				position: relative; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| 
 | ||||
|  | @ -20,6 +20,8 @@ | |||
| 		</main> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<XSide v-if="isDesktop" class="side" ref="side"/> | ||||
| 
 | ||||
| 	<div v-if="isDesktop" class="widgets"> | ||||
| 		<div ref="widgetsSpacer"></div> | ||||
| 		<XWidgets @mounted="attachSticky"/> | ||||
|  | @ -47,19 +49,21 @@ | |||
| 		<XWidgets v-if="widgetsShowing" class="tray"/> | ||||
| 	</transition> | ||||
| 
 | ||||
| 	<StreamIndicator/> | ||||
| 	<XCommon/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { defineComponent, defineAsyncComponent, markRaw } from 'vue'; | ||||
| import { faLayerGroup, faBars, faHome, faCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faBell } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { host } from '@/config'; | ||||
| import { search } from '@/scripts/search'; | ||||
| import { StickySidebar } from '@/scripts/sticky-sidebar'; | ||||
| import XSidebar from '@/components/sidebar.vue'; | ||||
| import XCommon from './_common_/common.vue'; | ||||
| import XHeader from './_common_/header.vue'; | ||||
| import XSide from './default.side.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { sidebarDef } from '@/sidebar'; | ||||
| 
 | ||||
|  | @ -67,9 +71,19 @@ const DESKTOP_THRESHOLD = 1100; | |||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XCommon, | ||||
| 		XSidebar, | ||||
| 		XHeader, | ||||
| 		XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')), | ||||
| 		XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			sideViewHook: (url) => { | ||||
| 				this.$refs.side.navigate(url); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
|  | @ -245,7 +259,7 @@ export default defineComponent({ | |||
| } | ||||
| 
 | ||||
| .mk-app { | ||||
| 	$header-height: 60px; | ||||
| 	$header-height: 58px; // TODO: どこかに集約したい | ||||
| 	$ui-font-size: 1em; // TODO: どこかに集約したい | ||||
| 	$widgets-hide-threshold: 1090px; | ||||
| 
 | ||||
|  | @ -301,6 +315,12 @@ export default defineComponent({ | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .side { | ||||
| 		min-width: 370px; | ||||
| 		max-width: 370px; | ||||
| 		border-left: solid 1px var(--divider); | ||||
| 	} | ||||
| 
 | ||||
| 	> .widgets { | ||||
| 		padding: 0 var(--margin); | ||||
| 		border-left: solid 1px var(--divider); | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| <template> | ||||
| <div class="mk-app"> | ||||
| 	<header> | ||||
| 		<router-link class="link" to="/">{{ $t('home') }}</router-link> | ||||
| 		<router-link class="link" to="/announcements">{{ $t('announcements') }}</router-link> | ||||
| 		<router-link class="link" to="/channels">{{ $t('channel') }}</router-link> | ||||
| 		<router-link class="link" to="/about">{{ $t('aboutX', { x: instanceName || host }) }}</router-link> | ||||
| 		<MkA class="link" to="/">{{ $t('home') }}</MkA> | ||||
| 		<MkA class="link" to="/announcements">{{ $t('announcements') }}</MkA> | ||||
| 		<MkA class="link" to="/channels">{{ $t('channel') }}</MkA> | ||||
| 		<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName || host }) }}</MkA> | ||||
| 	</header> | ||||
| 
 | ||||
| 	<div class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> | ||||
|  | @ -23,12 +23,12 @@ | |||
| 			</router-view> | ||||
| 		</main> | ||||
| 		<div class="powered-by"> | ||||
| 			<b><router-link to="/">{{ host }}</router-link></b> | ||||
| 			<b><MkA to="/">{{ host }}</MkA></b> | ||||
| 			<small>Powered by <a href="https://github.com/syuilo/misskey" target="_blank">Misskey</a></small> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<StreamIndicator v-if="$store.getters.isSignedIn"/> | ||||
| 	<XCommon/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -39,12 +39,14 @@ import { host, instanceName } from '@/config'; | |||
| import { search } from '@/scripts/search'; | ||||
| import * as os from '@/os'; | ||||
| import XHeader from './_common_/header.vue'; | ||||
| import XCommon from './_common_/common.vue'; | ||||
| 
 | ||||
| const DESKTOP_THRESHOLD = 1100; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XHeader | ||||
| 		XCommon, | ||||
| 		XHeader, | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
|  | @ -130,7 +132,7 @@ export default defineComponent({ | |||
| 			line-height: 60px; | ||||
| 			padding: 0 0.7em; | ||||
| 
 | ||||
| 			&.router-link-active { | ||||
| 			&.MkA-active { | ||||
| 				box-shadow: 0 -2px 0 0 var(--accent) inset; | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ | |||
| 		</main> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<StreamIndicator/> | ||||
| 	<XCommon/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -28,10 +28,12 @@ import { faBell } from '@fortawesome/free-regular-svg-icons'; | |||
| import { host } from '@/config'; | ||||
| import { search } from '@/scripts/search'; | ||||
| import XHeader from './_common_/header.vue'; | ||||
| import XCommon from './_common_/common.vue'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XCommon, | ||||
| 		XHeader, | ||||
| 	}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
| 		<transition-group tag="div" name="chart" class="tags" v-else> | ||||
| 			<div v-for="stat in stats" :key="stat.tag"> | ||||
| 				<div class="tag"> | ||||
| 					<router-link class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link> | ||||
| 					<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA> | ||||
| 					<p>{{ $t('nUsersMentioned', { n: stat.usersCount }) }}</p> | ||||
| 				</div> | ||||
| 				<MkMiniChart class="chart" :src="stat.chart"/> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue