From d3fe02fb3e8dd7bfc45c246d54d45acccd5959c7 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 10 Apr 2021 12:40:50 +0900 Subject: [PATCH] Default UI redesign (#7429) * wip * wip * wip * wip * Update default.sidebar.vue * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update sticky-sidebar.ts * wip * wip * Update messaging-room.form.vue * Update timeline.vue --- locales/ja-JP.yml | 1 + src/client/components/abuse-report-window.vue | 2 +- src/client/components/channel-preview.vue | 2 +- src/client/components/date-separated-list.vue | 31 +- src/client/components/drive.file.vue | 4 +- src/client/components/drive.vue | 5 +- src/client/components/emoji-picker-dialog.vue | 4 +- src/client/components/emoji-picker-window.vue | 4 +- src/client/components/emoji-picker.vue | 6 +- src/client/components/launch-pad.vue | 2 +- src/client/components/media-image.vue | 2 +- src/client/components/note-detailed.vue | 6 +- src/client/components/note-header.vue | 2 +- src/client/components/note.sub.vue | 2 +- src/client/components/note.vue | 23 +- src/client/components/notes.vue | 4 +- src/client/components/page-window.vue | 5 +- src/client/components/poll.vue | 2 +- src/client/components/post-form.vue | 2 +- src/client/components/signin.vue | 7 +- src/client/components/signup-dialog.vue | 2 +- .../components/taskmanager.api-window.vue | 2 +- src/client/components/taskmanager.vue | 4 +- src/client/components/ui/container.vue | 6 +- src/client/components/ui/folder.vue | 4 + src/client/components/ui/modal-window.vue | 4 +- src/client/components/user-info.vue | 4 +- src/client/components/user-select-dialog.vue | 2 +- src/client/components/visibility-picker.vue | 2 +- src/client/init.ts | 1 + src/client/pages/clip.vue | 2 +- src/client/pages/doc.vue | 4 +- src/client/pages/drive.vue | 13 +- src/client/pages/explore.vue | 2 +- src/client/pages/instance/instance.vue | 4 +- src/client/pages/messaging/index.vue | 68 ++- .../pages/messaging/messaging-room.form.vue | 4 +- src/client/pages/my-clips/index.vue | 2 +- src/client/pages/note.vue | 15 +- src/client/pages/notifications.vue | 6 +- src/client/pages/page-editor/page-editor.vue | 134 +++--- src/client/pages/reversi/index.vue | 2 +- src/client/pages/timeline.vue | 185 ++++---- src/client/pages/user/index.timeline.vue | 4 +- src/client/pages/user/index.vue | 150 +++--- src/client/pages/welcome.entrance.a.vue | 4 +- src/client/pages/welcome.entrance.c.vue | 4 +- src/client/scripts/sticky-sidebar.ts | 25 +- src/client/scripts/theme.ts | 1 + src/client/sidebar.ts | 6 + src/client/style.scss | 95 ++-- src/client/themes/_dark.json5 | 2 +- src/client/themes/_light.json5 | 2 +- src/client/themes/d-dark.json5 | 2 +- src/client/themes/d-persimmon.json5 | 1 - src/client/themes/l-light.json5 | 4 +- src/client/themes/l-rainy.json5 | 21 + src/client/ui/_common_/header.vue | 100 ++-- .../{components => ui/_common_}/sidebar.vue | 12 +- src/client/ui/chat/index.vue | 12 +- src/client/ui/chat/note-header.vue | 2 +- src/client/ui/chat/note.sub.vue | 2 +- src/client/ui/chat/note.vue | 2 +- src/client/ui/chat/post-form.vue | 4 +- src/client/ui/chat/side.vue | 4 +- src/client/ui/deck.vue | 2 +- src/client/ui/deck/column.vue | 2 +- src/client/ui/deck/main-column.vue | 2 +- src/client/ui/default.side.vue | 2 +- src/client/ui/default.sidebar.vue | 362 +++++++++++++++ src/client/ui/default.vue | 241 ++++------ src/client/ui/default.widgets.vue | 2 - src/client/ui/desktop.vue | 2 +- src/client/ui/universal.vue | 433 ++++++++++++++++++ src/client/ui/universal.widgets.vue | 81 ++++ src/client/ui/zen.vue | 2 +- src/client/widgets/aiscript.vue | 4 +- src/client/widgets/federation.vue | 2 +- src/client/widgets/job-queue.vue | 2 +- src/client/widgets/memo.vue | 2 +- src/client/widgets/trends.vue | 2 +- 81 files changed, 1532 insertions(+), 658 deletions(-) create mode 100644 src/client/themes/l-rainy.json5 rename src/client/{components => ui/_common_}/sidebar.vue (96%) create mode 100644 src/client/ui/default.sidebar.vue create mode 100644 src/client/ui/universal.vue create mode 100644 src/client/ui/universal.widgets.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5729da8da8..b06432fc16 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -710,6 +710,7 @@ typingUsers: "{users}が入力中" jumpToSpecifiedDate: "特定の日付にジャンプ" showingPastTimeline: "過去のタイムラインを表示しています" clear: "クリア" +markAllAsRead: "全て既読にする" _email: _follow: diff --git a/src/client/components/abuse-report-window.vue b/src/client/components/abuse-report-window.vue index 7dbb9657bd..35732d5731 100644 --- a/src/client/components/abuse-report-window.vue +++ b/src/client/components/abuse-report-window.vue @@ -80,6 +80,6 @@ export default defineComponent({ <style lang="scss" scoped> .dpvffvvy { - --section-padding: 16px; + --root-margin: 16px; } </style> diff --git a/src/client/components/channel-preview.vue b/src/client/components/channel-preview.vue index e222ad7ae7..4dc633bcb7 100644 --- a/src/client/components/channel-preview.vue +++ b/src/client/components/channel-preview.vue @@ -123,7 +123,7 @@ export default defineComponent({ > footer { padding: 12px 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); > span { opacity: 0.7; diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue index be9f01ca1f..433655d6ed 100644 --- a/src/client/components/date-separated-list.vue +++ b/src/client/components/date-separated-list.vue @@ -37,14 +37,16 @@ export default defineComponent({ }); } + const noGap = [...document.querySelectorAll('._noGap_')].some(el => el.contains(this.$parent.$el)); + return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? { - class: 'sqadhkmv _list_', + class: 'sqadhkmv' + (noGap ? ' _block' : ''), name: 'list', tag: 'div', 'data-direction': this.direction, 'data-reversed': this.reversed ? 'true' : 'false', } : { - class: 'sqadhkmv _list_', + class: 'sqadhkmv', }, this.items.map((item, i) => { const el = this.$slots.default({ item: item @@ -117,11 +119,7 @@ export default defineComponent({ transform: translateY(-64px); } } -} -</style> -<style lang="scss"> -.sqadhkmv { > .separator { text-align: center; @@ -155,4 +153,25 @@ export default defineComponent({ } } } + +._noGap_ .sqadhkmv { + > * { + margin: 0 !important; + border: none; + border-radius: 0; + box-shadow: none; + + &:not(:last-child) { + border-bottom: solid 0.5px var(--divider); + } + } +} + +._inContainer_ .sqadhkmv > * { + margin: 0 !important; + border: none; + border-bottom: solid 0.5px var(--divider); + border-radius: 0; + box-shadow: none; +} </style> diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue index 03f2da008d..fb8b50d25a 100644 --- a/src/client/components/drive.file.vue +++ b/src/client/components/drive.file.vue @@ -330,8 +330,8 @@ export default defineComponent({ } > .thumbnail { - width: 128px; - height: 128px; + width: 110px; + height: 110px; margin: auto; } diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue index 150d0d8774..2c8c16c482 100644 --- a/src/client/components/drive.vue +++ b/src/client/components/drive.vue @@ -704,6 +704,7 @@ export default defineComponent({ > .main { flex: 1; overflow: auto; + padding: var(--margin); &, * { user-select: none; @@ -735,7 +736,7 @@ export default defineComponent({ > .folder, > .file { flex-grow: 1; - width: 144px; + width: 128px; margin: 4px; box-sizing: border-box; } @@ -743,7 +744,7 @@ export default defineComponent({ > .padding { flex-grow: 1; pointer-events: none; - width: 144px + 8px; + width: 128px + 8px; } } diff --git a/src/client/components/emoji-picker-dialog.vue b/src/client/components/emoji-picker-dialog.vue index 5bdbc330ad..c4b12e2f61 100644 --- a/src/client/components/emoji-picker-dialog.vue +++ b/src/client/components/emoji-picker-dialog.vue @@ -123,7 +123,7 @@ export default defineComponent({ > .index { min-height: var(--height); position: relative; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); > .arrow { position: absolute; @@ -181,7 +181,7 @@ export default defineComponent({ } &.result { - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); &:empty { display: none; diff --git a/src/client/components/emoji-picker-window.vue b/src/client/components/emoji-picker-window.vue index 5504eaecd6..53b6ae6b32 100644 --- a/src/client/components/emoji-picker-window.vue +++ b/src/client/components/emoji-picker-window.vue @@ -119,7 +119,7 @@ export default defineComponent({ > .index { min-height: var(--height); position: relative; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); > .arrow { position: absolute; @@ -177,7 +177,7 @@ export default defineComponent({ } &.result { - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); &:empty { display: none; diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue index 573833b9d3..a212c15049 100644 --- a/src/client/components/emoji-picker.vue +++ b/src/client/components/emoji-picker.vue @@ -402,7 +402,7 @@ export default defineComponent({ > .tab { flex: 1; height: 38px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); &.active { border-top: solid 1px var(--accent); @@ -425,7 +425,7 @@ export default defineComponent({ > div { &:not(.index) { padding: 4px 0 8px 0; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } > header { @@ -492,7 +492,7 @@ export default defineComponent({ } &.result { - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); &:empty { display: none; diff --git a/src/client/components/launch-pad.vue b/src/client/components/launch-pad.vue index a81320954c..7610b44eb5 100644 --- a/src/client/components/launch-pad.vue +++ b/src/client/components/launch-pad.vue @@ -146,7 +146,7 @@ export default defineComponent({ > .sub { margin-top: 8px; padding-top: 8px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } </style> diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 41760d98d7..4de5daa84f 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -123,7 +123,7 @@ export default defineComponent({ .gqnyydlz { position: relative; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); > i { display: block; diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue index 5124b2a88c..373c96e5a0 100644 --- a/src/client/components/note-detailed.vue +++ b/src/client/components/note-detailed.vue @@ -1,6 +1,6 @@ <template> <div - class="note _panel" + class="note _block" v-if="!muted" v-show="!isDeleted" :tabindex="!isDeleted ? '-1' : null" @@ -1007,7 +1007,7 @@ export default defineComponent({ margin: 0 0.5em; padding: 4px 6px; font-size: 80%; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 4px; } @@ -1110,7 +1110,7 @@ export default defineComponent({ } > .reply { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } &.max-width_500px { diff --git a/src/client/components/note-header.vue b/src/client/components/note-header.vue index a6e9b6fe56..ab40c5fd4a 100644 --- a/src/client/components/note-header.vue +++ b/src/client/components/note-header.vue @@ -78,7 +78,7 @@ export default defineComponent({ margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 3px; } diff --git a/src/client/components/note.sub.vue b/src/client/components/note.sub.vue index 853d481406..899c4b2f16 100644 --- a/src/client/components/note.sub.vue +++ b/src/client/components/note.sub.vue @@ -139,7 +139,7 @@ export default defineComponent({ } > .reply { - border-left: solid 1px var(--divider); + border-left: solid 0.5px var(--divider); margin-top: 10px; } } diff --git a/src/client/components/note.vue b/src/client/components/note.vue index a656ffc356..870f8a839b 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -1,6 +1,6 @@ <template> <div - class="tkcbzcuz _panel" + class="tkcbzcuz" v-if="!muted" v-show="!isDeleted" :tabindex="!isDeleted ? '-1' : null" @@ -90,7 +90,7 @@ </div> </article> </div> -<div v-else class="_panel muted" @click="muted = false"> +<div v-else class="muted" @click="muted = false"> <I18n :src="$ts.userSaysSomething" tag="small"> <template #name> <MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId"> @@ -851,6 +851,7 @@ export default defineComponent({ position: relative; transition: box-shadow 0.1s ease; overflow: hidden; + overflow: clip; contain: content; // これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、 @@ -981,11 +982,17 @@ export default defineComponent({ > .avatar { flex-shrink: 0; display: block; - //position: sticky; - //top: 72px; margin: 0 14px 8px 0; width: 58px; height: 58px; + position: sticky; + top: calc(22px + var(--stickyTop, 0px)); + left: 0; + + /* iOSはoverflow: clipをサポートしていない影響でposition: stickyが動作しない */ + @supports (-webkit-touch-callout: none) { + top: 0; + } } > .main { @@ -1106,7 +1113,7 @@ export default defineComponent({ } > .reply { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } &.max-width_500px { @@ -1129,6 +1136,12 @@ export default defineComponent({ margin: 0 10px 8px 0; width: 50px; height: 50px; + top: calc(14px + var(--stickyTop, 0px)); + + /* iOSはoverflow: clipをサポートしていない影響でposition: stickyが動作しない */ + @supports (-webkit-touch-callout: none) { + top: 0; + } } } } diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue index 80a9502d5f..724131d0c2 100644 --- a/src/client/components/notes.vue +++ b/src/client/components/notes.vue @@ -1,5 +1,5 @@ <template> -<div class="_list_"> +<div> <div class="_fullinfo" v-if="empty"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> <div>{{ $ts.noNotes }}</div> @@ -15,7 +15,7 @@ </div> <XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> - <XNote :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/> + <XNote :note="note" class="_block _isolated" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/> </XList> <div v-show="more && !reversed" style="margin-top: var(--margin);"> diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index ca6f4dd73e..e423757e0c 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -14,7 +14,7 @@ <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);"> + <div class="yrolvcoq _flat_"> <component :is="component" v-bind="props" :ref="changePage"/> </div> </XWindow> @@ -155,6 +155,7 @@ export default defineComponent({ <style lang="scss" scoped> .yrolvcoq { - --section-padding: 16px; + min-height: 100%; + background: var(--bg); } </style> diff --git a/src/client/components/poll.vue b/src/client/components/poll.vue index af3b3804ab..6cf6a8e918 100644 --- a/src/client/components/poll.vue +++ b/src/client/components/poll.vue @@ -110,7 +110,7 @@ export default defineComponent({ position: relative; margin: 4px 0; padding: 4px 8px; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 4px; overflow: hidden; cursor: pointer; diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 765824b2ab..a67f3ed10a 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -767,7 +767,7 @@ export default defineComponent({ > .cw { z-index: 1; padding-bottom: 8px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } > .text { diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index b9d8fe6a77..193d5afecf 100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue @@ -1,6 +1,6 @@ <template> -<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> - <div class="auth _section"> +<form class="eppvobhk _root" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> + <div class="auth"> <div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> <div class="normal-signin" v-if="!totpLogin"> <MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange"> @@ -38,7 +38,8 @@ </div> </div> </div> - <div class="social _section"> + <div class="_hr"></div> + <div class="social"> <a class="_borderButton _vMargin" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a> <a class="_borderButton _vMargin" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a> <a class="_borderButton _vMargin" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a> diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue index 072a5ac19f..5015b77c75 100644 --- a/src/client/components/signup-dialog.vue +++ b/src/client/components/signup-dialog.vue @@ -7,7 +7,7 @@ > <template #header>{{ $ts.signup }}</template> - <div class="_section"> + <div class="_root"> <XSignup :auto-set="autoSet" @signup="onSignup"/> </div> </XModalWindow> diff --git a/src/client/components/taskmanager.api-window.vue b/src/client/components/taskmanager.api-window.vue index 9b6c3f16d0..c9b2c43413 100644 --- a/src/client/components/taskmanager.api-window.vue +++ b/src/client/components/taskmanager.api-window.vue @@ -9,7 +9,7 @@ <template #header>Req Viewer</template> <div class="rlkneywz"> - <MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);"> + <MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);"> <option value="req">Request</option> <option value="res">Response</option> </MkTab> diff --git a/src/client/components/taskmanager.vue b/src/client/components/taskmanager.vue index af9033178e..1339e2e352 100644 --- a/src/client/components/taskmanager.vue +++ b/src/client/components/taskmanager.vue @@ -4,7 +4,7 @@ <Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager </template> <div class="qljqmnzj _monospace"> - <MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);"> + <MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);"> <option value="windows">Windows</option> <option value="stream">Stream</option> <option value="streamPool">Stream (Pool)</option> @@ -215,7 +215,7 @@ export default defineComponent({ width: 100%; padding: 8px 16px; box-sizing: border-box; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); font-size: 0.9em; > div { diff --git a/src/client/components/ui/container.vue b/src/client/components/ui/container.vue index c3353cca89..efcff7a36c 100644 --- a/src/client/components/ui/container.vue +++ b/src/client/components/ui/container.vue @@ -1,5 +1,5 @@ <template> -<div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }"> +<div class="ukygtjoj _block _isolated" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }"> <header v-if="showHeader" ref="header"> <div class="title"><slot name="header"></slot></div> <div class="sub"> @@ -136,7 +136,7 @@ export default defineComponent({ position: relative; color: var(--panelHeaderFg); background: var(--panelHeaderBg); - box-shadow: 0 1px 0 0 var(--panelHeaderDivider); + border-bottom: solid 0.5px var(--panelHeaderDivider); z-index: 2; line-height: 1.4em; @@ -172,7 +172,7 @@ export default defineComponent({ padding: 24px; & + ._content { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } } diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue index 1cd67c2521..523298f225 100644 --- a/src/client/components/ui/folder.vue +++ b/src/client/components/ui/folder.vue @@ -137,4 +137,8 @@ export default defineComponent({ } } } + +._flat_ .ssazuxis { + margin: var(--margin); +} </style> diff --git a/src/client/components/ui/modal-window.vue b/src/client/components/ui/modal-window.vue index 2cdf961379..ca17ae6093 100644 --- a/src/client/components/ui/modal-window.vue +++ b/src/client/components/ui/modal-window.vue @@ -94,10 +94,10 @@ export default defineComponent({ flex-direction: column; contain: content; - --section-padding: 24px; + --root-margin: 24px; @media (max-width: 500px) { - --section-padding: 16px; + --root-margin: 16px; } > .header { diff --git a/src/client/components/user-info.vue b/src/client/components/user-info.vue index 34ea38c3b4..ac2f9a75a6 100644 --- a/src/client/components/user-info.vue +++ b/src/client/components/user-info.vue @@ -104,7 +104,7 @@ export default defineComponent({ > .description { padding: 16px; font-size: 0.8em; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); > .mfm { display: -webkit-box; @@ -116,7 +116,7 @@ export default defineComponent({ > .status { padding: 10px 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); > div { display: inline-block; diff --git a/src/client/components/user-select-dialog.vue b/src/client/components/user-select-dialog.vue index e21deea178..05a43402a8 100644 --- a/src/client/components/user-select-dialog.vue +++ b/src/client/components/user-select-dialog.vue @@ -153,7 +153,7 @@ export default defineComponent({ > .user { display: flex; align-items: center; - padding: 8px var(--section-padding); + padding: 8px var(--root-margin); font-size: 14px; &:hover { diff --git a/src/client/components/visibility-picker.vue b/src/client/components/visibility-picker.vue index 0b98d30b9d..caa2b116a6 100644 --- a/src/client/components/visibility-picker.vue +++ b/src/client/components/visibility-picker.vue @@ -97,7 +97,7 @@ export default defineComponent({ > .divider { margin: 8px 0; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } > button { diff --git a/src/client/init.ts b/src/client/init.ts index bacc839c29..e77e53dc64 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -161,6 +161,7 @@ const app = createApp(await ( ui === 'deck' ? import('@client/ui/deck.vue') : ui === 'desktop' ? import('@client/ui/desktop.vue') : ui === 'chat' ? import('@client/ui/chat/index.vue') : + ui === 'pope' ? import('@client/ui/universal.vue') : import('@client/ui/default.vue') ).then(x => x.default)); diff --git a/src/client/pages/clip.vue b/src/client/pages/clip.vue index c6e46a9834..9ae41c72f2 100644 --- a/src/client/pages/clip.vue +++ b/src/client/pages/clip.vue @@ -142,7 +142,7 @@ export default defineComponent({ > .user { $height: 32px; padding: 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); line-height: $height; > .avatar { diff --git a/src/client/pages/doc.vue b/src/client/pages/doc.vue index 4341cd1f6e..8b8a89efc6 100644 --- a/src/client/pages/doc.vue +++ b/src/client/pages/doc.vue @@ -150,7 +150,7 @@ export default defineComponent({ font-size: 1.25em; padding: 0 0 0.5em 0; margin: 1.5em 0 1em 0; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } ::v-deep(table) { @@ -170,7 +170,7 @@ export default defineComponent({ ::v-deep(kbd.key) { display: inline-block; padding: 6px 8px; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 4px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); } diff --git a/src/client/pages/drive.vue b/src/client/pages/drive.vue index 754581a8b6..3ca7f60099 100644 --- a/src/client/pages/drive.vue +++ b/src/client/pages/drive.vue @@ -6,7 +6,7 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; -import { faCloud, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; +import { faCloud } from '@fortawesome/free-solid-svg-icons'; import XDrive from '@client/components/drive.vue'; import * as os from '@client/os'; @@ -20,19 +20,10 @@ export default defineComponent({ INFO: { title: computed(() => this.folder ? this.folder.name : this.$ts.drive), icon: faCloud, - action: { - icon: faEllipsisH, - handler: this.menu - } + menu: () => this.$refs.drive.getMenu() }, folder: null, }; }, - - methods: { - menu(ev) { - os.modalMenu(this.$refs.drive.getMenu(), ev.currentTarget || ev.target); - } - } }); </script> diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue index da2eaffb84..dc3d424ba4 100644 --- a/src/client/pages/explore.vue +++ b/src/client/pages/explore.vue @@ -1,5 +1,5 @@ <template> -<div class="lznhrdub"> +<div class="lznhrdub _root"> <div class="_section"> <MkInput v-model:value="query" :debounce="true" type="search"><template #icon><Fa :icon="faSearch"/></template><span>{{ $ts.searchUser }}</span></MkInput> diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue index 118e8eae6e..1adb3ab9d2 100644 --- a/src/client/pages/instance/instance.vue +++ b/src/client/pages/instance/instance.vue @@ -500,12 +500,12 @@ export default defineComponent({ } &:not(:first-child) { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } > .chart { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); padding: 16px 0 12px 0; > .header { diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue index c906b968a4..114a01ec3c 100644 --- a/src/client/pages/messaging/index.vue +++ b/src/client/pages/messaging/index.vue @@ -1,40 +1,38 @@ <template> -<div class="_section"> - <div class="mk-messaging _content" v-size="{ max: [400] }"> - <MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $ts.startMessaging }}</MkButton> +<div class="yweeujhr _root" v-size="{ max: [400] }"> + <MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $ts.startMessaging }}</MkButton> - <div class="history" v-if="messages.length > 0"> - <MkA v-for="(message, i) in messages" - class="message _panel" - :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($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" - v-anim="i" - > - <div> - <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/> - <header v-if="message.groupId"> - <span class="name">{{ message.group.name }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <header v-else> - <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> - <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <div class="body"> - <p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p> - </div> + <div class="history" v-if="messages.length > 0"> + <MkA v-for="(message, i) in messages" + class="message _block _isolated" + :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($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" + v-anim="i" + > + <div> + <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/> + <header v-if="message.groupId"> + <span class="name">{{ message.group.name }}</span> + <MkTime :time="message.createdAt" class="time"/> + </header> + <header v-else> + <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> + <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> + <MkTime :time="message.createdAt" class="time"/> + </header> + <div class="body"> + <p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p> </div> - </MkA> - </div> - <div class="_fullinfo" v-if="!fetching && messages.length == 0"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noHistory }}</div> - </div> - <MkLoading v-if="fetching"/> + </div> + </MkA> </div> + <div class="_fullinfo" v-if="!fetching && messages.length == 0"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noHistory }}</div> + </div> + <MkLoading v-if="fetching"/> </div> </template> @@ -167,10 +165,10 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.mk-messaging { +.yweeujhr { > .start { - margin: 0 auto var(--margin) auto; + margin: var(--margin) auto var(--margin) auto; } > .history { diff --git a/src/client/pages/messaging/messaging-room.form.vue b/src/client/pages/messaging/messaging-room.form.vue index 5826b8d0d2..c547e18850 100644 --- a/src/client/pages/messaging/messaging-room.form.vue +++ b/src/client/pages/messaging/messaging-room.form.vue @@ -1,5 +1,5 @@ <template> -<div class="mk-messaging-form _panel" +<div class="pemppnzi _block" @dragover.stop="onDragover" @drop.stop="onDrop" > @@ -230,7 +230,7 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.mk-messaging-form { +.pemppnzi { position: relative; > textarea { diff --git a/src/client/pages/my-clips/index.vue b/src/client/pages/my-clips/index.vue index b724dfb6b7..d29d147e87 100644 --- a/src/client/pages/my-clips/index.vue +++ b/src/client/pages/my-clips/index.vue @@ -96,7 +96,7 @@ export default defineComponent({ > .description { margin-top: 8px; padding-top: 8px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } } diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue index eb46c49d8b..6debb611fd 100644 --- a/src/client/pages/note.vue +++ b/src/client/pages/note.vue @@ -1,12 +1,12 @@ <template> -<div class="fcuexfpr"> +<div class="fcuexfpr _root"> <div v-if="note" class="note" v-anim> - <div class="_section" v-if="showNext"> + <div class="_vMargin" v-if="showNext"> <XNotes class="_content _noGap_" :pagination="next"/> </div> - <div class="_section main"> - <MkButton v-if="!showNext && hasNext" class="load next _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton> + <div class="main _vMargin"> + <MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton> <div class="_content _vMargin"> <MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/> <XNoteDetailed v-model:note="note" :key="note.id" class="_vMargin"/> @@ -21,10 +21,10 @@ </div> </MkA> </div> - <MkButton v-if="!showPrev && hasPrev" class="load prev _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton> + <MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton> </div> - <div class="_section" v-if="showPrev"> + <div class="_vMargin" v-if="showPrev"> <XNotes class="_content _noGap_" :pagination="prev"/> </div> </div> @@ -137,6 +137,7 @@ export default defineComponent({ > .main { > .load { min-width: 0; + margin: 0 auto; border-radius: 999px; &.next { @@ -165,7 +166,7 @@ export default defineComponent({ > .user { $height: 32px; padding-top: 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); line-height: $height; > .avatar { diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue index a6d73b4290..19b1726759 100644 --- a/src/client/pages/notifications.vue +++ b/src/client/pages/notifications.vue @@ -1,8 +1,6 @@ <template> -<div> - <div class="_section"> - <XNotifications class="_content" @before="before" @after="after" page/> - </div> +<div class="_root"> + <XNotifications class="_content" @before="before" @after="after" page/> </div> </template> diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index 762ee774ed..79e3bfdf9a 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -1,87 +1,85 @@ <template> -<div class="_section"> - <div class="_content"> - <MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $ts._pages.viewPage }}</MkA> +<div class="_root"> + <MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $ts._pages.viewPage }}</MkA> - <div class="buttons" style="margin: 16px 0;"> - <MkButton inline @click="save" primary class="save" v-if="!readonly"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton> - <MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><Fa :icon="faCopy"/> {{ $ts.duplicate }}</MkButton> - <MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton> - </div> + <div class="buttons" style="margin: 16px;"> + <MkButton inline @click="save" primary class="save" v-if="!readonly"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton> + <MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><Fa :icon="faCopy"/> {{ $ts.duplicate }}</MkButton> + <MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton> + </div> - <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> - <template #header><Fa :icon="faCog"/> {{ $ts._pages.pageSetting }}</template> - <div class="_section"> - <MkInput v-model:value="title"> - <span>{{ $ts._pages.title }}</span> - </MkInput> + <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> + <template #header><Fa :icon="faCog"/> {{ $ts._pages.pageSetting }}</template> + <div style="padding: 16px;"> + <MkInput v-model:value="title"> + <span>{{ $ts._pages.title }}</span> + </MkInput> - <MkInput v-model:value="summary"> - <span>{{ $ts._pages.summary }}</span> - </MkInput> + <MkInput v-model:value="summary"> + <span>{{ $ts._pages.summary }}</span> + </MkInput> - <MkInput v-model:value="name"> - <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> - <span>{{ $ts._pages.url }}</span> - </MkInput> + <MkInput v-model:value="name"> + <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> + <span>{{ $ts._pages.url }}</span> + </MkInput> - <MkSwitch v-model:value="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch> + <MkSwitch v-model:value="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch> - <MkSelect v-model:value="font"> - <template #label>{{ $ts._pages.font }}</template> - <option value="serif">{{ $ts._pages.fontSerif }}</option> - <option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option> - </MkSelect> + <MkSelect v-model:value="font"> + <template #label>{{ $ts._pages.font }}</template> + <option value="serif">{{ $ts._pages.fontSerif }}</option> + <option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option> + </MkSelect> - <MkSwitch v-model:value="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch> + <MkSwitch v-model:value="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch> - <div class="eyeCatch"> - <MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $ts._pages.eyeCatchingImageSet }}</MkButton> - <div v-else-if="eyeCatchingImage"> - <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/> - <MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton> - </div> + <div class="eyeCatch"> + <MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $ts._pages.eyeCatchingImageSet }}</MkButton> + <div v-else-if="eyeCatchingImage"> + <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/> + <MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton> </div> </div> - </MkContainer> + </div> + </MkContainer> - <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> - <template #header><Fa :icon="faStickyNote"/> {{ $ts._pages.contents }}</template> - <div class="_section"> - <XBlocks class="content" v-model:value="content" :hpml="hpml"/> + <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> + <template #header><Fa :icon="faStickyNote"/> {{ $ts._pages.contents }}</template> + <div style="padding: 16px;"> + <XBlocks class="content" v-model:value="content" :hpml="hpml"/> - <MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton> - </div> - </MkContainer> + <MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton> + </div> + </MkContainer> - <MkContainer :body-togglable="true" class="_vMargin"> - <template #header><Fa :icon="faMagic"/> {{ $ts._pages.variables }}</template> - <div class="qmuvgica"> - <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> - <template #item="{element}"> - <XVariable - :value="element" - :removable="true" - @remove="() => removeVariable(element)" - :hpml="hpml" - :name="element.name" - :title="element.name" - :draggable="true" - /> - </template> - </XDraggable> + <MkContainer :body-togglable="true" class="_vMargin"> + <template #header><Fa :icon="faMagic"/> {{ $ts._pages.variables }}</template> + <div class="qmuvgica"> + <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> + <template #item="{element}"> + <XVariable + :value="element" + :removable="true" + @remove="() => removeVariable(element)" + :hpml="hpml" + :name="element.name" + :title="element.name" + :draggable="true" + /> + </template> + </XDraggable> - <MkButton @click="addVariable()" class="add" v-if="!readonly"><Fa :icon="faPlus"/></MkButton> - </div> - </MkContainer> + <MkButton @click="addVariable()" class="add" v-if="!readonly"><Fa :icon="faPlus"/></MkButton> + </div> + </MkContainer> - <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> - <template #header><Fa :icon="faCode"/> {{ $ts.script }}</template> - <div> - <MkTextarea class="_code" v-model:value="script"/> - </div> - </MkContainer> - </div> + <MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> + <template #header><Fa :icon="faCode"/> {{ $ts.script }}</template> + <div> + <MkTextarea class="_code" v-model:value="script"/> + </div> + </MkContainer> </div> </template> diff --git a/src/client/pages/reversi/index.vue b/src/client/pages/reversi/index.vue index f40990b37c..b6a1db1161 100644 --- a/src/client/pages/reversi/index.vue +++ b/src/client/pages/reversi/index.vue @@ -259,7 +259,7 @@ export default defineComponent({ > footer { display: flex; align-items: baseline; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); padding: 6px 8px; font-size: 0.9em; diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue index e0c0b2995a..0d8e5b0ec3 100644 --- a/src/client/pages/timeline.vue +++ b/src/client/pages/timeline.vue @@ -1,39 +1,37 @@ <template> -<div class="cmuxhskf" v-hotkey.global="keymap"> +<div class="cmuxhskf _root" v-hotkey.global="keymap"> <div class="new" v-if="queue > 0" :style="{ width: width + 'px' }"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> - <div class="_section"> - <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _content _vMargin"/> - <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _panel _content _vMargin" fixed/> - <div class="tabs _panel _vMargin"> - <div class="left"> - <button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><Fa :icon="faHome"/></button> - <button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><Fa :icon="faComments"/></button> - <button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><Fa :icon="faShareAlt"/></button> - <button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><Fa :icon="faGlobe"/></button> - <span class="divider"></span> - <button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><Fa :icon="faAt"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadMentions"/></button> - <button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><Fa :icon="faEnvelope"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadSpecifiedNotes"/></button> - </div> - <div class="right"> - <button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><Fa :icon="faSatelliteDish"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadChannel"/></button> - <button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><Fa :icon="faSatellite"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadAntenna"/></button> - <button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><Fa :icon="faListUl"/></button> - </div> + <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/> + <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/> + <div class="tabs _block _vMargin"> + <div class="left"> + <button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><Fa :icon="faHome"/></button> + <button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><Fa :icon="faComments"/></button> + <button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><Fa :icon="faShareAlt"/></button> + <button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><Fa :icon="faGlobe"/></button> + <span class="divider"></span> + <button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><Fa :icon="faAt"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadMentions"/></button> + <button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><Fa :icon="faEnvelope"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadSpecifiedNotes"/></button> + </div> + <div class="right"> + <button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><Fa :icon="faSatelliteDish"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadChannel"/></button> + <button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><Fa :icon="faSatellite"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadAntenna"/></button> + <button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><Fa :icon="faListUl"/></button> </div> - <XTimeline ref="tl" - class="_content _vMargin" - :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src" - :src="src" - :list="list ? list.id : null" - :antenna="antenna ? antenna.id : null" - :channel="channel ? channel.id : null" - :sound="true" - @before="before()" - @after="after()" - @queue="queueUpdated" - /> </div> + <XTimeline ref="tl" + class="_vMargin" + :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src" + :src="src" + :list="list ? list.id : null" + :antenna="antenna ? antenna.id : null" + :channel="channel ? channel.id : null" + :sound="true" + @before="before()" + @after="after()" + @queue="queueUpdated" + /> </div> </template> @@ -200,6 +198,18 @@ export default defineComponent({ }); }, + async timetravel() { + const { canceled, result: date } = await os.dialog({ + title: this.$ts.date, + input: { + type: 'date' + } + }); + if (canceled) return; + + this.$refs.tl.timetravel(new Date(date)); + }, + focus() { (this.$refs.tl as any).focus(); } @@ -221,70 +231,65 @@ export default defineComponent({ } } - > ._section { - > .tabs { - display: flex; - box-sizing: border-box; - padding: 0 8px; - max-width: var(--baseContentWidth); + > .tabs { + display: flex; + box-sizing: border-box; + padding: 0 8px; + white-space: nowrap; + overflow: auto; + + // 影の都合上 + position: relative; + + > .right { margin-left: auto; - margin-right: auto; - white-space: nowrap; - overflow: auto; + } - // 影の都合上 - position: relative; + > .left, > .right { + > .tab { + position: relative; + height: 50px; + padding: 0 12px; - > .right { - margin-left: auto; + &:hover { + color: var(--fgHighlighted); + } + + &.active { + color: var(--fgHighlighted); + + &:after { + content: ""; + display: block; + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 auto; + width: calc(100% - 16px); + height: 4px; + background: var(--accent); + border-radius: 8px 8px 0 0; + } + } + + > .i { + position: absolute; + top: 16px; + right: 8px; + color: var(--indicator); + font-size: 8px; + animation: blink 1s infinite; + } } - > .left, > .right { - > .tab { - position: relative; - height: 50px; - padding: 0 12px; - - &:hover { - color: var(--fgHighlighted); - } - - &.active { - color: var(--fgHighlighted); - - &:after { - content: ""; - display: block; - position: absolute; - bottom: 0; - left: 0; - right: 0; - margin: 0 auto; - width: calc(100% - 16px); - height: 4px; - background: var(--accent); - border-radius: 8px 8px 0 0; - } - } - - > .i { - position: absolute; - top: 16px; - right: 8px; - color: var(--indicator); - font-size: 8px; - animation: blink 1s infinite; - } - } - - > .divider { - display: inline-block; - width: 1px; - height: 28px; - vertical-align: middle; - margin: 0 8px; - background: var(--divider); - } + > .divider { + display: inline-block; + width: 1px; + height: 28px; + vertical-align: middle; + margin: 0 8px; + background: var(--divider); } } } diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue index 8c824a2111..62f7f7d654 100644 --- a/src/client/pages/user/index.timeline.vue +++ b/src/client/pages/user/index.timeline.vue @@ -1,11 +1,11 @@ <template> <div> - <MkTab v-model:value="with_" class="_vMargin"> + <MkTab v-model:value="with_" class="_isolated _section"> <option :value="null">{{ $ts.notes }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> </MkTab> - <XNotes ref="timeline" class="_vMargin" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> + <XNotes ref="timeline" class="_section _noGap_" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> </div> </template> diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index 5ca29a3e41..18f78d270a 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -93,15 +93,15 @@ </div> </div> </div> - <div class="ftskorzw narrow _section" v-else-if="user && narrow === true" v-size="{ max: [500] }"> + <div class="ftskorzw narrow _root" v-else-if="user && narrow === true" v-size="{ max: [500] }"> <!-- TODO --> <!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $ts.userSuspended }}</div> --> <!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $ts.userSilenced }}</div> --> - <div class="profile _content _vMargin"> + <div class="profile"> <MkRemoteCaution v-if="user.host != null" :href="user.url" class="_vMargin"/> - <div class="_vMargin _panel main" :key="user.id"> + <div class="_vMargin _block main" :key="user.id"> <div class="banner-container" :style="style"> <div class="banner" ref="banner" :style="style"></div> <div class="fade"></div> @@ -177,37 +177,39 @@ </div> </div> - <div class="nav _vMargin"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> - <Fa :icon="faCommentAlt" class="icon"/> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link"> - <Fa :icon="faPaperclip" class="icon"/> - <span>{{ $ts.clips }}</span> - </MkA> - <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link"> - <Fa :icon="faFileAlt" class="icon"/> - <span>{{ $ts.pages }}</span> - </MkA> - </div> + <div class="contents"> + <div class="nav _isolated"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> + <Fa :icon="faCommentAlt" class="icon"/> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link"> + <Fa :icon="faPaperclip" class="icon"/> + <span>{{ $ts.clips }}</span> + </MkA> + <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link"> + <Fa :icon="faFileAlt" class="icon"/> + <span>{{ $ts.pages }}</span> + </MkA> + </div> - <template v-if="page === 'index'"> - <div class="_content _vMargin"> - <div v-if="user.pinnedNotes.length > 0" class="_vMargin"> - <XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + <template v-if="page === 'index'"> + <div> + <div v-if="user.pinnedNotes.length > 0"> + <XNote v-for="note in user.pinnedNotes" class="note _block _isolated" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + </div> + <XPhotos :user="user" :key="user.id"/> + <XActivity :user="user" :key="user.id"/> </div> - <XPhotos :user="user" :key="user.id" class="_vMargin"/> - <XActivity :user="user" :key="user.id" class="_vMargin"/> - </div> - <div class="_content _vMargin"> - <XUserTimeline :user="user" class="_content"/> - </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _vMargin"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _vMargin"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_vMargin"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_vMargin"/> + <div> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _vMargin"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _vMargin"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_vMargin"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_vMargin"/> + </div> </div> <div v-else-if="error"> <MkError @retry="fetch()"/> @@ -234,6 +236,7 @@ import { getUserMenu } from '@client/scripts/get-user-menu'; import number from '../../filters/number'; import { userPage, acct as getAcct } from '../../filters/user'; import * as os from '@client/os'; +import { url } from '@client/config'; export default defineComponent({ components: { @@ -268,6 +271,10 @@ export default defineComponent({ INFO: computed(() => this.user ? { userName: this.user, avatar: this.user, + share: { + title: this.user.name, + url: `${url}/@${this.user.username}` + }, action: { icon: faEllipsisH, handler: this.menu @@ -304,7 +311,7 @@ export default defineComponent({ mounted() { window.requestAnimationFrame(this.parallaxLoop); - this.narrow = this.$el.clientWidth < 1000; + this.narrow = true; //this.$el.clientWidth < 1000; }, beforeUnmount() { @@ -415,7 +422,7 @@ export default defineComponent({ font-size: 80%; padding: 8px 12px; margin-bottom: 20px; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 999px; } } @@ -423,7 +430,7 @@ export default defineComponent({ > .status { display: flex; padding: 20px 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); font-size: 90%; > a { @@ -451,13 +458,13 @@ export default defineComponent({ > .description { padding: 20px 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); font-size: 90%; } > .fields { padding: 20px 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); font-size: 90%; > .field { @@ -540,9 +547,9 @@ export default defineComponent({ } .ftskorzw.narrow { - max-width: 100vw; box-sizing: border-box; overflow: hidden; + overflow: clip; > .punished { font-size: 0.8em; @@ -654,7 +661,7 @@ export default defineComponent({ text-align: center; padding: 50px 8px 16px 8px; font-weight: bold; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); > .bottom { > * { @@ -689,7 +696,7 @@ export default defineComponent({ > .fields { padding: 24px; font-size: 0.9em; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); > .field { display: flex; @@ -726,7 +733,7 @@ export default defineComponent({ > .status { display: flex; padding: 24px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); > a { flex: 1; @@ -753,41 +760,42 @@ export default defineComponent({ } } - > .nav { - display: flex; - align-items: center; - margin-top: var(--margin); - //font-size: 120%; - font-weight: bold; + > .contents { + > .nav { + display: flex; + align-items: center; + //font-size: 120%; + font-weight: bold; - > .link { - flex: 1; - display: inline-block; - padding: 16px; - text-align: center; - border-bottom: solid 3px transparent; + > .link { + flex: 1; + display: inline-block; + padding: 16px; + text-align: center; + border-bottom: solid 3px transparent; - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; + } - &.active { - color: var(--accent); - border-bottom-color: var(--accent); - } + &.active { + color: var(--accent); + border-bottom-color: var(--accent); + } - &:not(.active):hover { - color: var(--fgHighlighted); - } + &:not(.active):hover { + color: var(--fgHighlighted); + } - > .icon { - margin-right: 6px; + > .icon { + margin-right: 6px; + } } } - } - > .content { - margin-bottom: var(--margin); + > .content { + margin-bottom: var(--margin); + } } &.max-width_500px { @@ -831,8 +839,10 @@ export default defineComponent({ } } - > .nav { - font-size: 80%; + > .contents { + > .nav { + font-size: 80%; + } } } } diff --git a/src/client/pages/welcome.entrance.a.vue b/src/client/pages/welcome.entrance.a.vue index 01b3038312..7b02c44923 100644 --- a/src/client/pages/welcome.entrance.a.vue +++ b/src/client/pages/welcome.entrance.a.vue @@ -283,7 +283,7 @@ export default defineComponent({ } > .status { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); padding: 32px; font-size: 90%; @@ -291,7 +291,7 @@ export default defineComponent({ > span:not(:last-child) { padding-right: 1em; margin-right: 1em; - border-right: solid 1px var(--divider); + border-right: solid 0.5px var(--divider); } } diff --git a/src/client/pages/welcome.entrance.c.vue b/src/client/pages/welcome.entrance.c.vue index 4e4c0ed210..47ddf9e5ed 100644 --- a/src/client/pages/welcome.entrance.c.vue +++ b/src/client/pages/welcome.entrance.c.vue @@ -255,7 +255,7 @@ export default defineComponent({ } > .status { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); padding: 32px; font-size: 90%; @@ -263,7 +263,7 @@ export default defineComponent({ > span:not(:last-child) { padding-right: 1em; margin-right: 1em; - border-right: solid 1px var(--divider); + border-right: solid 0.5px var(--divider); } } diff --git a/src/client/scripts/sticky-sidebar.ts b/src/client/scripts/sticky-sidebar.ts index 9d46a7831f..18670bc037 100644 --- a/src/client/scripts/sticky-sidebar.ts +++ b/src/client/scripts/sticky-sidebar.ts @@ -1,40 +1,45 @@ export class StickySidebar { private lastScrollTop = 0; + private container: HTMLElement; private el: HTMLElement; private spacer: HTMLElement; private marginTop: number; private isTop = false; private isBottom = false; + private offsetTop: number; - constructor(el: StickySidebar['el'], spacer: StickySidebar['spacer'], marginTop = 0) { - this.el = el; - this.spacer = spacer; + constructor(container: StickySidebar['container'], marginTop = 0) { + this.container = container; + this.el = this.container.children[0] as HTMLElement; + this.el.style.position = 'sticky'; + this.spacer = document.createElement('div'); + this.container.prepend(this.spacer); this.marginTop = marginTop; + this.offsetTop = this.container.getBoundingClientRect().top; } public calc(scrollTop: number) { if (scrollTop > this.lastScrollTop) { // downscroll - const overflow = this.el.clientHeight - window.innerHeight; + const overflow = Math.max(0, (this.el.clientHeight + this.marginTop) - window.innerHeight); this.el.style.bottom = null; - this.el.style.top = `${-overflow}px`; + this.el.style.top = `${-overflow + this.marginTop}px`; this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight); if (this.isTop) { this.isTop = false; - this.spacer.style.marginTop = `${this.lastScrollTop}px`; + this.spacer.style.marginTop = `${Math.max(0, this.lastScrollTop + this.marginTop - this.offsetTop)}px`; } } else { // upscroll - const overflow = this.el.clientHeight - window.innerHeight; + const overflow = (this.el.clientHeight + this.marginTop) - window.innerHeight; this.el.style.top = null; - this.el.style.bottom = `${-overflow - this.marginTop}px`; + this.el.style.bottom = `${-overflow}px`; this.isTop = scrollTop <= this.el.offsetTop; if (this.isBottom) { this.isBottom = false; - const overflow = this.el.clientHeight - window.innerHeight; - this.spacer.style.marginTop = `${this.lastScrollTop - (overflow + this.marginTop)}px`; + this.spacer.style.marginTop = `${this.lastScrollTop + this.marginTop - this.offsetTop - overflow}px`; } } diff --git a/src/client/scripts/theme.ts b/src/client/scripts/theme.ts index c1580c6367..804d808683 100644 --- a/src/client/scripts/theme.ts +++ b/src/client/scripts/theme.ts @@ -17,6 +17,7 @@ export const themeProps = Object.keys(lightTheme.props).filter(key => !key.start export const builtinThemes = [ require('../themes/l-light.json5'), require('../themes/l-apricot.json5'), + require('../themes/l-rainy.json5'), require('../themes/d-dark.json5'), require('../themes/d-persimmon.json5'), diff --git a/src/client/sidebar.ts b/src/client/sidebar.ts index 97036042cf..09b69fd815 100644 --- a/src/client/sidebar.ts +++ b/src/client/sidebar.ts @@ -142,6 +142,12 @@ export const sidebarDef = { localStorage.setItem('ui', 'deck'); unisonReload(); } + }, { + text: 'pope', + action: () => { + localStorage.setItem('ui', 'pope'); + unisonReload(); + } }, { text: 'Chat (β)', action: () => { diff --git a/src/client/style.scss b/src/client/style.scss index b6a83d967e..f2a3682e47 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -242,11 +242,21 @@ hr { //border: var(--panelBorder); box-shadow: var(--panelShadow); overflow: hidden; + overflow: clip; +} + +._block { + @extend ._panel; +} + +._isolated { + margin: var(--margin) 0; } ._card { @extend ._panel; + // TODO: _cardTitle に > ._title { margin: 0; padding: 22px 32px; @@ -262,6 +272,7 @@ hr { } } + // TODO: _cardContent に > ._content { padding: 32px; @@ -274,12 +285,13 @@ hr { } & + ._content { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } + // TODO: _cardFooter に > ._footer { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); padding: 24px 32px; @media (max-width: 500px) { @@ -288,26 +300,6 @@ hr { } } -._noGap_ ._list_ { - @extend ._panel; - - > * { - margin: 0 !important; - border: none; - border-bottom: solid 1px var(--divider); - border-radius: 0; - box-shadow: none; - } -} - -._inContainer_ ._list_ > * { - margin: 0 !important; - border: none; - border-bottom: solid 1px var(--divider); - border-radius: 0; - box-shadow: none; -} - ._borderButton { @extend ._button; display: block; @@ -315,7 +307,7 @@ hr { padding: 10px; box-sizing: border-box; text-align: center; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: var(--radius); &:active { @@ -329,47 +321,36 @@ hr { contain: content; } -._section { - padding: var(--section-padding, 32px); +._root { + box-sizing: border-box; + margin: var(--root-margin, 32px) auto; + max-width: min(var(--baseContentWidth), calc(100% - (var(--root-margin, 32px) * 2))); - &:empty { - display: none; - } - - &:not(:empty) + ._section { - border-top: solid 1px var(--divider); + ._hr { + margin: var(--margin) calc(var(--root-margin, 32px) * -1); } @media (max-width: 500px) { - padding: var(--section-padding, 10px); - - > ._title { - font-size: 1.1em; - font-weight: bold; - } - } - - > ._title, - > ._content { - box-sizing: border-box; - max-width: var(--baseContentWidth); - margin: 0 auto; - } - - > ._title { - margin-bottom: 24px; - font-weight: bold; - } - - &._fitBottom { - padding-bottom: 0; + --root-margin: 10px; } } -._narrow_ ._section { - > ._title { - padding: 8px; - font-size: 1em; +._flat_ { + --root-margin: 0; + --baseContentWidth: 100%; + --panelShadow: none; + + ._block { + //border-top: solid 0.5px var(--divider); + //border-bottom: solid 0.5px var(--divider); + border-radius: 0; + box-shadow: none; + } + + @media (max-width: 500px) { + ._root { + --root-margin: 0; + } } } diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5 index 2fa4853e6f..6414a7ad42 100644 --- a/src/client/themes/_dark.json5 +++ b/src/client/themes/_dark.json5 @@ -30,7 +30,7 @@ panelShadow: '" 0 8px 24px rgba(0, 0, 0, 0.12)', acrylicPanel: ':alpha<0.5<@panel', shadow: 'rgba(0, 0, 0, 0.3)', - header: ':alpha<0.7<@bg', + header: ':alpha<0.7<@panel', navBg: '@panel', navFg: '@fg', navHoverFg: ':lighten<17<@fg', diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5 index 94e6977502..0438b54a49 100644 --- a/src/client/themes/_light.json5 +++ b/src/client/themes/_light.json5 @@ -30,7 +30,7 @@ panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)', acrylicPanel: ':alpha<0.5<@panel', shadow: 'rgba(0, 0, 0, 0.1)', - header: ':alpha<0.7<@bg', + header: ':alpha<0.7<@panel', navBg: '@panel', navFg: '@fg', navHoverFg: ':darken<17<@fg', diff --git a/src/client/themes/d-dark.json5 b/src/client/themes/d-dark.json5 index 7dd29b4a0f..337eaa6396 100644 --- a/src/client/themes/d-dark.json5 +++ b/src/client/themes/d-dark.json5 @@ -18,7 +18,7 @@ panelHeaderDivider: '@divider', infoFg: '@accent', infoBg: 'rgb(0, 0, 0)', - header: ':alpha<0.7<@bg', + header: ':alpha<0.7<@panel', navBg: '#363636', renote: '@accent', mention: '#da6d35', diff --git a/src/client/themes/d-persimmon.json5 b/src/client/themes/d-persimmon.json5 index 862ccc6cea..a1ebaf59eb 100644 --- a/src/client/themes/d-persimmon.json5 +++ b/src/client/themes/d-persimmon.json5 @@ -16,7 +16,6 @@ panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)', infoFg: '@fg', infoBg: '#333c3b', - header: ':alpha<0.7<@bg', navBg: '#141714', renote: '@accent', mention: '@accent', diff --git a/src/client/themes/l-light.json5 b/src/client/themes/l-light.json5 index 34be20fae0..fdc1700b95 100644 --- a/src/client/themes/l-light.json5 +++ b/src/client/themes/l-light.json5 @@ -9,9 +9,9 @@ props: { bg: '#f9f9f9', - fg: '#636b71', + fg: '#676767', divider: 'rgb(223, 223, 223)', - header: ':alpha<0.7<@bg', + header: ':alpha<0.7<@panel', navBg: '#fff', panel: '#fff', panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)', diff --git a/src/client/themes/l-rainy.json5 b/src/client/themes/l-rainy.json5 new file mode 100644 index 0000000000..1edde1cabf --- /dev/null +++ b/src/client/themes/l-rainy.json5 @@ -0,0 +1,21 @@ +{ + id: 'a58a0abb-ff8c-476a-8dec-0ad7837e7e96', + + name: 'Mi Rainy', + author: 'syuilo', + + base: 'light', + + props: { + accent: '#5db0da', + bg: 'rgb(246 248 249)', + fg: '#636b71', + panel: '#fff', + divider: 'rgb(230 233 234)', + panelHeaderDivider: '@divider', + renote: '@accent', + link: '@accent', + mention: '@accent', + hashtag: '@accent', + }, +} diff --git a/src/client/ui/_common_/header.vue b/src/client/ui/_common_/header.vue index f150653a84..4c914edbbd 100644 --- a/src/client/ui/_common_/header.vue +++ b/src/client/ui/_common_/header.vue @@ -12,14 +12,16 @@ <MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/> </div> </div> - <button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button> + <button class="_button menu" @click.stop="menu"><Fa :icon="faEllipsisH"/></button> + <!--<button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button>--> </template> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import { faChevronLeft, faCircle } from '@fortawesome/free-solid-svg-icons'; +import { faChevronLeft, faCircle, faShareAlt, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; +import { modalMenu } from '@client/os'; export default defineComponent({ props: { @@ -42,7 +44,7 @@ export default defineComponent({ return { canBack: false, height: 0, - faChevronLeft, faCircle + faChevronLeft, faCircle, faShareAlt, faEllipsisH, }; }, @@ -66,6 +68,23 @@ export default defineComponent({ back() { if (this.canBack) this.$router.back(); }, + + share() { + navigator.share(this.info.share); + }, + + menu(ev) { + const menu = this.info.menu ? this.info.menu() : []; + if (this.info.share) { + if (menu.length > 0) menu.push(null); + menu.push({ + text: this.$ts.share, + icon: faShareAlt, + action: this.share + }); + } + modalMenu(menu, ev.currentTarget || ev.target); + } } }); </script> @@ -74,59 +93,33 @@ export default defineComponent({ .fdidabkb { &.center { text-align: center; - } - > .back { - height: var(--height); - width: var(--height); - } - - > .action { - height: var(--height); - width: var(--height); - } - - > .titleContainer { - width: calc(100% - (var(--height) * 2)); - - > .title { - height: var(--height); - - > .avatar { - $size: 32px; - margin: calc((var(--height) - #{$size}) / 2) 8px calc((var(--height) - #{$size}) / 2) 0; - pointer-events: none; - } - } - } -} -</style> - -<style lang="scss" scoped> -.fdidabkb { - > .back { - position: absolute; - z-index: 1; - top: 0; - left: 0; - } - - > .action { - position: absolute; - z-index: 1; - top: 0; - right: 0; - } - - &.center { > .titleContainer { margin: 0 auto; } } + > .back, + > .menu { + position: absolute; + z-index: 1; + top: 0; + height: var(--height); + width: var(--height); + } + + > .back { + left: 0; + } + + > .menu { + right: 0; + } + > .titleContainer { overflow: auto; white-space: nowrap; + width: calc(100% - (var(--height) * 2)); > .title { display: inline-block; @@ -136,16 +129,7 @@ export default defineComponent({ text-overflow: ellipsis; padding: 0 16px; position: relative; - - > .indicator { - position: absolute; - top: initial; - right: 8px; - top: 8px; - color: var(--indicator); - font-size: 12px; - animation: blink 1s infinite; - } + height: var(--height); > .icon + .text { margin-left: 8px; @@ -157,6 +141,8 @@ export default defineComponent({ width: $size; height: $size; vertical-align: bottom; + margin: calc((var(--height) - #{$size}) / 2) 8px calc((var(--height) - #{$size}) / 2) 0; + pointer-events: none; } } } diff --git a/src/client/components/sidebar.vue b/src/client/ui/_common_/sidebar.vue similarity index 96% rename from src/client/components/sidebar.vue rename to src/client/ui/_common_/sidebar.vue index 61439781b4..6243d6fcc2 100644 --- a/src/client/components/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -235,12 +235,12 @@ export default defineComponent({ }, more(ev) { - os.popup(import('./launch-pad.vue'), {}, { + os.popup(import('@client/components/launch-pad.vue'), {}, { }, 'closed'); }, addAcount() { - os.popup(import('./signin-dialog.vue'), {}, { + os.popup(import('@client/components/signin-dialog.vue'), {}, { done: res => { addAccount(res.id, res.i); os.success(); @@ -249,7 +249,7 @@ export default defineComponent({ }, createAccount() { - os.popup(import('./signup-dialog.vue'), {}, { + os.popup(import('@client/components/signup-dialog.vue'), {}, { done: res => { addAccount(res.id, res.i); this.switchAccountWithToken(res.i); @@ -380,7 +380,7 @@ export default defineComponent({ > .divider { margin: 16px 0; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } > .item { @@ -443,13 +443,13 @@ export default defineComponent({ &:first-child { top: 0; margin-bottom: 16px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } &:last-child { bottom: 0; margin-top: 16px; - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } } diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue index 91d3fb5c9d..d5c455d123 100644 --- a/src/client/ui/chat/index.vue +++ b/src/client/ui/chat/index.vue @@ -136,7 +136,7 @@ import { defineComponent, defineAsyncComponent } from 'vue'; import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, faAt, faLink, faEllipsisH, faGlobe } from '@fortawesome/free-solid-svg-icons'; import { faBell, faStar as farStar, faEnvelope, faComments, faCalendarAlt } from '@fortawesome/free-regular-svg-icons'; import { instanceName, url } from '@client/config'; -import XSidebar from '@client/components/sidebar.vue'; +import XSidebar from '@client/ui/_common_/sidebar.vue'; import XWidgets from './widgets.vue'; import XCommon from '../_common_/common.vue'; import XSide from './side.vue'; @@ -354,7 +354,7 @@ export default defineComponent({ flex-direction: column; width: 250px; height: 100vh; - border-right: solid 1px var(--divider); + border-right: solid 0.5px var(--divider); > .header, > .footer { $padding: 8px; @@ -367,11 +367,11 @@ export default defineComponent({ user-select: none; &.header { - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } &.footer { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } > .left, > .right { @@ -526,7 +526,7 @@ export default defineComponent({ padding: $padding; box-sizing: border-box; background-color: var(--panel); - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); user-select: none; > .left { @@ -599,7 +599,7 @@ export default defineComponent({ > .side { width: 350px; - border-left: solid 1px var(--divider); + border-left: solid 0.5px var(--divider); &.widgets.sideViewOpening { @media (max-width: 1400px) { diff --git a/src/client/ui/chat/note-header.vue b/src/client/ui/chat/note-header.vue index 55228c4c38..be08183d39 100644 --- a/src/client/ui/chat/note-header.vue +++ b/src/client/ui/chat/note-header.vue @@ -79,7 +79,7 @@ export default defineComponent({ margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 3px; } diff --git a/src/client/ui/chat/note.sub.vue b/src/client/ui/chat/note.sub.vue index 6c778d1468..bb528dd936 100644 --- a/src/client/ui/chat/note.sub.vue +++ b/src/client/ui/chat/note.sub.vue @@ -130,7 +130,7 @@ export default defineComponent({ } > .reply { - border-left: solid 1px var(--divider); + border-left: solid 0.5px var(--divider); margin-top: 10px; } } diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue index f6789f214d..3764bbb65a 100644 --- a/src/client/ui/chat/note.vue +++ b/src/client/ui/chat/note.vue @@ -1127,7 +1127,7 @@ export default defineComponent({ } > .reply { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } } diff --git a/src/client/ui/chat/post-form.vue b/src/client/ui/chat/post-form.vue index 9dd87edac4..bdf18cf290 100644 --- a/src/client/ui/chat/post-form.vue +++ b/src/client/ui/chat/post-form.vue @@ -615,7 +615,7 @@ export default defineComponent({ <style lang="scss" scoped> .pxiwixjf { position: relative; - border: solid 1px var(--divider); + border: solid 0.5px var(--divider); border-radius: 8px; > .form { @@ -696,7 +696,7 @@ export default defineComponent({ > .cw { z-index: 1; padding-bottom: 8px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } > .text { diff --git a/src/client/ui/chat/side.vue b/src/client/ui/chat/side.vue index 2645874ce4..a3c03b6d06 100644 --- a/src/client/ui/chat/side.vue +++ b/src/client/ui/chat/side.vue @@ -117,7 +117,7 @@ export default defineComponent({ .mrajymqm { $header-height: 54px; // TODO: どこかに集約したい - --section-padding: 16px; + --root-margin: 16px; --margin: var(--marginHalf); height: 100%; @@ -137,7 +137,7 @@ export default defineComponent({ -webkit-backdrop-filter: blur(32px); backdrop-filter: blur(32px); background-color: var(--header); - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); box-sizing: border-box; > ._button { diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue index a63db17b01..0429dbc9b1 100644 --- a/src/client/ui/deck.vue +++ b/src/client/ui/deck.vue @@ -36,7 +36,7 @@ import { } from '@fortawesome/free-regular-svg-icons'; import { v4 as uuid } from 'uuid'; import { host } from '@client/config'; import DeckColumnCore from '@client/ui/deck/column-core.vue'; -import XSidebar from '@client/components/sidebar.vue'; +import XSidebar from '@client/ui/_common_/sidebar.vue'; import { getScrollContainer } from '@client/scripts/scroll'; import * as os from '@client/os'; import { sidebarDef } from '@client/sidebar'; diff --git a/src/client/ui/deck/column.vue b/src/client/ui/deck/column.vue index 6a242c691a..3fae7c27ee 100644 --- a/src/client/ui/deck/column.vue +++ b/src/client/ui/deck/column.vue @@ -265,7 +265,7 @@ export default defineComponent({ <style lang="scss" scoped> .dnpfarvg { - --section-padding: 10px; + --root-margin: 10px; height: 100%; overflow: hidden; diff --git a/src/client/ui/deck/main-column.vue b/src/client/ui/deck/main-column.vue index 4577b0b533..5a8c72d871 100644 --- a/src/client/ui/deck/main-column.vue +++ b/src/client/ui/deck/main-column.vue @@ -4,7 +4,7 @@ <XHeader :info="pageInfo"/> </template> - <router-view v-slot="{ Component }"> + <router-view v-slot="{ Component }" class="_flat_"> <transition> <keep-alive :include="['timeline']"> <component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/> diff --git a/src/client/ui/default.side.vue b/src/client/ui/default.side.vue index 995f987a6a..3a32cb4e13 100644 --- a/src/client/ui/default.side.vue +++ b/src/client/ui/default.side.vue @@ -118,7 +118,7 @@ export default defineComponent({ .qvzfzxam { $header-height: 58px; // TODO: どこかに集約したい - --section-padding: 16px; + --root-margin: 16px; --margin: var(--marginHalf); > .container { diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue new file mode 100644 index 0000000000..710a9b1f85 --- /dev/null +++ b/src/client/ui/default.sidebar.vue @@ -0,0 +1,362 @@ +<template> +<div class="npcljfve" :class="{ iconOnly }"> + <button class="item _button account" @click="openAccountMenu"> + <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> + </button> + <div class="post" @click="post"> + <MkButton class="button" primary full> + <Fa :icon="faPencilAlt" fixed-width/><span class="text" v-if="!iconOnly">{{ $ts.note }}</span> + </MkButton> + </div> + <div class="divider"></div> + <MkA class="item index" active-class="active" to="/" exact> + <Fa :icon="faHome" fixed-width/><span class="text">{{ $ts.timeline }}</span> + </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 ? '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">{{ $ts[menuDef[item].title] }}</span> + <i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i> + </component> + </template> + <div class="divider"></div> + <button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$i.isAdmin || $i.isModerator" @click="oepnInstanceMenu"> + <Fa :icon="faServer" fixed-width/><span class="text">{{ $ts.instance }}</span> + </button> + <button class="item _button" @click="more"> + <Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $ts.more }}</span> + <i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i> + </button> + <MkA class="item" active-class="active" to="/settings"> + <Fa :icon="faCog" fixed-width/><span class="text">{{ $ts.settings }}</span> + </MkA> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; +import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons'; +import { host } from '@client/config'; +import { search } from '@client/scripts/search'; +import * as os from '@client/os'; +import { sidebarDef } from '@client/sidebar'; +import { getAccounts, addAccount, login } from '@client/account'; +import MkButton from '@client/components/ui/button.vue'; +import { StickySidebar } from '@client/scripts/sticky-sidebar'; + +export default defineComponent({ + components: { + MkButton + }, + + data() { + return { + host: host, + accounts: [], + connection: null, + menuDef: sidebarDef, + iconOnly: false, + faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram + }; + }, + + computed: { + menu(): string[] { + return this.$store.state.menu; + }, + + otherNavItemIndicated(): boolean { + for (const def in this.menuDef) { + if (this.menu.includes(def)) continue; + if (this.menuDef[def].indicated) return true; + } + return false; + }, + }, + + watch: { + '$store.reactiveState.sidebarDisplay.value'() { + this.calcViewState(); + }, + + iconOnly() { + this.$nextTick(() => { + this.$emit('change-view-mode'); + }); + }, + }, + + created() { + window.addEventListener('resize', this.calcViewState); + this.calcViewState(); + }, + + mounted() { + const sticky = new StickySidebar(this.$el.parentElement, 16); + window.addEventListener('scroll', () => { + sticky.calc(window.scrollY); + }, { passive: true }); + }, + + methods: { + calcViewState() { + this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon'); + }, + + post() { + os.post(); + }, + + search() { + search(); + }, + + async openAccountMenu(ev) { + const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); + const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); + + const accountItemPromises = storedAccounts.map(a => new Promise(res => { + accountsPromise.then(accounts => { + const account = accounts.find(x => x.id === a.id); + if (account == null) return res(null); + res({ + type: 'user', + user: account, + action: () => { this.switchAccount(account); } + }); + }); + })); + + os.modalMenu([...[{ + type: 'link', + text: this.$ts.profile, + to: `/@${ this.$i.username }`, + avatar: this.$i, + }, null, ...accountItemPromises, { + icon: faPlus, + text: this.$ts.addAcount, + action: () => { + os.modalMenu([{ + text: this.$ts.existingAcount, + action: () => { this.addAcount(); }, + }, { + text: this.$ts.createAccount, + action: () => { this.createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); + }, + + oepnInstanceMenu(ev) { + os.modalMenu([{ + type: 'link', + text: this.$ts.dashboard, + to: '/instance', + icon: faTachometerAlt, + }, null, this.$i.isAdmin ? { + type: 'link', + text: this.$ts.settings, + to: '/instance/settings', + icon: faCog, + } : undefined, { + type: 'link', + text: this.$ts.customEmojis, + to: '/instance/emojis', + icon: faLaugh, + }, { + type: 'link', + text: this.$ts.users, + to: '/instance/users', + icon: faUsers, + }, { + type: 'link', + text: this.$ts.files, + to: '/instance/files', + icon: faCloud, + }, { + type: 'link', + text: this.$ts.jobQueue, + to: '/instance/queue', + icon: faExchangeAlt, + }, { + type: 'link', + text: this.$ts.federation, + to: '/instance/federation', + icon: faGlobe, + }, { + type: 'link', + text: this.$ts.relays, + to: '/instance/relays', + icon: faProjectDiagram, + }, { + type: 'link', + text: this.$ts.announcements, + to: '/instance/announcements', + icon: faBroadcastTower, + }, { + type: 'link', + text: this.$ts.abuseReports, + to: '/instance/abuses', + icon: faExclamationCircle, + }, { + type: 'link', + text: this.$ts.logs, + to: '/instance/logs', + icon: faStream, + }], ev.currentTarget || ev.target); + }, + + more(ev) { + os.popup(import('@client/components/launch-pad.vue'), {}, { + }, 'closed'); + }, + + addAcount() { + os.popup(import('@client/components/signin-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + os.success(); + }, + }, 'closed'); + }, + + createAccount() { + os.popup(import('@client/components/signup-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + this.switchAccountWithToken(res.i); + }, + }, 'closed'); + }, + + switchAccount(account: any) { + const storedAccounts = getAccounts(); + const token = storedAccounts.find(x => x.id === account.id).token; + this.switchAccountWithToken(token); + }, + + switchAccountWithToken(token: string) { + login(token); + }, + } +}); +</script> + +<style lang="scss" scoped> +.npcljfve { + $ui-font-size: 1em; // TODO: どこかに集約したい + $nav-icon-only-width: 78px; // TODO: どこかに集約したい + $avatar-size: 32px; + $avatar-margin: 8px; + + padding: 0 16px; + box-sizing: border-box; + width: 260px; + + &.iconOnly { + flex: 0 0 $nav-icon-only-width; + width: $nav-icon-only-width !important; + + > .divider { + margin: 8px auto; + width: calc(100% - 32px); + } + + > .post { + > .button { + width: 46px; + height: 46px; + padding: 0; + } + } + + > .item { + padding-left: 0; + width: 100%; + text-align: center; + font-size: $ui-font-size * 1.1; + line-height: 3.7rem; + + > [data-icon], + > .avatar { + margin-right: 0; + } + + > i { + left: 10px; + } + + > .text { + display: none; + } + + } + } + + > .divider { + margin: 10px 0; + border-top: solid 0.5px var(--divider); + } + + > .post { + position: sticky; + top: 0; + z-index: 1; + padding: 16px 0; + background: var(--bg); + + > .button { + min-width: 0; + } + } + + > .item { + position: relative; + display: block; + font-size: $ui-font-size; + line-height: 2.6rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + + > [data-icon] { + width: 32px; + } + + > [data-icon], + > .avatar { + margin-right: $avatar-margin; + } + + > .avatar { + width: $avatar-size; + height: $avatar-size; + vertical-align: middle; + } + + > i { + position: absolute; + top: 0; + left: 20px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } + + &.active { + color: var(--navActive); + } + } +} +</style> diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue index 38f98f6365..84d6c01094 100644 --- a/src/client/ui/default.vue +++ b/src/client/ui/default.vue @@ -1,13 +1,15 @@ <template> -<div class="mk-app" :class="{ wallpaper }"> - <XSidebar ref="nav" class="sidebar"/> +<div class="mk-app" :class="{ wallpaper, isMobile }"> + <div class="columns"> + <div class="sidebar" ref="sidebar" v-if="!isMobile"> + <XSidebar/> + </div> - <div class="contents" ref="contents" :class="{ withHeader: $store.state.titlebar }" @contextmenu.stop="onContextmenu"> - <header v-if="$store.state.titlebar" class="header" ref="header" @click="onHeaderClick"> - <XHeader :info="pageInfo"/> - </header> - <main ref="main"> - <div class="content"> + <main class="main _panel" @contextmenu.stop="onContextmenu"> + <header v-if="$store.state.titlebar" class="header" @click="onHeaderClick"> + <XHeader :info="pageInfo"/> + </header> + <div class="content _flat_"> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> <keep-alive :include="['timeline']"> @@ -16,26 +18,22 @@ </transition> </router-view> </div> - <div class="spacer"></div> </main> + + <div v-if="isDesktop" class="widgets" ref="widgets"> + <XWidgets @mounted="attachSticky"/> + </div> </div> - <XSide v-if="isDesktop" class="side" ref="side"/> - - <div v-if="isDesktop" class="widgets"> - <div ref="widgetsSpacer"></div> - <XWidgets @mounted="attachSticky"/> - </div> - - <div class="buttons" :class="{ navHidden }"> - <button class="button nav _button" @click="showNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button> + <div class="buttons" v-if="isMobile"> + <button class="button nav _button" @click="showDrawerNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button> <button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><Fa :icon="faHome"/></button> <button class="button notifications _button" @click="$router.push('/my/notifications')"><Fa :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></button> <button class="button widget _button" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button> <button class="button post _button" @click="post"><Fa :icon="faPencilAlt"/></button> </div> - <button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button> + <XDrawerSidebar ref="drawerNav" class="sidebar" v-if="isMobile"/> <transition name="tray-back"> <div class="tray-back _modalBg" @@ -59,38 +57,31 @@ import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, fa import { faBell } from '@fortawesome/free-regular-svg-icons'; import { instanceName } from '@client/config'; import { StickySidebar } from '@client/scripts/sticky-sidebar'; -import XSidebar from '@client/components/sidebar.vue'; +import XSidebar from './default.sidebar.vue'; +import XDrawerSidebar from '@client/ui/_common_/sidebar.vue'; import XCommon from './_common_/common.vue'; import XHeader from './_common_/header.vue'; -import XSide from './default.side.vue'; import * as os from '@client/os'; import { sidebarDef } from '@client/sidebar'; const DESKTOP_THRESHOLD = 1100; +const MOBILE_THRESHOLD = 600; export default defineComponent({ components: { XCommon, XSidebar, + XDrawerSidebar, XHeader, XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')), - XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる - }, - - provide() { - return { - sideViewHook: this.isDesktop ? (url) => { - this.$refs.side.navigate(url); - } : null - }; }, data() { return { pageInfo: null, - isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, menuDef: sidebarDef, - navHidden: false, + isMobile: window.innerWidth <= MOBILE_THRESHOLD, + isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, widgetsShowing: false, wallpaper: localStorage.getItem('wallpaper') != null, faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, @@ -125,21 +116,10 @@ export default defineComponent({ }, mounted() { - this.adjustUI(); - - const ro = new ResizeObserver((entries, observer) => { - this.adjustUI(); - }); - - ro.observe(this.$refs.contents); - - window.addEventListener('resize', this.adjustUI, { passive: true }); - - if (!this.isDesktop) { - window.addEventListener('resize', () => { - if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true; - }, { passive: true }); - } + window.addEventListener('resize', () => { + this.isMobile = (window.innerWidth <= MOBILE_THRESHOLD); + this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD); + }, { passive: true }); }, methods: { @@ -151,20 +131,8 @@ export default defineComponent({ } }, - adjustUI() { - const navWidth = this.$refs.nav.$el.offsetWidth; - this.navHidden = navWidth === 0; - if (this.$refs.contents == null) return; - const width = this.$refs.contents.offsetWidth; - if (this.$refs.header) this.$refs.header.style.width = `${width}px`; - }, - - showNav() { - this.$refs.nav.show(); - }, - - attachSticky(el) { - const sticky = new StickySidebar(el, this.$refs.widgetsSpacer); + attachSticky() { + const sticky = new StickySidebar(this.$refs.widgets, 16); window.addEventListener('scroll', () => { sticky.calc(window.scrollY); }, { passive: true }); @@ -178,6 +146,10 @@ export default defineComponent({ window.scroll({ top: 0, behavior: 'smooth' }); }, + showDrawerNav() { + this.$refs.drawerNav.show(); + }, + onTransition() { if (window._scroll) window._scroll(); }, @@ -200,12 +172,6 @@ export default defineComponent({ os.contextMenu([{ type: 'label', text: path, - }, { - icon: faColumns, - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(path); - } }, { icon: faWindowMaximize, text: this.$ts.openInWindow, @@ -242,99 +208,98 @@ export default defineComponent({ } .mk-app { - $header-height: 58px; // TODO: どこかに集約したい - $ui-font-size: 1em; // TODO: どこかに集約したい - $widgets-hide-threshold: 1090px; + $header-height: 50px; + $ui-font-size: 1em; + $widgets-hide-threshold: 1200px; + $nav-icon-only-width: 78px; // TODO: どこかに集約したい // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ min-height: calc(var(--vh, 1vh) * 100); box-sizing: border-box; - display: flex; &.wallpaper { background: var(--wallpaperOverlay); //backdrop-filter: blur(4px); } - > .contents { - width: 100%; - min-width: 0; + &.isMobile { + > .columns { + display: block; + margin: 0; - &.withHeader { - padding-top: $header-height; + > .main { + margin: 0; + border: none; + width: 100%; + border-radius: 0; + + > .header { + width: 100%; + } + } } + } - > .header { - position: fixed; - 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); - user-select: none; - } + > .columns { + display: flex; + justify-content: center; + max-width: 100%; + margin: 32px 0; - > main { - min-width: 0; + > .main { + width: 750px; + margin: 0 16px 0 0; + background: var(--bg); + --margin: 12px; + + > .header { + position: sticky; + z-index: 1000; + top: 0; + height: $header-height; + line-height: $header-height; + -webkit-backdrop-filter: blur(32px); + backdrop-filter: blur(32px); + background-color: var(--header); + border-bottom: solid 0.5px var(--divider); + } > .content { - > * { - // ほんとは単に calc(100vh - #{$header-height}) と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - min-height: calc((var(--vh, 1vh) * 100) - #{$header-height}); - } + background: var(--bg); + --stickyTop: #{$header-height}; } - > .spacer { - height: 82px; + @media (max-width: 850px) { + padding-top: $header-height; - @media (min-width: ($widgets-hide-threshold + 1px)) { - display: none; + > .header { + position: fixed; + width: calc(100% - #{$nav-icon-only-width}); } } } - } - > .side { - min-width: 370px; - max-width: 370px; - border-left: solid 1px var(--divider); - } + > .widgets { + //--panelShadow: none; - > .widgets { - padding: 0 var(--margin); - border-left: solid 1px var(--divider); - - @media (max-width: $widgets-hide-threshold) { - display: none; - } - } - - > .widgetButton { - display: block; - position: fixed; - z-index: 1000; - bottom: 32px; - right: 32px; - width: 64px; - height: 64px; - border-radius: 100%; - box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); - font-size: 22px; - background: var(--panel); - - &.navHidden { - display: none; + @media (max-width: $widgets-hide-threshold) { + display: none; + } } - @media (min-width: ($widgets-hide-threshold + 1px)) { - display: none; + @media (max-width: 850px) { + margin: 0; + + > .sidebar { + border-right: solid 0.5px var(--divider); + } + + > .main { + margin: 0; + border-radius: 0; + box-shadow: none; + width: 100%; + } } } @@ -349,10 +314,7 @@ export default defineComponent({ -webkit-backdrop-filter: blur(32px); backdrop-filter: blur(32px); background-color: var(--header); - - &:not(.navHidden) { - display: none; - } + border-top: solid 0.5px var(--divider); > .button { position: relative; @@ -429,6 +391,3 @@ export default defineComponent({ } } </style> - -<style lang="scss"> -</style> diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue index 35d3442bb2..b12de841a7 100644 --- a/src/client/ui/default.widgets.vue +++ b/src/client/ui/default.widgets.vue @@ -61,8 +61,6 @@ export default defineComponent({ .efzpzdvf { position: sticky; height: min-content; - min-height: 100vh; - padding: var(--margin) 0; box-sizing: border-box; > * { diff --git a/src/client/ui/desktop.vue b/src/client/ui/desktop.vue index 1480fd1840..a60aed6841 100644 --- a/src/client/ui/desktop.vue +++ b/src/client/ui/desktop.vue @@ -12,7 +12,7 @@ import { host } from '@client/config'; import { search } from '@client/scripts/search'; import XCommon from './_common_/common.vue'; import * as os from '@client/os'; -import XSidebar from '@client/components/sidebar.vue'; +import XSidebar from '@client/ui/_common_/sidebar.vue'; import { sidebarDef } from '@client/sidebar'; import { ColdDeviceStorage } from '@client/store'; diff --git a/src/client/ui/universal.vue b/src/client/ui/universal.vue new file mode 100644 index 0000000000..e1b368c25a --- /dev/null +++ b/src/client/ui/universal.vue @@ -0,0 +1,433 @@ +<template> +<div class="mk-app" :class="{ wallpaper }"> + <XSidebar ref="nav" class="sidebar"/> + + <div class="contents" ref="contents" :class="{ withHeader: $store.state.titlebar }" @contextmenu.stop="onContextmenu"> + <header v-if="$store.state.titlebar" class="header" ref="header" @click="onHeaderClick"> + <XHeader :info="pageInfo"/> + </header> + <main ref="main"> + <div class="content"> + <router-view v-slot="{ Component }"> + <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> + <keep-alive :include="['timeline']"> + <component :is="Component" :ref="changePage"/> + </keep-alive> + </transition> + </router-view> + </div> + <div class="spacer"></div> + </main> + </div> + + <XSide v-if="isDesktop" class="side" ref="side"/> + + <div v-if="isDesktop" class="widgets" ref="widgets"> + <XWidgets @mounted="attachSticky"/> + </div> + + <div class="buttons" :class="{ navHidden }"> + <button class="button nav _button" @click="showNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button> + <button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><Fa :icon="faHome"/></button> + <button class="button notifications _button" @click="$router.push('/my/notifications')"><Fa :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></button> + <button class="button widget _button" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button> + <button class="button post _button" @click="post"><Fa :icon="faPencilAlt"/></button> + </div> + + <button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button> + + <transition name="tray-back"> + <div class="tray-back _modalBg" + v-if="widgetsShowing" + @click="widgetsShowing = false" + @touchstart.passive="widgetsShowing = false" + ></div> + </transition> + + <transition name="tray"> + <XWidgets v-if="widgetsShowing" class="tray"/> + </transition> + + <XCommon/> +</div> +</template> + +<script lang="ts"> +import { defineComponent, defineAsyncComponent } from 'vue'; +import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; +import { faBell } from '@fortawesome/free-regular-svg-icons'; +import { instanceName } from '@client/config'; +import { StickySidebar } from '@client/scripts/sticky-sidebar'; +import XSidebar from '@client/ui/_common_/sidebar.vue'; +import XCommon from './_common_/common.vue'; +import XHeader from './_common_/header.vue'; +import XSide from './default.side.vue'; +import * as os from '@client/os'; +import { sidebarDef } from '@client/sidebar'; + +const DESKTOP_THRESHOLD = 1100; + +export default defineComponent({ + components: { + XCommon, + XSidebar, + XHeader, + XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')), + XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる + }, + + provide() { + return { + sideViewHook: this.isDesktop ? (url) => { + this.$refs.side.navigate(url); + } : null + }; + }, + + data() { + return { + pageInfo: null, + isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, + menuDef: sidebarDef, + navHidden: false, + widgetsShowing: false, + wallpaper: localStorage.getItem('wallpaper') != null, + faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, + }; + }, + + computed: { + navIndicated(): boolean { + for (const def in this.menuDef) { + if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから + if (this.menuDef[def].indicated) return true; + } + return false; + } + }, + + created() { + document.documentElement.style.overflowY = 'scroll'; + + if (this.$store.state.widgets.length === 0) { + this.$store.set('widgets', [{ + name: 'calendar', + id: 'a', place: 'right', data: {} + }, { + name: 'notifications', + id: 'b', place: 'right', data: {} + }, { + name: 'trends', + id: 'c', place: 'right', data: {} + }]); + } + }, + + mounted() { + this.adjustUI(); + + const ro = new ResizeObserver((entries, observer) => { + this.adjustUI(); + }); + + ro.observe(this.$refs.contents); + + window.addEventListener('resize', this.adjustUI, { passive: true }); + + if (!this.isDesktop) { + window.addEventListener('resize', () => { + if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true; + }, { passive: true }); + } + }, + + methods: { + changePage(page) { + if (page == null) return; + if (page.INFO) { + this.pageInfo = page.INFO; + document.title = `${this.pageInfo.title} | ${instanceName}`; + } + }, + + adjustUI() { + const navWidth = this.$refs.nav.$el.offsetWidth; + this.navHidden = navWidth === 0; + if (this.$refs.contents == null) return; + const width = this.$refs.contents.offsetWidth; + if (this.$refs.header) this.$refs.header.style.width = `${width}px`; + }, + + showNav() { + this.$refs.nav.show(); + }, + + attachSticky(el) { + const sticky = new StickySidebar(this.$refs.widgets); + window.addEventListener('scroll', () => { + sticky.calc(window.scrollY); + }, { passive: true }); + }, + + post() { + os.post(); + }, + + top() { + window.scroll({ top: 0, behavior: 'smooth' }); + }, + + onTransition() { + if (window._scroll) window._scroll(); + }, + + onHeaderClick() { + window.scroll({ top: 0, behavior: 'smooth' }); + }, + + onContextmenu(e) { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); + } + }; + if (isLink(e.target)) return; + if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (window.getSelection().toString() !== '') return; + const path = this.$route.path; + os.contextMenu([{ + type: 'label', + text: path, + }, { + icon: faColumns, + text: this.$ts.openInSideView, + action: () => { + this.$refs.side.navigate(path); + } + }, { + icon: faWindowMaximize, + text: this.$ts.openInWindow, + action: () => { + os.pageWindow(path); + } + }], e); + }, + } +}); +</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 { + $header-height: 58px; // TODO: どこかに集約したい + $ui-font-size: 1em; // TODO: どこかに集約したい + $widgets-hide-threshold: 1090px; + + // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + min-height: calc(var(--vh, 1vh) * 100); + box-sizing: border-box; + display: flex; + + &.wallpaper { + background: var(--wallpaperOverlay); + //backdrop-filter: blur(4px); + } + + > .contents { + width: 100%; + min-width: 0; + + &.withHeader { + padding-top: $header-height; + } + + > .header { + position: fixed; + 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 0.5px var(--divider); + user-select: none; + } + + > main { + min-width: 0; + + > .content { + > * { + // ほんとは単に calc(100vh - #{$header-height}) と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + min-height: calc((var(--vh, 1vh) * 100) - #{$header-height}); + } + } + + > .spacer { + height: 82px; + + @media (min-width: ($widgets-hide-threshold + 1px)) { + display: none; + } + } + } + } + + > .side { + min-width: 370px; + max-width: 370px; + border-left: solid 0.5px var(--divider); + } + + > .widgets { + padding: 0 var(--margin); + border-left: solid 0.5px var(--divider); + + @media (max-width: $widgets-hide-threshold) { + display: none; + } + } + + > .widgetButton { + display: block; + position: fixed; + z-index: 1000; + bottom: 32px; + right: 32px; + width: 64px; + height: 64px; + border-radius: 100%; + box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); + font-size: 22px; + background: var(--panel); + + &.navHidden { + display: none; + } + + @media (min-width: ($widgets-hide-threshold + 1px)) { + display: none; + } + } + + > .buttons { + position: fixed; + z-index: 1000; + bottom: 0; + padding: 16px; + display: flex; + width: 100%; + box-sizing: border-box; + -webkit-backdrop-filter: blur(32px); + backdrop-filter: blur(32px); + background-color: var(--header); + + &:not(.navHidden) { + display: none; + } + + > .button { + position: relative; + flex: 1; + padding: 0; + margin: auto; + height: 64px; + border-radius: 8px; + background: var(--panel); + color: var(--fg); + + &:not(:last-child) { + margin-right: 12px; + } + + @media (max-width: 400px) { + height: 60px; + + &:not(:last-child) { + margin-right: 8px; + } + } + + &:hover { + background: var(--X2); + } + + > i { + position: absolute; + top: 0; + left: 0; + color: var(--indicator); + font-size: 16px; + animation: blink 1s infinite; + } + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + + > * { + font-size: 22px; + } + + &:disabled { + cursor: default; + + > * { + opacity: 0.5; + } + } + } + } + + > .tray-back { + z-index: 1001; + } + + > .tray { + position: fixed; + top: 0; + right: 0; + z-index: 1001; + // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + height: calc(var(--vh, 1vh) * 100); + padding: var(--margin); + box-sizing: border-box; + overflow: auto; + background: var(--bg); + } +} +</style> + +<style lang="scss"> +</style> diff --git a/src/client/ui/universal.widgets.vue b/src/client/ui/universal.widgets.vue new file mode 100644 index 0000000000..35d3442bb2 --- /dev/null +++ b/src/client/ui/universal.widgets.vue @@ -0,0 +1,81 @@ +<template> +<div class="efzpzdvf"> + <XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> + + <button v-if="editMode" @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button> + <button v-else @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button> +</div> +</template> + +<script lang="ts"> +import { defineComponent, defineAsyncComponent } from 'vue'; +import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'; +import XWidgets from '@client/components/widgets.vue'; +import * as os from '@client/os'; + +export default defineComponent({ + components: { + XWidgets + }, + + emits: ['mounted'], + + data() { + return { + editMode: false, + faPencilAlt, faPlus, faBars, faTimes, faCheck, + }; + }, + + mounted() { + this.$emit('mounted', this.$el); + }, + + methods: { + addWidget(widget) { + this.$store.set('widgets', [{ + ...widget, + place: null, + }, ...this.$store.state.widgets]); + }, + + removeWidget(widget) { + this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); + }, + + updateWidget({ id, data }) { + this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { + ...w, + data: data + } : w)); + }, + + updateWidgets(widgets) { + this.$store.set('widgets', widgets); + } + } +}); +</script> + +<style lang="scss" scoped> +.efzpzdvf { + position: sticky; + height: min-content; + min-height: 100vh; + padding: var(--margin) 0; + box-sizing: border-box; + + > * { + margin: var(--margin) 0; + width: 300px; + + &:first-child { + margin-top: 0; + } + } + + > .add { + margin: 0 auto; + } +} +</style> diff --git a/src/client/ui/zen.vue b/src/client/ui/zen.vue index 9215a639b0..321eb1a025 100644 --- a/src/client/ui/zen.vue +++ b/src/client/ui/zen.vue @@ -94,7 +94,7 @@ export default defineComponent({ -webkit-backdrop-filter: blur(32px); backdrop-filter: blur(32px); background-color: var(--header); - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); } > main { diff --git a/src/client/widgets/aiscript.vue b/src/client/widgets/aiscript.vue index 84390866bd..f24e033593 100644 --- a/src/client/widgets/aiscript.vue +++ b/src/client/widgets/aiscript.vue @@ -122,7 +122,7 @@ export default defineComponent({ color: var(--fg); background: transparent; border: none; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); border-radius: 0; box-sizing: border-box; font: inherit; @@ -147,7 +147,7 @@ export default defineComponent({ } > .logs { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); text-align: left; padding: 16px; diff --git a/src/client/widgets/federation.vue b/src/client/widgets/federation.vue index 6eb656ce05..f0a79a31a6 100644 --- a/src/client/widgets/federation.vue +++ b/src/client/widgets/federation.vue @@ -100,7 +100,7 @@ export default defineComponent({ display: flex; align-items: center; padding: 14px 16px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); > img { display: block; diff --git a/src/client/widgets/job-queue.vue b/src/client/widgets/job-queue.vue index aaf2b2fc8f..0b560ca56e 100644 --- a/src/client/widgets/job-queue.vue +++ b/src/client/widgets/job-queue.vue @@ -136,7 +136,7 @@ export default defineComponent({ padding: 16px; &:not(:first-child) { - border-top: solid 1px var(--divider); + border-top: solid 0.5px var(--divider); } > .label { diff --git a/src/client/widgets/memo.vue b/src/client/widgets/memo.vue index 4a7786312a..c3ab2934a8 100644 --- a/src/client/widgets/memo.vue +++ b/src/client/widgets/memo.vue @@ -77,7 +77,7 @@ export default defineComponent({ color: var(--fg); background: transparent; border: none; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); border-radius: 0; box-sizing: border-box; font: inherit; diff --git a/src/client/widgets/trends.vue b/src/client/widgets/trends.vue index 300e3b31de..cd7202f1f8 100644 --- a/src/client/widgets/trends.vue +++ b/src/client/widgets/trends.vue @@ -79,7 +79,7 @@ export default defineComponent({ display: flex; align-items: center; padding: 14px 16px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 0.5px var(--divider); > .tag { flex: 1;