diff options
Diffstat (limited to 'src/client/components')
25 files changed, 245 insertions, 285 deletions
diff --git a/src/client/components/avatar.vue b/src/client/components/avatar.vue index 627818a8e7..d90607bb8a 100644 --- a/src/client/components/avatar.vue +++ b/src/client/components/avatar.vue @@ -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"> diff --git a/src/client/components/channel-preview.vue b/src/client/components/channel-preview.vue index 705d3b09c4..e5676e5ae9 100644 --- a/src/client/components/channel-preview.vue +++ b/src/client/components/channel-preview.vue @@ -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"> diff --git a/src/client/components/index.ts b/src/client/components/index.ts index 6cc06e37c3..92a29ded15 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.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); } diff --git a/src/client/components/link.vue b/src/client/components/link.vue index e0a7f43477..bac49a62ef 100644 --- a/src/client/components/link.vue +++ b/src/client/components/link.vue @@ -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" diff --git a/src/client/components/mention.vue b/src/client/components/mention.vue index 50b43df07b..85f8436a42 100644 --- a/src/client/components/mention.vue +++ b/src/client/components/mention.vue @@ -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> diff --git a/src/client/components/mfm.ts b/src/client/components/mfm.ts index 791fd1b4e5..7a8ee8b19f 100644 --- a/src/client/components/mfm.ts +++ b/src/client/components/mfm.ts @@ -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);' diff --git a/src/client/components/note-header.vue b/src/client/components/note-header.vue index 3be0ba38fe..1f7a07bac3 100644 --- a/src/client/components/note-header.vue +++ b/src/client/components/note-header.vue @@ -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"/> diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 85bdb9c6fb..8ddb01f733 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -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 diff --git a/src/client/components/notification.vue b/src/client/components/notification.vue index ab890bbf0f..db6d8ad167 100644 --- a/src/client/components/notification.vue +++ b/src/client/components/notification.vue @@ -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> diff --git a/src/client/components/page-preview.vue b/src/client/components/page-preview.vue index ad1069f53f..95ed8d0e38 100644 --- a/src/client/components/page-preview.vue +++ b/src/client/components/page-preview.vue @@ -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"> diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index 2673b3f8ec..c3ec7db867 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -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(); diff --git a/src/client/components/sidebar.vue b/src/client/components/sidebar.vue index 383378241b..3ceb1f9b8d 100644 --- a/src/client/components/sidebar.vue +++ b/src/client/components/sidebar.vue @@ -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> diff --git a/src/client/components/stream-indicator.vue b/src/client/components/stream-indicator.vue deleted file mode 100644 index 7b020171a4..0000000000 --- a/src/client/components/stream-indicator.vue +++ /dev/null @@ -1,70 +0,0 @@ -<template> -<div class="nsbbhtug" v-if="hasDisconnected && $store.state.device.serverDisconnectedBehavior === 'quiet'" @click="resetDisconnected"> - <div>{{ $t('disconnectedFromServer') }}</div> - <div class="command"> - <button class="_textButton" @click="reload">{{ $t('reload') }}</button> - <button class="_textButton">{{ $t('doNothing') }}</button> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - hasDisconnected: false, - } - }, - computed: { - stream() { - return os.stream; - }, - }, - created() { - os.stream.on('_disconnected_', this.onDisconnected); - }, - beforeUnmount() { - os.stream.off('_disconnected_', this.onDisconnected); - }, - methods: { - onDisconnected() { - this.hasDisconnected = true; - }, - resetDisconnected() { - this.hasDisconnected = false; - }, - reload() { - location.reload(); - }, - } -}); -</script> - -<style lang="scss" scoped> -.nsbbhtug { - position: fixed; - z-index: 16385; - bottom: 8px; - right: 8px; - margin: 0; - padding: 6px 12px; - font-size: 0.9em; - color: #fff; - background: #000; - opacity: 0.8; - border-radius: 4px; - max-width: 320px; - - > .command { - display: flex; - justify-content: space-around; - - > button { - padding: 0.7em; - } - } -} -</style> diff --git a/src/client/components/sub-note-content.vue b/src/client/components/sub-note-content.vue index 0bef072fe4..cb65a76495 100644 --- a/src/client/components/sub-note-content.vue +++ b/src/client/components/sub-note-content.vue @@ -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> diff --git a/src/client/components/ui/a.vue b/src/client/components/ui/a.vue new file mode 100644 index 0000000000..dce99ef676 --- /dev/null +++ b/src/client/components/ui/a.vue @@ -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> diff --git a/src/client/components/ui/context-menu.vue b/src/client/components/ui/context-menu.vue index 98586cf3fe..3a11589e8a 100644 --- a/src/client/components/ui/context-menu.vue +++ b/src/client/components/ui/context-menu.vue @@ -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> diff --git a/src/client/components/ui/menu.vue b/src/client/components/ui/menu.vue index 5e74828c20..9e4e319c8a 100644 --- a/src/client/components/ui/menu.vue +++ b/src/client/components/ui/menu.vue @@ -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> diff --git a/src/client/components/ui/radio.vue b/src/client/components/ui/radio.vue index 8f2b843ee6..890ff08751 100644 --- a/src/client/components/ui/radio.vue +++ b/src/client/components/ui/radio.vue @@ -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; diff --git a/src/client/components/ui/window.vue b/src/client/components/ui/window.vue index d545ac4827..4c90ab9c8d 100644 --- a/src/client/components/ui/window.vue +++ b/src/client/components/ui/window.vue @@ -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; diff --git a/src/client/components/upload.vue b/src/client/components/upload.vue deleted file mode 100644 index 2ba2186f57..0000000000 --- a/src/client/components/upload.vue +++ /dev/null @@ -1,136 +0,0 @@ -<template> -<div class="mk-uploader _acrylic"> - <ol v-if="uploads.length > 0"> - <li v-for="ctx in uploads" :key="ctx.id"> - <div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> - <div class="top"> - <p class="name"><Fa :icon="faSpinner" pulse/>{{ ctx.name }}</p> - <p class="status"> - <span class="initing" v-if="ctx.progressValue === undefined">{{ $t('waiting') }}<MkEllipsis/></span> - <span class="kb" v-if="ctx.progressValue !== undefined">{{ String(Math.floor(ctx.progressValue / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progressMax / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> - <span class="percentage" v-if="ctx.progressValue !== undefined">{{ Math.floor((ctx.progressValue / ctx.progressMax) * 100) }}</span> - </p> - </div> - <progress :value="ctx.progressValue || 0" :max="ctx.progressMax || 0" :class="{ initing: ctx.progressValue === undefined, waiting: ctx.progressValue !== undefined && ctx.progressValue === ctx.progressMax }"></progress> - </li> - </ol> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - uploads: os.uploads, - faSpinner - }; - }, -}); -</script> - -<style lang="scss" scoped> -.mk-uploader { - position: fixed; - z-index: 10000; - right: 16px; - width: 260px; - top: 32px; - padding: 16px 20px; - pointer-events: none; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); - border-radius: 8px; -} -.mk-uploader:empty { - display: none; -} -.mk-uploader > ol { - display: block; - margin: 0; - padding: 0; - list-style: none; -} -.mk-uploader > ol > li { - display: grid; - margin: 8px 0 0 0; - padding: 0; - height: 36px; - width: 100%; - border-top: solid 8px transparent; - grid-template-columns: 36px calc(100% - 44px); - grid-template-rows: 1fr 8px; - column-gap: 8px; - box-sizing: content-box; -} -.mk-uploader > ol > li:first-child { - margin: 0; - box-shadow: none; - border-top: none; -} -.mk-uploader > ol > li > .img { - display: block; - background-size: cover; - background-position: center center; - grid-column: 1/2; - grid-row: 1/3; -} -.mk-uploader > ol > li > .top { - display: flex; - grid-column: 2/3; - grid-row: 1/2; -} -.mk-uploader > ol > li > .top > .name { - display: block; - padding: 0 8px 0 0; - margin: 0; - font-size: 0.8em; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - flex-shrink: 1; -} -.mk-uploader > ol > li > .top > .name > [data-icon] { - margin-right: 4px; -} -.mk-uploader > ol > li > .top > .status { - display: block; - margin: 0 0 0 auto; - padding: 0; - font-size: 0.8em; - flex-shrink: 0; -} -.mk-uploader > ol > li > .top > .status > .initing { -} -.mk-uploader > ol > li > .top > .status > .kb { -} -.mk-uploader > ol > li > .top > .status > .percentage { - display: inline-block; - width: 48px; - text-align: right; -} -.mk-uploader > ol > li > .top > .status > .percentage:after { - content: '%'; -} -.mk-uploader > ol > li > progress { - display: block; - background: transparent; - border: none; - border-radius: 4px; - overflow: hidden; - grid-column: 2/3; - grid-row: 2/3; - z-index: 2; - width: 100%; - height: 8px; -} -.mk-uploader > ol > li > progress::-webkit-progress-value { - background: var(--accent); -} -.mk-uploader > ol > li > progress::-webkit-progress-bar { - //background: var(--accentAlpha01); - background: transparent; -} -</style> diff --git a/src/client/components/url-preview.vue b/src/client/components/url-preview.vue index df02698b5d..55872113be 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -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> diff --git a/src/client/components/url.vue b/src/client/components/url.vue index 649ce5fa24..ceb0381f87 100644 --- a/src/client/components/url.vue +++ b/src/client/components/url.vue @@ -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" > diff --git a/src/client/components/user-info.vue b/src/client/components/user-info.vue index 893747b7c4..09736b1a2c 100644 --- a/src/client/components/user-info.vue +++ b/src/client/components/user-info.vue @@ -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"> diff --git a/src/client/components/user-preview.vue b/src/client/components/user-preview.vue index d1a11dc790..d258489860 100644 --- a/src/client/components/user-preview.vue +++ b/src/client/components/user-preview.vue @@ -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"> diff --git a/src/client/components/users-dialog.vue b/src/client/components/users-dialog.vue index c8ca93703d..f2e8ec480e 100644 --- a/src/client/components/users-dialog.vue +++ b/src/client/components/users-dialog.vue @@ -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> |