diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 43774adaed..e3d94cfea4 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -650,6 +650,7 @@ pageLikedCount: "Pageにいいねされた数" reversiCount: "リバーシの対局数" contact: "連絡先" useSystemFont: "システムのデフォルトのフォントを使う" +clips: "クリップ" _aboutMisskey: about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。" diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index fd2e2f10cb..0c816a2d22 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -508,12 +508,17 @@ export default defineComponent({ } .ftskorzw.narrow { + max-width: 100vw; + box-sizing: border-box; + overflow: hidden; + > .punished { font-size: 0.8em; padding: 16px; } > .profile { + > .main { position: relative; overflow: hidden; @@ -678,6 +683,7 @@ export default defineComponent({ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + margin: 0; } } diff --git a/src/client/pages/welcome.entrance.block.vue b/src/client/pages/welcome.entrance.block.vue index 0e4aefa4b0..b4f5881773 100644 --- a/src/client/pages/welcome.entrance.block.vue +++ b/src/client/pages/welcome.entrance.block.vue @@ -136,6 +136,7 @@ export default defineComponent({ > div { flex: 1; overflow: auto; + background: var(--bg); } } </style> diff --git a/src/client/scripts/get-user-menu.ts b/src/client/scripts/get-user-menu.ts index c3de7313cc..a97fed9919 100644 --- a/src/client/scripts/get-user-menu.ts +++ b/src/client/scripts/get-user-menu.ts @@ -7,9 +7,10 @@ import getAcct from '../../misc/acct/render'; import * as os from '@/os'; import { store, userActions } from '@/store'; import { router } from '@/router'; -import { popout } from './popout'; export function getUserMenu(user) { + const meId = store.getters.isSignedIn ? store.state.i.id : null; + async function pushList() { const t = i18n.global.t('selectList'); // なぜか後で参照すると null になるので最初にメモリに確保しておく const lists = await os.api('users/lists/list'); @@ -130,7 +131,7 @@ export function getUserMenu(user) { action: () => { os.post({ specified: user }); } - }, store.state.i.id != user.id ? { + }, meId != user.id ? { type: 'link', icon: faComments, text: i18n.global.t('startMessaging'), @@ -139,13 +140,13 @@ export function getUserMenu(user) { icon: faListUl, text: i18n.global.t('addToList'), action: pushList - }, store.state.i.id != user.id ? { + }, meId != user.id ? { icon: faUsers, text: i18n.global.t('inviteToGroup'), action: inviteGroup } : undefined] as any; - if (store.getters.isSignedIn && store.state.i.id != user.id) { + if (store.getters.isSignedIn && meId != user.id) { menu = menu.concat([null, { icon: user.isMuted ? faEye : faEyeSlash, text: user.isMuted ? i18n.global.t('unmute') : i18n.global.t('mute'), @@ -175,7 +176,7 @@ export function getUserMenu(user) { } } - if (store.getters.isSignedIn && store.state.i.id === user.id) { + if (store.getters.isSignedIn && meId === user.id) { menu = menu.concat([null, { icon: faPencilAlt, text: i18n.global.t('editProfile'), diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5 index f290586eb4..9ce8853614 100644 --- a/src/client/themes/_dark.json5 +++ b/src/client/themes/_dark.json5 @@ -76,5 +76,6 @@ X13: 'rgba(255, 255, 255, 0.15)', X14: ':alpha<0.5<@navBg', X15: ':alpha<0<@panel', + X16: ':alpha<0.7<@panel', }, } diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5 index 0a1125cab7..d08063fdaf 100644 --- a/src/client/themes/_light.json5 +++ b/src/client/themes/_light.json5 @@ -76,5 +76,6 @@ X13: 'rgba(0, 0, 0, 0.15)', X14: ':alpha<0.5<@navBg', X15: ':alpha<0<@panel', + X16: ':alpha<0.7<@panel', }, } diff --git a/src/client/ui/visitor/b.vue b/src/client/ui/visitor/b.vue index 39ee4e3ed9..6c7366c6f2 100644 --- a/src/client/ui/visitor/b.vue +++ b/src/client/ui/visitor/b.vue @@ -4,7 +4,7 @@ <div :style="{ backgroundImage: `url(${ $store.state.instance.meta.backgroundImageUrl })` }"> <div class="fade"></div> <h1 v-if="meta"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> - <div class="about _panel" v-if="meta"> + <div class="about" v-if="meta"> <div class="desc" v-html="meta.description || $t('introMisskey')"></div> </div> <div class="action"> @@ -28,14 +28,35 @@ <div class="main"> <div v-if="narrow" class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> - <h1 v-if="meta"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> + <h1 v-if="meta"> + <MkA to="/" class="link"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></MkA> + </h1> + <template v-if="$route.path === '/'"> + <div class="about" v-if="meta"> + <div class="desc" v-html="meta.description || $t('introMisskey')"></div> + </div> + <div class="action"> + <button class="_buttonPrimary" @click="signup()">{{ $t('signup') }}</button> + <button class="_button" @click="signin()">{{ $t('login') }}</button> + </div> + <div class="announcements panel"> + <header>{{ $t('announcements') }}</header> + <MkPagination :pagination="announcements" #default="{items}" class="list"> + <section class="item" v-for="(announcement, i) in items" :key="announcement.id"> + <div class="title">{{ announcement.title }}</div> + <div class="content"> + <Mfm :text="announcement.text"/> + <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/> + </div> + </section> + </MkPagination> + </div> + </template> </div> - <div class="contents" ref="contents" :class="{ wallpaper }"> - <header class="header" ref="header" v-show="$route.path !== '/'"> - <XHeader :info="pageInfo"/> - </header> - <main ref="main"> + <div class="contents" :class="{ wallpaper }"> + <XHeader class="header" :info="pageInfo"/> + <main> <router-view v-slot="{ Component }"> <transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> <component :is="Component" :ref="changePage"/> @@ -48,6 +69,27 @@ </div> </div> </div> + + <transition name="tray-back"> + <div class="menu-back _modalBg" + v-if="showMenu" + @click="showMenu = false" + @touchstart.passive="showMenu = false" + ></div> + </transition> + + <transition name="tray"> + <div v-if="showMenu" class="menu"> + <MkA to="/" class="link" active-class="active"><Fa :icon="faHome" class="icon"/>{{ $t('home') }}</MkA> + <MkA to="/explore" class="link" active-class="active"><Fa :icon="faHashtag" class="icon"/>{{ $t('explore') }}</MkA> + <MkA to="/featured" class="link" active-class="active"><Fa :icon="faFireAlt" class="icon"/>{{ $t('featured') }}</MkA> + <MkA to="/channels" class="link" active-class="active"><Fa :icon="faSatelliteDish" class="icon"/>{{ $t('channel') }}</MkA> + <div class="action"> + <button class="_buttonPrimary" @click="signup()">{{ $t('signup') }}</button> + <button class="_button" @click="signin()">{{ $t('login') }}</button> + </div> + </div> + </transition> </div> </template> @@ -61,7 +103,7 @@ import MkPagination from '@/components/ui/pagination.vue'; import XSigninDialog from '@/components/signin-dialog.vue'; import XSignupDialog from '@/components/signup-dialog.vue'; import MkButton from '@/components/ui/button.vue'; -import XHeader from '../_common_/header.vue'; +import XHeader from './header.vue'; const DESKTOP_THRESHOLD = 1100; @@ -79,6 +121,7 @@ export default defineComponent({ pageKey: 0, pageInfo: null, meta: null, + showMenu: false, narrow: window.innerWidth < 1280, announcements: { endpoint: 'announcements', @@ -159,6 +202,28 @@ export default defineComponent({ </script> <style lang="scss" scoped> +.tray-enter-active, +.tray-leave-active { + opacity: 1; + transform: translateX(0); + transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.tray-enter-from, +.tray-leave-active { + opacity: 0; + transform: translateX(-240px); +} + +.tray-back-enter-active, +.tray-back-leave-active { + opacity: 1; + transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.tray-back-enter-from, +.tray-back-leave-active { + opacity: 0; +} + .mk-app { display: flex; min-height: 100vh; @@ -217,6 +282,8 @@ export default defineComponent({ padding: 24px; text-align: center; box-sizing: border-box; + text-shadow: 0 0 8px black; + color: #fff; } > .action { @@ -272,30 +339,9 @@ export default defineComponent({ > .main { flex: 1; - > header { - position: relative; - z-index: 1; - background: var(--panel); - padding: 0 32px; - text-align: left; - overflow: auto; - white-space: nowrap; - - > .link { - display: inline-block; - line-height: 60px; - padding: 0 0.7em; - - &.MkA-active { - box-shadow: 0 -2px 0 0 var(--accent) inset; - } - } - } - > .banner { position: relative; width: 100%; - height: 200px; background-size: cover; background-position: center; @@ -311,15 +357,90 @@ export default defineComponent({ } > h1 { + position: relative; + z-index: 2; margin: 0; padding: 32px; text-align: center; color: #fff; text-shadow: 0 0 8px #000; - > .logo { - vertical-align: bottom; - max-height: 150px; + > .link { + display: block; + + > ::v-deep(.logo) { + vertical-align: bottom; + max-height: 100px; + } + } + } + + > .panel { + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + background: rgba(0, 0, 0, 0.5); + border-radius: var(--radius); + + &, * { + color: #fff !important; + } + } + + > .about { + display: block; + margin: 0 0 16px 0; + padding: 0 16px 24px 16px; + text-align: center; + box-sizing: border-box; + text-shadow: 0 0 8px black; + color: #fff; + } + + > .action { + padding: 0 64px; + + > button { + display: block; + width: 100%; + padding: 10px; + box-sizing: border-box; + text-align: center; + border-radius: 999px; + + &._button { + background: var(--panel); + } + + &:first-child { + margin-bottom: 16px; + } + } + } + + > .announcements { + margin: 64px 64px 16px 64px; + text-align: left; + + > header { + padding: 12px 16px; + border-bottom: solid 1px rgba(255, 255, 255, 0.5); + } + + > .list { + max-height: 300px; + overflow: auto; + + > .item { + padding: 12px 16px; + + & + .item { + border-top: solid 1px rgba(255, 255, 255, 0.5); + } + + > .title { + font-weight: bold; + } + } } } } @@ -328,21 +449,6 @@ export default defineComponent({ position: relative; z-index: 1; - > .header { - position: sticky; - top: 0; - left: 0; - z-index: 1000; - height: 60px; - width: 100%; - line-height: 60px; - text-align: center; - -webkit-backdrop-filter: blur(32px); - backdrop-filter: blur(32px); - background-color: var(--header); - border-bottom: 1px solid var(--divider); - } - > .powered-by { padding: 28px; font-size: 14px; @@ -357,8 +463,54 @@ export default defineComponent({ } } } + + > .menu-back { + position: fixed; + z-index: 1001; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + } + + > .menu { + position: fixed; + z-index: 1001; + top: 0; + left: 0; + width: 240px; + height: 100vh; + background: var(--panel); + + > .link { + display: block; + padding: 16px; + + > .icon { + margin-right: 1em; + } + } + + > .action { + padding: 16px; + + > button { + display: block; + width: 100%; + padding: 10px; + box-sizing: border-box; + text-align: center; + border-radius: 999px; + + &._button { + background: var(--panel); + } + + &:first-child { + margin-bottom: 16px; + } + } + } + } } </style> - -<style lang="scss"> -</style> diff --git a/src/client/ui/visitor/header.vue b/src/client/ui/visitor/header.vue index ff6fb91443..8086164637 100644 --- a/src/client/ui/visitor/header.vue +++ b/src/client/ui/visitor/header.vue @@ -1,31 +1,47 @@ <template> <div class="sqxihjet"> - <div class="content"> - <MkA to="/" class="link" active-class="active">{{ $t('home') }}</MkA> - <MkA to="/explore" class="link" active-class="active">{{ $t('explore') }}</MkA> - <MkA to="/featured" class="link" active-class="active">{{ $t('featured') }}</MkA> - <MkA to="/channels" class="link" active-class="active">{{ $t('channel') }}</MkA> - <div class="page link" v-if="info"> - <div class="title"> - <Fa v-if="info.icon" :icon="info.icon" :key="info.icon" class="icon"/> - <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true"/> - <span v-if="info.title" class="text">{{ info.title }}</span> - <MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/> + <div class="wide" v-if="narrow === false"> + <div class="content"> + <MkA to="/" class="link" active-class="active"><Fa :icon="faHome" class="icon"/>{{ $t('home') }}</MkA> + <MkA to="/explore" class="link" active-class="active"><Fa :icon="faHashtag" class="icon"/>{{ $t('explore') }}</MkA> + <MkA to="/featured" class="link" active-class="active"><Fa :icon="faFireAlt" class="icon"/>{{ $t('featured') }}</MkA> + <MkA to="/channels" class="link" active-class="active"><Fa :icon="faSatelliteDish" class="icon"/>{{ $t('channel') }}</MkA> + <div class="page active link" v-if="info"> + <div class="title"> + <Fa v-if="info.icon" :icon="info.icon" :key="info.icon" class="icon"/> + <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true"/> + <span v-if="info.title" class="text">{{ info.title }}</span> + <MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/> + </div> + <button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button> + </div> + <div class="right"> + <button class="_button search" @click="search()"><Fa :icon="faSearch" class="icon"/><span>{{ $t('search') }}</span></button> + <button class="_buttonPrimary signup" @click="signup()">{{ $t('signup') }}</button> + <button class="_button login" @click="signin()">{{ $t('login') }}</button> </div> - <button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button> </div> - <div class="right"> - <button class="_button search" @click="search()"><Fa :icon="faSearch" class="icon"/><span>{{ $t('search') }}</span></button> - <button class="_buttonPrimary signup" @click="signup()">{{ $t('signup') }}</button> - <button class="_button login" @click="signin()">{{ $t('login') }}</button> + </div> + <div class="narrow" v-else-if="narrow === true"> + <button class="menu _button" @click="$parent.showMenu = true"> + <Fa :icon="faBars" class="icon"/> + </button> + <div class="title" v-if="info"> + <Fa v-if="info.icon" :icon="info.icon" :key="info.icon" class="icon"/> + <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true"/> + <span v-if="info.title" class="text">{{ info.title }}</span> + <MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/> </div> + <button class="action _button" v-if="info && info.action" @click.stop="info.action.handler"> + <Fa :icon="info.action.icon" :key="info.action.icon" class="icon"/> + </button> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import { faSearch } from '@fortawesome/free-solid-svg-icons'; +import { faSearch, faHome, faFireAlt, faHashtag, faSatelliteDish, faBars } from '@fortawesome/free-solid-svg-icons'; import XSigninDialog from '@/components/signin-dialog.vue'; import XSignupDialog from '@/components/signup-dialog.vue'; import * as os from '@/os'; @@ -40,10 +56,16 @@ export default defineComponent({ data() { return { - faSearch + narrow: null, + showMenu: false, + faSearch, faHome, faFireAlt, faHashtag, faSatelliteDish, faBars, }; }, + mounted() { + this.narrow = this.$el.clientWidth < 1300; + }, + methods: { signin() { os.popup(XSigninDialog, { @@ -65,105 +87,142 @@ export default defineComponent({ <style lang="scss" scoped> .sqxihjet { $height: 60px; + position: sticky; + top: 0; + left: 0; + z-index: 1000; line-height: $height; - background: var(--panel); + -webkit-backdrop-filter: blur(32px); + backdrop-filter: blur(32px); + background-color: var(--X16); - > .content { - max-width: 1400px; - margin: 0 auto; - display: flex; - align-items: center; + > .wide { + > .content { + max-width: 1400px; + margin: 0 auto; + display: flex; + align-items: center; - > .link { - display: inline-block; - padding: 0 16px; - line-height: $height - 4px; - border-top: solid 2px transparent; - border-bottom: solid 2px transparent; - - &.page { - border-bottom-color: var(--accent); - } - } - - > .page { - > .title { + > .link { + $line: 3px; display: inline-block; - vertical-align: bottom; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; + padding: 0 16px; + line-height: $height - ($line * 2); + border-top: solid $line transparent; + border-bottom: solid $line transparent; - > .indicator { - position: absolute; - top: initial; - right: 8px; - top: 8px; - color: var(--indicator); - font-size: 12px; - animation: blink 1s infinite; + > .icon { + margin-right: 0.5em; } - > .icon + .text { - margin-left: 8px; + &.page { + border-bottom-color: var(--accent); } + } - > .avatar { - $size: 32px; + > .page { + > .title { display: inline-block; - width: $size; - height: $size; - vertical-align: middle; - margin-right: 8px; - pointer-events: none; - } + vertical-align: bottom; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; - &._button { - &:hover { + > .icon + .text { + margin-left: 8px; + } + + > .avatar { + $size: 32px; + display: inline-block; + width: $size; + height: $size; + vertical-align: middle; + margin-right: 8px; + pointer-events: none; + } + + &._button { + &:hover { + color: var(--fgHighlighted); + } + } + + &.selected { + box-shadow: 0 -2px 0 0 var(--accent) inset; color: var(--fgHighlighted); } } - &.selected { - box-shadow: 0 -2px 0 0 var(--accent) inset; - color: var(--fgHighlighted); + > .action { + padding: 0 0 0 16px; } } - > .action { - padding: 0 0 0 16px; - } - } + > .right { + margin-left: auto; - > .right { - margin-left: auto; + > .search { + background: var(--bg); + border-radius: 999px; + width: 230px; + line-height: $height - 20px; + margin-right: 16px; + text-align: left; - > .search { - background: var(--bg); - border-radius: 999px; - width: 230px; - line-height: $height - 20px; - margin-right: 16px; - text-align: left; + > * { + opacity: 0.7; + } - > * { - opacity: 0.7; + > .icon { + padding: 0 16px; + } } - > .icon { + > .signup { + border-radius: 999px; + padding: 0 24px; + line-height: $height - 20px; + } + + > .login { padding: 0 16px; } } + } + } - > .signup { - border-radius: 999px; - padding: 0 24px; - line-height: $height - 20px; + > .narrow { + display: flex; + + > .menu, + > .action { + width: $height; + height: $height; + font-size: 20px; + } + + > .title { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + text-align: center; + + > .icon + .text { + margin-left: 8px; } - > .login { - padding: 0 16px; + > .avatar { + $size: 32px; + display: inline-block; + width: $size; + height: $size; + vertical-align: middle; + margin-right: 8px; + pointer-events: none; } } }