diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2020-03-21 13:36:41 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2020-03-21 13:36:41 +0900 |
| commit | 6fb7721798657cf842556fb63f116316365efa74 (patch) | |
| tree | 6917342553156b6483b899eafc66ef8f040b8352 /src/client | |
| parent | Update CHANGELOG.md (diff) | |
| parent | 12.22.0 (diff) | |
| download | misskey-6fb7721798657cf842556fb63f116316365efa74.tar.gz misskey-6fb7721798657cf842556fb63f116316365efa74.tar.bz2 misskey-6fb7721798657cf842556fb63f116316365efa74.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/client')
48 files changed, 457 insertions, 399 deletions
diff --git a/src/client/app.vue b/src/client/app.vue index 48df0b9aa8..4e5dfbd18a 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -51,11 +51,7 @@ <fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span> </router-link> <template v-if="$store.getters.isSignedIn"> - <button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.state.device.useNotificationsPopup"> - <fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span> - <i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i> - </button> - <router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton" v-else> + <router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton"> <fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span> <i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i> </router-link> @@ -149,17 +145,12 @@ <button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button> <button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button> <button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button> - <button v-if="$store.getters.isSignedIn && $store.state.device.useNotificationsPopup" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button> - <button v-if="$store.getters.isSignedIn && !$store.state.device.useNotificationsPopup" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button> + <button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button> <button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button> </div> <button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button> - <transition name="zoom-in-top"> - <x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/> - </transition> - <stream-indicator v-if="$store.getters.isSignedIn"/> </div> </template> @@ -173,7 +164,6 @@ import { v4 as uuid } from 'uuid'; import i18n from './i18n'; import { host, instanceName } from './config'; import { search } from './scripts/search'; -import contains from './scripts/contains'; import MkToast from './components/toast.vue'; const DESKTOP_THRESHOLD = 1100; @@ -183,7 +173,6 @@ export default Vue.extend({ components: { XClock: () => import('./components/header-clock.vue').then(m => m.default), - XNotifications: () => import('./components/notifications.vue').then(m => m.default), MkButton: () => import('./components/ui/button.vue').then(m => m.default), XDraggable: () => import('vuedraggable'), }, @@ -194,7 +183,6 @@ export default Vue.extend({ pageKey: 0, showNav: false, searching: false, - notificationsOpen: false, accounts: [], lists: [], connection: null, @@ -226,23 +214,10 @@ export default Vue.extend({ watch:{ $route(to, from) { this.pageKey++; - this.notificationsOpen = false; this.showNav = false; this.canBack = (window.history.length > 0 && !['index'].includes(to.name)); }, - notificationsOpen(open) { - if (open) { - for (const el of Array.from(document.querySelectorAll('*'))) { - el.addEventListener('mousedown', this.onMousedown); - } - } else { - for (const el of Array.from(document.querySelectorAll('*'))) { - el.removeEventListener('mousedown', this.onMousedown); - } - } - }, - isDesktop() { if (this.isDesktop) this.adjustWidgetsWidth(); } @@ -568,15 +543,6 @@ export default Vue.extend({ this.$root.sound('notification'); }, - onMousedown(e) { - e.preventDefault(); - if (!contains(this.$refs.notifications.$el, e.target) && - !contains(this.$refs.notificationButton, e.target) && - !contains(this.$refs.notificationButton2, e.target) - ) this.notificationsOpen = false; - return false; - }, - widgetFunc(id) { const w = this.$refs[id][0]; if (w.func) w.func(); @@ -652,7 +618,7 @@ export default Vue.extend({ $header-height: 60px; $nav-width: 250px; $nav-icon-only-width: 74px; - $main-width: 700px; + $main-width: 650px; $ui-font-size: 1em; $nav-icon-only-threshold: 1300px; $nav-hide-threshold: 700px; @@ -975,17 +941,21 @@ export default Vue.extend({ > main { width: $main-width; min-width: $main-width; + box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider); @media (max-width: $side-hide-threshold) { min-width: 0; } > .content { - padding: 16px; - box-sizing: border-box; + > * { + &:not(.full) { + padding: var(--margin) 0; + } - @media (max-width: 500px) { - padding: 8px; + &:not(.naked) { + background: var(--pageBg); + } } } @@ -1023,6 +993,7 @@ export default Vue.extend({ > .widgets { box-sizing: border-box; + margin-left: var(--margin); @media (max-width: $side-hide-threshold) { display: none; @@ -1175,34 +1146,5 @@ export default Vue.extend({ } } } - - > .notifications { - position: fixed; - top: 32px; - left: 0; - right: 0; - margin: 0 auto; - padding: 8px 8px 0 8px; - z-index: 10001; - width: 350px; - height: 400px; - box-sizing: border-box; - background: var(--vocsgcxy); - -webkit-backdrop-filter: blur(12px); - backdrop-filter: blur(12px); - border-radius: 6px; - box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15); - overflow: auto; - - @media (max-width: 800px) { - width: 320px; - height: 350px; - } - - @media (max-width: 500px) { - width: 290px; - height: 310px; - } - } } </style> diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue index 53fd0a7c7f..d41dd9d521 100644 --- a/src/client/components/date-separated-list.vue +++ b/src/client/components/date-separated-list.vue @@ -1,5 +1,5 @@ <template> -<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" appear :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'"> +<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'"> <template v-for="(item, i) in items"> <slot :item="item" :i="i"></slot> <div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)"> @@ -109,8 +109,6 @@ export default Vue.extend({ line-height: 32px; text-align: center; font-size: 12px; - border-radius: 64px; - background: var(--dateLabelBg); color: var(--dateLabelFg); > span { diff --git a/src/client/components/error.vue b/src/client/components/error.vue index f4698247b2..7446a7cb5d 100644 --- a/src/client/components/error.vue +++ b/src/client/components/error.vue @@ -27,8 +27,6 @@ export default Vue.extend({ <style lang="scss" scoped> .mjndxjcg { - max-width: 350px; - margin: 0 auto; padding: 32px; text-align: center; diff --git a/src/client/components/google.vue b/src/client/components/google.vue index 21560008f6..01dcf24bf8 100644 --- a/src/client/components/google.vue +++ b/src/client/components/google.vue @@ -1,12 +1,13 @@ <template> <div class="mk-google"> <input type="search" v-model="query" :placeholder="q"> - <button @click="search"><fa icon="search"/> {{ $t('search') }}</button> + <button @click="search"><fa :icon="faSearch"/> {{ $t('search') }}</button> </div> </template> <script lang="ts"> import Vue from 'vue'; +import { faSearch } from '@fortawesome/free-solid-svg-icons'; import i18n from '../i18n'; export default Vue.extend({ @@ -14,7 +15,8 @@ export default Vue.extend({ props: ['q'], data() { return { - query: null + query: null, + faSearch }; }, mounted() { @@ -42,27 +44,17 @@ export default Vue.extend({ width: 100%; height: 40px; font-size: 16px; - color: var(--googleSearchFg); - background: var(--googleSearchBg); - border: solid 1px var(--googleSearchBorder); + border: solid 1px var(--divider); border-radius: 4px 0 0 4px; - - &:hover { - border-color: var(--googleSearchHoverBorder); - } } > button { flex-shrink: 0; padding: 0 16px; - border: solid 1px var(--googleSearchBorder); + border: solid 1px var(--divider); border-left: none; border-radius: 0 4px 4px 0; - &:hover { - background-color: var(--googleSearchHoverButton); - } - &:active { box-shadow: 0 2px 4px rgba(#000, 0.15) inset; } diff --git a/src/client/components/index.ts b/src/client/components/index.ts index 9e95fba873..87547599a9 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -9,7 +9,6 @@ import ellipsis from './ellipsis.vue'; import time from './time.vue'; import url from './url.vue'; import loading from './loading.vue'; -import SequentialEntrance from './sequential-entrance.vue'; import error from './error.vue'; import streamIndicator from './stream-indicator.vue'; @@ -23,5 +22,4 @@ Vue.component('mk-time', time); Vue.component('mk-url', url); Vue.component('mk-loading', loading); Vue.component('mk-error', error); -Vue.component('sequential-entrance', SequentialEntrance); Vue.component('stream-indicator', streamIndicator); diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 3bb1bda5e2..79b5150b11 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -90,7 +90,7 @@ export default Vue.extend({ > div { background-color: var(--fg); border-radius: 6px; - color: var(--secondary); + color: var(--accentLighten); display: inline-block; font-size: 14px; font-weight: bold; diff --git a/src/client/components/menu.vue b/src/client/components/menu.vue index 6fee809c40..74e9a29ccf 100644 --- a/src/client/components/menu.vue +++ b/src/client/components/menu.vue @@ -1,6 +1,6 @@ <template> <x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap"> - <sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction" ref="items"> + <div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items"> <template v-for="(item, i) in items.filter(item => item !== undefined)"> <div v-if="item === null" class="divider" :key="i"></div> <span v-else-if="item.type === 'label'" class="label item" :key="i"> @@ -28,7 +28,7 @@ <i v-if="item.indicate"><fa :icon="faCircle"/></i> </button> </template> - </sequential-entrance> + </div> </x-popup> </template> @@ -91,7 +91,7 @@ export default Vue.extend({ mounted() { if (this.viaKeyboard) { this.$nextTick(() => { - focusNext(this.$refs.items.$slots.default[0].elm, true); + focusNext(this.$refs.items.children[0], true); }); } }, diff --git a/src/client/components/note.sub.vue b/src/client/components/note.sub.vue index 7f6f972896..5efbb8f1e9 100644 --- a/src/client/components/note.sub.vue +++ b/src/client/components/note.sub.vue @@ -1,5 +1,5 @@ <template> -<div class="zlrxdaqttccpwhpaagdmkawtzklsccam"> +<div class="wrpstxzv" v-size="[{ max: 450 }]"> <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <x-note-header class="header" :note="note" :mini="true"/> @@ -56,13 +56,12 @@ export default Vue.extend({ </script> <style lang="scss" scoped> -.zlrxdaqttccpwhpaagdmkawtzklsccam { +.wrpstxzv { display: flex; padding: 16px 32px; font-size: 0.9em; - background: rgba(0, 0, 0, 0.03); - @media (max-width: 450px) { + &.max-width_450px { padding: 14px 16px; } diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 909ed30235..db669309d3 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -79,7 +79,7 @@ <div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div> </div> </article> - <x-sub v-for="note in replies" :key="note.id" :note="note"/> + <x-sub v-for="note in replies" :key="note.id" :note="note" class="reply"/> </div> </template> @@ -684,6 +684,7 @@ export default Vue.extend({ .note { position: relative; transition: box-shadow 0.1s ease; + overflow: hidden; &.max-width_500px { font-size: 0.9em; @@ -749,14 +750,6 @@ export default Vue.extend({ opacity: 1; } - > *:first-child { - border-radius: var(--radius) var(--radius) 0 0; - } - - > *:last-child { - border-radius: 0 0 var(--radius) var(--radius); - } - > .info { display: flex; align-items: center; @@ -784,6 +777,11 @@ export default Vue.extend({ padding-top: 8px; } + > .reply-to { + opacity: 0.7; + padding-bottom: 0; + } + > .renote { display: flex; align-items: center; @@ -937,5 +935,9 @@ export default Vue.extend({ } } } + + > .reply { + border-top: solid 1px var(--divider); + } } </style> diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue index dc93c1f6c4..bc2ae8472c 100644 --- a/src/client/components/notes.vue +++ b/src/client/components/notes.vue @@ -7,22 +7,22 @@ <mk-error v-if="error" @retry="init()"/> - <div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);"> - <mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary> + <div v-if="more && reversed" style="margin-bottom: var(--margin);"> + <button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()"> <template v-if="!moreFetching">{{ $t('loadMore') }}</template> <template v-if="moreFetching"><mk-loading inline/></template> - </mk-button> + </button> </div> <x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> <x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/> </x-list> - <div class="more" v-if="more && !reversed" style="margin-top: var(--margin);"> - <mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary> + <div v-if="more && !reversed" style="margin-top: var(--margin);"> + <button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()"> <template v-if="!moreFetching">{{ $t('loadMore') }}</template> <template v-if="moreFetching"><mk-loading inline/></template> - </mk-button> + </button> </div> </div> </template> @@ -111,16 +111,10 @@ export default Vue.extend({ &.max-width_500px { > .notes { > ::v-deep *:not(:last-child) { - margin-bottom: var(--marginHalf); + //margin-bottom: var(--marginHalf); + margin-bottom: 0; } } } - - > .more > .button { - margin-left: auto; - margin-right: auto; - height: 48px; - width: 100%; - } } </style> diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index ff6d63821f..a17663b01d 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -1,13 +1,13 @@ <template> -<div class="mk-notifications" :class="{ page }"> +<div class="mk-notifications"> <x-list class="notifications" :items="items" v-slot="{ item: notification }"> <x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/> - <x-notification v-else :notification="notification" :with-time="true" :full="true" class="notification" :class="{ _panel: page }" :key="notification.id"/> + <x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/> </x-list> - <button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching"> + <button class="_panel _button" v-if="more" @click="fetchMore" :disabled="moreFetching"> <template v-if="!moreFetching">{{ $t('loadMore') }}</template> - <template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template> + <template v-if="moreFetching"><mk-loading inline/></template> </button> <p class="empty" v-if="empty">{{ $t('noNotifications') }}</p> @@ -18,7 +18,6 @@ <script lang="ts"> import Vue from 'vue'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; import i18n from '../i18n'; import paging from '../scripts/paging'; import XNotification from './notification.vue'; @@ -43,11 +42,6 @@ export default Vue.extend({ type: String, required: false }, - page: { - type: Boolean, - required: false, - default: false - } }, data() { @@ -60,7 +54,6 @@ export default Vue.extend({ includeTypes: this.type ? [this.type] : undefined }) }, - faSpinner }; }, @@ -94,35 +87,10 @@ export default Vue.extend({ <style lang="scss" scoped> .mk-notifications { - &.page { - > .notifications { - > ::v-deep * { - margin-bottom: var(--margin); - } - } - } - - &:not(.page) { - > .notifications { - > ::v-deep * { - margin-bottom: 8px; - } - - > .notification { - background: var(--panel); - border-radius: 6px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - } - } - } - - > .more { - display: block; - width: 100%; - padding: 16px; - - > [data-icon] { - margin-right: 4px; + > .notifications { + > ::v-deep * { + //margin-bottom: var(--margin); + margin-bottom: 0; } } diff --git a/src/client/components/poll-editor.vue b/src/client/components/poll-editor.vue index b5b8c2c02d..91c7dab598 100644 --- a/src/client/components/poll-editor.vue +++ b/src/client/components/poll-editor.vue @@ -53,7 +53,7 @@ import Vue from 'vue'; import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons'; import i18n from '../i18n'; import { erase } from '../../prelude/array'; -import { addTimespan } from '../../prelude/time'; +import { addTime } from '../../prelude/time'; import { formatDateTimeString } from '../../misc/format-time-string'; import MkInput from './ui/input.vue'; import MkSelect from './ui/select.vue'; @@ -73,7 +73,7 @@ export default Vue.extend({ choices: ['', ''], multiple: false, expiration: 'infinite', - atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'), + atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'), atTime: '00:00', after: 0, unit: 'second', diff --git a/src/client/components/post-form-dialog.vue b/src/client/components/post-form-dialog.vue index b6531474cf..9cb527af23 100644 --- a/src/client/components/post-form-dialog.vue +++ b/src/client/components/post-form-dialog.vue @@ -17,7 +17,8 @@ :initial-note="initialNote" :instant="instant" @posted="onPosted" - @cancel="onCanceled"/> + @cancel="onCanceled" + style="border-radius: var(--radius);"/> </transition> </div> </div> diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 2d35cfe167..7b84938d5a 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -586,7 +586,6 @@ export default Vue.extend({ <style lang="scss" scoped> .gafaadew { background: var(--panel); - border-radius: var(--radius); > header { z-index: 1000; diff --git a/src/client/components/remote-caution.vue b/src/client/components/remote-caution.vue new file mode 100644 index 0000000000..95b37d3053 --- /dev/null +++ b/src/client/components/remote-caution.vue @@ -0,0 +1,36 @@ +<template> +<div class="jmgmzlwq _panel"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../i18n'; + +export default Vue.extend({ + i18n, + props: { + href: { + type: String, + required: true + }, + }, + data() { + return { + faExclamationTriangle + }; + } +}); +</script> + +<style lang="scss" scoped> +.jmgmzlwq { + font-size: 0.8em; + padding: 16px; + + > a { + margin-left: 4px; + color: var(--accent); + } +} +</style> diff --git a/src/client/components/sequential-entrance.vue b/src/client/components/sequential-entrance.vue deleted file mode 100644 index 50113cff1c..0000000000 --- a/src/client/components/sequential-entrance.vue +++ /dev/null @@ -1,40 +0,0 @@ -<template> -<transition-group v-if="$store.state.device.animation" - class="uupnnhew" - name="staggered" - tag="div" - appear -> - <slot></slot> -</transition-group> -<div v-else> - <slot></slot> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; - -export default Vue.extend({ - methods: { - focus() { - this.$slots.default[0].elm.focus(); - } - }, -}); -</script> - -<style lang="scss"> -.uupnnhew { - > .staggered-enter { - opacity: 0; - transform: translateY(-64px); - } - - @for $i from 1 through 30 { - > .staggered-enter-active:nth-child(#{$i}) { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)); - } - } -} -</style> diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index 758bc59107..758bc59107 100644..100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue diff --git a/src/client/components/ui/button.vue b/src/client/components/ui/button.vue index 5264224c18..15289c820e 100644 --- a/src/client/components/ui/button.vue +++ b/src/client/components/ui/button.vue @@ -124,7 +124,6 @@ export default Vue.extend({ &.primary { color: #fff; background: var(--accent); - box-shadow: 0 6px 16px var(--accentShadow); &:not(:disabled):hover { background: var(--jkhztclx); diff --git a/src/client/components/ui/container.vue b/src/client/components/ui/container.vue index 4e7c9420ab..9d5abdf2dd 100644 --- a/src/client/components/ui/container.vue +++ b/src/client/components/ui/container.vue @@ -110,6 +110,7 @@ export default Vue.extend({ > header { position: relative; box-shadow: 0 1px 0 0 var(--divider); + z-index: 1; > .title { margin: 0; diff --git a/src/client/components/ui/pagination.vue b/src/client/components/ui/pagination.vue index 7f04b35de1..e888b7420c 100644 --- a/src/client/components/ui/pagination.vue +++ b/src/client/components/ui/pagination.vue @@ -1,5 +1,5 @@ <template> -<sequential-entrance class="cxiknjgy" :class="{ autoMargin }"> +<div class="cxiknjgy" :class="{ autoMargin }"> <slot :items="items"></slot> <div class="empty" v-if="empty" key="_empty_"> <slot name="empty"></slot> @@ -10,7 +10,7 @@ <template v-if="moreFetching"><mk-loading inline/></template> </mk-button> </div> -</sequential-entrance> +</div> </template> <script lang="ts"> diff --git a/src/client/components/ui/range.vue b/src/client/components/ui/range.vue new file mode 100644 index 0000000000..7fb857f520 --- /dev/null +++ b/src/client/components/ui/range.vue @@ -0,0 +1,138 @@ +<template> +<div class="timctyfi" :class="{ focused, disabled }"> + <div class="icon"><slot name="icon"></slot></div> + <span class="title"><slot name="title"></slot></span> + <input + type="range" + ref="input" + v-model="v" + :disabled="disabled" + :min="min" + :max="max" + :step="step" + :autofocus="autofocus" + @focus="focused = true" + @blur="focused = false" + @input="$emit('input', $event.target.value)" + /> +</div> +</template> + +<script lang="ts"> +import Vue from "vue"; +export default Vue.extend({ + props: { + value: { + type: Number, + required: false, + default: 0 + }, + disabled: { + type: Boolean, + required: false, + default: false + }, + min: { + type: Number, + required: false, + default: 0 + }, + max: { + type: Number, + required: false, + default: 100 + }, + step: { + type: Number, + required: false, + default: 1 + }, + autofocus: { + type: Boolean, + required: false + } + }, + data() { + return { + v: this.value, + focused: false + }; + }, + watch: { + value(v) { + this.v = parseFloat(v); + } + }, + mounted() { + if (this.autofocus) { + this.$nextTick(() => { + this.$refs.input.focus(); + }); + } + } +}); +</script> + +<style lang="scss" scoped> +.timctyfi { + position: relative; + margin: 8px; + + > .icon { + display: inline-block; + width: 24px; + text-align: center; + } + + > .title { + pointer-events: none; + font-size: 16px; + color: var(--inputLabel); + overflow: hidden; + } + + > input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: var(--xxubwiul); + height: 7px; + margin: 0 8px; + outline: 0; + border: 0; + border-radius: 7px; + + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + box-sizing: content-box; + } + + &::-moz-range-thumb { + -moz-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + } + } +} +</style> diff --git a/src/client/components/ui/select.vue b/src/client/components/ui/select.vue index a1e89cdf02..ce21949713 100644 --- a/src/client/components/ui/select.vue +++ b/src/client/components/ui/select.vue @@ -158,6 +158,11 @@ export default Vue.extend({ outline: none; box-shadow: none; color: var(--fg); + + option, + optgroup { + background: var(--bg); + } } > .prefix, diff --git a/src/client/components/url-preview.vue b/src/client/components/url-preview.vue index 940d3892db..94d07cbaed 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -230,8 +230,8 @@ export default Vue.extend({ position: relative; display: block; font-size: 14px; - box-shadow: 0 1px 4px var(--tyvedwbe); - border-radius: 4px; + box-shadow: 0 0 0 1px var(--divider); + border-radius: 6px; overflow: hidden; &:hover { diff --git a/src/client/components/users-dialog.vue b/src/client/components/users-dialog.vue index a70b3c2d13..9d0c5e4251 100644 --- a/src/client/components/users-dialog.vue +++ b/src/client/components/users-dialog.vue @@ -6,15 +6,15 @@ <button class="_button" @click="close()"><fa :icon="faTimes"/></button> </div> - <sequential-entrance class="users"> - <router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage"> + <div class="users"> + <router-link v-for="item in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage"> <mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/> <div class="body"> <mk-user-name :user="extract ? extract(item) : item" class="name"/> <mk-acct :user="extract ? extract(item) : item" class="acct"/> </div> </router-link> - </sequential-entrance> + </div> <button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching"> <template v-if="!moreFetching">{{ $t('loadMore') }}</template> diff --git a/src/client/components/visibility-chooser.vue b/src/client/components/visibility-chooser.vue index 28413fd837..dc7b41e286 100644 --- a/src/client/components/visibility-chooser.vue +++ b/src/client/components/visibility-chooser.vue @@ -1,6 +1,6 @@ <template> <x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }"> - <sequential-entrance class="gqyayizv" :delay="30"> + <div class="gqyayizv"> <button class="_button" @click="choose('public')" :class="{ active: v == 'public' }" data-index="1" key="public"> <div><fa :icon="faGlobe"/></div> <div> @@ -29,7 +29,7 @@ <span>{{ $t('_visibility.specifiedDescription') }}</span> </div> </button> - </sequential-entrance> + </div> </x-popup> </template> diff --git a/src/client/directives/size.ts b/src/client/directives/size.ts index c4dd7b145d..541f38fd76 100644 --- a/src/client/directives/size.ts +++ b/src/client/directives/size.ts @@ -59,7 +59,7 @@ export default { const ro = new ResizeObserver((entries, observer) => { calc(); }); - + ro.observe(el); el._ro_ = ro; diff --git a/src/client/init.ts b/src/client/init.ts index 2f2f9f5d59..29eabfee4e 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -81,14 +81,14 @@ if (lang == null) { // Detect the user agent const ua = navigator.userAgent.toLowerCase(); -let isMobile = /mobile|iphone|ipad|android/.test(ua); +const isMobile = /mobile|iphone|ipad|android/.test(ua); // Get the <head> element const head = document.getElementsByTagName('head')[0]; // If mobile, insert the viewport meta tag if (isMobile || window.innerWidth <= 1024) { - const viewport = document.getElementsByName("viewport").item(0); + const viewport = document.getElementsByName('viewport').item(0); viewport.setAttribute('content', `${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`); head.appendChild(viewport); diff --git a/src/client/mios.ts b/src/client/mios.ts index a29dcd8550..aa2b202abd 100644 --- a/src/client/mios.ts +++ b/src/client/mios.ts @@ -123,8 +123,13 @@ export default class MiOS extends EventEmitter { }); } else { // Get token from localStorage - const i = localStorage.getItem('i'); - + let i = localStorage.getItem('i'); + + // 連携ログインの場合用にCookieを参照する + if (i == null || i === 'null') { + i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; + } + fetchme(i, me => { if (me) { this.store.dispatch('login', me); diff --git a/src/client/pages/auth.vue b/src/client/pages/auth.vue index 8855948416..9f5b45f001 100644..100755 --- a/src/client/pages/auth.vue +++ b/src/client/pages/auth.vue @@ -26,7 +26,7 @@ </div> <div class="signin" v-else> <h1>{{ $t('sign-in') }}</h1> - <mk-signin/> + <mk-signin @login="onLogin"/> </div> </template> @@ -85,6 +85,9 @@ export default Vue.extend({ if (this.session.app.callbackUrl) { location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; } + }, onLogin(res) { + localStorage.setItem('i', res.i); + location.reload(); } } }); diff --git a/src/client/pages/drive.vue b/src/client/pages/drive.vue index 7b648939f2..8f8e949dcb 100644 --- a/src/client/pages/drive.vue +++ b/src/client/pages/drive.vue @@ -1,5 +1,5 @@ <template> -<div> +<div class="naked full"> <portal to="header"> <button @click="menu" class="_button _jmoebdiw_"> <fa :icon="faCloud" style="margin-right: 8px;"/> diff --git a/src/client/pages/instance/queue.queue.vue b/src/client/pages/instance/queue.queue.vue index 710f4ec099..7f0fc7d2bc 100644 --- a/src/client/pages/instance/queue.queue.vue +++ b/src/client/pages/instance/queue.queue.vue @@ -11,12 +11,12 @@ <canvas ref="chart"></canvas> </div> <div class="_content" style="max-height: 180px; overflow: auto;"> - <sequential-entrance :delay="15" v-if="jobs.length > 0"> - <div v-for="(job, i) in jobs" :key="job[0]"> + <div v-if="jobs.length > 0"> + <div v-for="job in jobs" :key="job[0]"> <span>{{ job[0] }}</span> <span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span> </div> - </sequential-entrance> + </div> <span v-else style="opacity: 0.5;">{{ $t('noJobs') }}</span> </div> </section> diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue index 7572108718..d11f840d8f 100644 --- a/src/client/pages/instance/settings.vue +++ b/src/client/pages/instance/settings.vue @@ -102,21 +102,20 @@ <div class="_content"> <mk-switch v-model="useObjectStorage">{{ $t('useObjectStorage') }}</mk-switch> <template v-if="useObjectStorage"> - <mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">URL</mk-input> + <mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">{{ $t('objectStorageBaseUrl') }}<template #desc>{{ $t('objectStorageBaseUrlDesc') }}</template></mk-input> <div class="_inputs"> - <mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">Bucket</mk-input> - <mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">Prefix</mk-input> + <mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('objectStorageBucket') }}<template #desc>{{ $t('objectStorageBucketDesc') }}</template></mk-input> + <mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('objectStoragePrefix') }}<template #desc>{{ $t('objectStoragePrefixDesc') }}</template></mk-input> </div> - <mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">Endpoint</mk-input> + <mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('objectStorageEndpoint') }}<template #desc>{{ $t('objectStorageEndpointDesc') }}</template></mk-input> <div class="_inputs"> - <mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">Region</mk-input> - <mk-input v-model="objectStoragePort" type="number" :disabled="!useObjectStorage">Port</mk-input> + <mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('objectStorageRegion') }}<template #desc>{{ $t('objectStorageRegionDesc') }}</template></mk-input> </div> <div class="_inputs"> <mk-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Access key</mk-input> <mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input> </div> - <mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">SSL</mk-switch> + <mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></mk-switch> </template> </div> <div class="_footer"> diff --git a/src/client/pages/messaging-room.message.vue b/src/client/pages/messaging-room.message.vue index 48de2c7cd7..f26ef449b2 100644 --- a/src/client/pages/messaging-room.message.vue +++ b/src/client/pages/messaging-room.message.vue @@ -2,7 +2,7 @@ <div class="thvuemwp" :data-is-me="isMe"> <mk-avatar class="avatar" :user="message.user"/> <div class="content"> - <div class="balloon _panel" :data-no-text="message.text == null"> + <div class="balloon" :data-no-text="message.text == null"> <button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del"> <img src="/assets/remove.png" alt="Delete"/> </button> @@ -243,13 +243,14 @@ export default Vue.extend({ } &:not([data-is-me]) { + padding-left: var(--margin); > .content { padding-left: 16px; padding-right: 32px; > .balloon { - $color: var(--panel); + $color: var(--messageBg); background: $color; &[data-no-text] { @@ -279,6 +280,7 @@ export default Vue.extend({ &[data-is-me] { flex-direction: row-reverse; + padding-right: var(--margin); > .content { padding-right: 16px; @@ -287,7 +289,6 @@ export default Vue.extend({ > .balloon { background: $me-balloon-color; - box-shadow: 0 6px 16px var(--accentShadow); text-align: left; &[data-no-text] { diff --git a/src/client/pages/messaging-room.vue b/src/client/pages/messaging-room.vue index 7f7e77fc14..5fca8c0ff3 100644 --- a/src/client/pages/messaging-room.vue +++ b/src/client/pages/messaging-room.vue @@ -1,5 +1,5 @@ <template> -<div class="mk-messaging-room" +<div class="mk-messaging-room naked" @dragover.prevent.stop="onDragover" @drop.prevent.stop="onDrop" > diff --git a/src/client/pages/messaging.vue b/src/client/pages/messaging.vue index bc85e7a56b..2179115dea 100644 --- a/src/client/pages/messaging.vue +++ b/src/client/pages/messaging.vue @@ -5,7 +5,7 @@ <mk-button @click="start" primary class="start"><fa :icon="faPlus"/> {{ $t('startMessaging') }}</mk-button> - <sequential-entrance class="history" v-if="messages.length > 0" :delay="30"> + <div class="history" v-if="messages.length > 0"> <router-link v-for="(message, i) in messages" class="message _panel" :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" @@ -30,7 +30,7 @@ </div> </div> </router-link> - </sequential-entrance> + </div> <div class="no-history" v-if="!fetching && messages.length == 0"> <img src="https://xn--931a.moe/assets/info.png" class="_ghost"/> <div>{{ $t('noHistory') }}</div> diff --git a/src/client/pages/my-settings/integration.vue b/src/client/pages/my-settings/integration.vue index 742d432018..3dd7783f12 100644 --- a/src/client/pages/my-settings/integration.vue +++ b/src/client/pages/my-settings/integration.vue @@ -70,11 +70,10 @@ export default Vue.extend({ }, mounted() { - if (!document.cookie.match(/i=(\w+)/)) { - document.cookie = `i=${this.$store.state.i.token}; path=/;` + - ` domain=${document.location.hostname}; max-age=31536000;` + + document.cookie = `igi=${this.$store.state.i.token}; path=/;` + + ` max-age=31536000;` + (document.location.protocol.startsWith('https') ? ' secure' : ''); - } + this.$watch('integrations', () => { if (this.integrations.twitter) { if (this.twitterForm) this.twitterForm.close(); diff --git a/src/client/pages/my-settings/reaction.vue b/src/client/pages/my-settings/reaction.vue index 250769ec9e..b2df3f0231 100644 --- a/src/client/pages/my-settings/reaction.vue +++ b/src/client/pages/my-settings/reaction.vue @@ -2,7 +2,10 @@ <section class="_card"> <div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div> <div class="_content"> - <mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea> + <mk-input v-model="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif"> + {{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template> + </mk-input> + <mk-button inline @click="setDefault"><fa :icon="faUndo"/> {{ $t('default') }}</mk-button> </div> <div class="_footer"> <mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button> @@ -14,24 +17,26 @@ <script lang="ts"> import Vue from 'vue'; import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons'; -import MkTextarea from '../../components/ui/textarea.vue'; +import { faUndo } from '@fortawesome/free-solid-svg-icons'; +import MkInput from '../../components/ui/input.vue'; import MkButton from '../../components/ui/button.vue'; import MkReactionPicker from '../../components/reaction-picker.vue'; import i18n from '../../i18n'; +import { emojiRegexWithCustom } from '../../../misc/emoji-regex'; export default Vue.extend({ i18n, components: { - MkTextarea, + MkInput, MkButton, }, data() { return { - reactions: this.$store.state.settings.reactions.join('\n'), + reactions: this.$store.state.settings.reactions.join(''), changed: false, - faLaugh, faSave, faEye + faLaugh, faSave, faEye, faUndo } }, @@ -41,21 +46,40 @@ export default Vue.extend({ } }, + computed: { + splited(): any { + return this.reactions.match(emojiRegexWithCustom); + }, + }, + methods: { save() { - this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions.trim().split('\n') }); + this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited }); this.changed = false; }, preview(ev) { const picker = this.$root.new(MkReactionPicker, { source: ev.currentTarget || ev.target, - reactions: this.reactions.trim().split('\n'), + reactions: this.splited, showFocus: false, }); picker.$once('chosen', reaction => { picker.close(); }); + }, + + setDefault() { + this.reactions = '👍❤😆🤔😮🎉💢😥😇🍮'; + }, + + async chooseEmoji(ev) { + const vm = this.$root.new(await import('../../components/emoji-picker.vue').then(m => m.default), { + source: ev.currentTarget || ev.target + }).$once('chosen', emoji => { + this.reactions += emoji; + vm.close(); + }); } } }); diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue index 30741bab55..dbdf8c3d35 100644 --- a/src/client/pages/note.vue +++ b/src/client/pages/note.vue @@ -1,24 +1,28 @@ <template> <div class="mk-note-page"> <portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal> - <portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal> + <portal to="title" v-if="note"> + <mfm + :text="$t('noteOf', { user: note.user.name || note.user.username })" + :plain="true" :nowrap="true" :custom-emojis="note.user.emojis" :is-note="false" + /> + </portal> - <transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in"> - <div v-if="note"> - <mk-button v-if="hasNext && !showNext" @click="showNext = true" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></mk-button> - <x-notes v-if="showNext" ref="next" :pagination="next"/> - <hr v-if="showNext"/> + <div v-if="note"> + <button class="_panel _button" v-if="hasNext && !showNext" @click="showNext = true" style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></button> + <x-notes v-if="showNext" ref="next" :pagination="next"/> + <hr v-if="showNext"/> - <x-note :note="note" :key="note.id" :detail="true"/> - <div v-if="error"> - <mk-error @retry="fetch()"/> - </div> - - <mk-button v-if="hasPrev && !showPrev" @click="showPrev = true" primary style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></mk-button> - <hr v-if="showPrev"/> - <x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/> + <mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/> + <x-note :note="note" :key="note.id" :detail="true"/> + <div v-if="error"> + <mk-error @retry="fetch()"/> </div> - </transition> + + <button class="_panel _button" v-if="hasPrev && !showPrev" @click="showPrev = true" style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></button> + <hr v-if="showPrev"/> + <x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/> + </div> </div> </template> @@ -29,7 +33,7 @@ import i18n from '../i18n'; import Progress from '../scripts/loading'; import XNote from '../components/note.vue'; import XNotes from '../components/notes.vue'; -import MkButton from '../components/ui/button.vue'; +import MkRemoteCaution from '../components/remote-caution.vue'; export default Vue.extend({ i18n, @@ -41,7 +45,7 @@ export default Vue.extend({ components: { XNote, XNotes, - MkButton, + MkRemoteCaution, }, data() { return { diff --git a/src/client/pages/preferences/index.vue b/src/client/pages/preferences/index.vue index f87f875107..8ffb481c99 100644 --- a/src/client/pages/preferences/index.vue +++ b/src/client/pages/preferences/index.vue @@ -8,8 +8,10 @@ <section class="_card"> <div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div> <div class="_content"> - {{ $t('volume') }} - <input type="range" v-model="sfxVolume" min="0" max="1" step="0.1"/> + <mk-range v-model="sfxVolume" min="0" max="1" step="0.1"> + <fa slot="icon" :icon="volumeIcon"/> + <span slot="title">{{ $t('volume') }}</span> + </mk-range> </div> <div class="_content"> <mk-select v-model="sfxNote"> @@ -61,7 +63,6 @@ <template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template> </mk-switch> <mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch> - <mk-switch v-model="useNotificationsPopup">{{ $t('useNotificationsPopup') }}</mk-switch> </div> <div class="_content"> <mk-select v-model="lang"> @@ -85,12 +86,13 @@ <script lang="ts"> import Vue from 'vue'; -import { faImage, faCog, faMusic, faPlay } from '@fortawesome/free-solid-svg-icons'; +import { faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons'; import MkInput from '../../components/ui/input.vue'; import MkButton from '../../components/ui/button.vue'; import MkSwitch from '../../components/ui/switch.vue'; import MkSelect from '../../components/ui/select.vue'; import MkRadio from '../../components/ui/radio.vue'; +import MkRange from '../../components/ui/range.vue'; import XTheme from './theme.vue'; import i18n from '../../i18n'; import { langs } from '../../config'; @@ -128,6 +130,7 @@ export default Vue.extend({ MkSwitch, MkSelect, MkRadio, + MkRange }, data() { @@ -136,7 +139,7 @@ export default Vue.extend({ lang: localStorage.getItem('lang'), fontSize: localStorage.getItem('fontSize'), sounds, - faImage, faCog, faMusic, faPlay + faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } }, @@ -171,14 +174,9 @@ export default Vue.extend({ set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); } }, - useNotificationsPopup: { - get() { return this.$store.state.device.useNotificationsPopup; }, - set(value) { this.$store.commit('device/set', { key: 'useNotificationsPopup', value }); } - }, - sfxVolume: { get() { return this.$store.state.device.sfxVolume; }, - set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value }); } + set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); } }, sfxNote: { @@ -210,6 +208,12 @@ export default Vue.extend({ get() { return this.$store.state.device.sfxAntenna; }, set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); } }, + + volumeIcon: { + get() { + return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp; + } + } }, watch: { diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index b5981937ed..9f5f968901 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -3,31 +3,13 @@ <portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal> <portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal> - <div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div> - <transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear> - <div class="profile _panel" :key="user.id"> - <div class="banner-container" :style="style"> - <div class="banner" ref="banner" :style="style"></div> - <div class="fade"></div> - <div class="title"> - <mk-user-name class="name" :user="user" :nowrap="true"/> - <div class="bottom"> - <span class="username"><mk-acct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span> - <span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span> - <span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span> - </div> - </div> - <span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span> - <div class="actions" v-if="$store.getters.isSignedIn"> - <button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button> - <mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> - </div> - </div> - <mk-avatar class="avatar" :user="user" :disable-preview="true"/> + <mk-remote-caution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/> + <div class="profile _panel" :key="user.id"> + <div class="banner-container" :style="style"> + <div class="banner" ref="banner" :style="style"></div> + <div class="fade"></div> <div class="title"> - <mk-user-name :user="user" :nowrap="false" class="name"/> + <mk-user-name class="name" :user="user" :nowrap="true"/> <div class="bottom"> <span class="username"><mk-acct :user="user" :detail="true" /></span> <span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span> @@ -36,55 +18,71 @@ <span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span> </div> </div> - <div class="description"> - <mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $t('noAccountDescription') }}</p> - </div> - <div class="fields system"> - <dl class="field" v-if="user.location"> - <dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl class="field" v-if="user.birthday"> - <dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd> - </dl> + <span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span> + <div class="actions" v-if="$store.getters.isSignedIn"> + <button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button> + <mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> </div> - <div class="fields" v-if="user.fields.length > 0"> - <dl class="field" v-for="(field, i) in user.fields" :key="i"> - <dt class="name"> - <mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <div class="status" v-if="user.host === null"> - <router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }"> - <b>{{ user.notesCount | number }}</b> - <span>{{ $t('notes') }}</span> - </router-link> - <router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }"> - <b>{{ user.followingCount | number }}</b> - <span>{{ $t('following') }}</span> - </router-link> - <router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }"> - <b>{{ user.followersCount | number }}</b> - <span>{{ $t('followers') }}</span> - </router-link> + </div> + <mk-avatar class="avatar" :user="user" :disable-preview="true"/> + <div class="title"> + <mk-user-name :user="user" :nowrap="false" class="name"/> + <div class="bottom"> + <span class="username"><mk-acct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span> + <span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span> + <span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span> </div> </div> - </transition> + <div class="description"> + <mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $t('noAccountDescription') }}</p> + </div> + <div class="fields system"> + <dl class="field" v-if="user.location"> + <dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl class="field" v-if="user.birthday"> + <dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd> + </dl> + </div> + <div class="fields" v-if="user.fields.length > 0"> + <dl class="field" v-for="(field, i) in user.fields" :key="i"> + <dt class="name"> + <mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <div class="status"> + <router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }"> + <b>{{ user.notesCount | number }}</b> + <span>{{ $t('notes') }}</span> + </router-link> + <router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }"> + <b>{{ user.followingCount | number }}</b> + <span>{{ $t('following') }}</span> + </router-link> + <router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }"> + <b>{{ user.followersCount | number }}</b> + <span>{{ $t('followers') }}</span> + </router-link> + </div> + </div> <router-view :user="user"></router-view> <template v-if="$route.name == 'user'"> - <sequential-entrance class="pins"> - <x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/> - </sequential-entrance> + <div class="pins"> + <x-note v-for="note in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/> + </div> <mk-container :body-togglable="true" class="content"> <template #header><fa :icon="faImage"/>{{ $t('images') }}</template> <div> @@ -107,7 +105,7 @@ <script lang="ts"> import Vue from 'vue'; -import { faEllipsisH, faRobot, faLock, faBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons'; +import { faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons'; import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons'; import * as age from 's-age'; import XUserTimeline from './index.timeline.vue'; @@ -115,6 +113,7 @@ import XUserMenu from '../../components/user-menu.vue'; import XNote from '../../components/note.vue'; import MkFollowButton from '../../components/follow-button.vue'; import MkContainer from '../../components/ui/container.vue'; +import MkRemoteCaution from '../../components/remote-caution.vue'; import Progress from '../../scripts/loading'; import parseAcct from '../../../misc/acct/parse'; @@ -124,6 +123,7 @@ export default Vue.extend({ XNote, MkFollowButton, MkContainer, + MkRemoteCaution, XPhotos: () => import('./index.photos.vue').then(m => m.default), XActivity: () => import('./index.activity.vue').then(m => m.default), }, @@ -139,7 +139,7 @@ export default Vue.extend({ user: null, error: null, parallaxAnimationId: null, - faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt + faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt }; }, @@ -217,17 +217,6 @@ export default Vue.extend({ <style lang="scss" scoped> .mk-user-page { - > .remote-caution { - font-size: 0.8em; - padding: 16px; - margin-bottom: var(--margin); - - > a { - margin-left: 4px; - color: var(--accent); - } - } - > .profile { position: relative; margin-bottom: var(--margin); diff --git a/src/client/scripts/hotkey.ts b/src/client/scripts/hotkey.ts index ec627ab15b..672dbedde1 100644 --- a/src/client/scripts/hotkey.ts +++ b/src/client/scripts/hotkey.ts @@ -12,14 +12,22 @@ type action = { patterns: pattern[]; callback: Function; + + allowRepeat: boolean; }; const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => { const result = { patterns: [], - callback: callback + callback: callback, + allowRepeat: true } as action; + if (patterns.match(/^\(.*\)$/) !== null) { + result.allowRepeat = false; + patterns = patterns.slice(1, -1); + } + result.patterns = patterns.split('|').map(part => { const pattern = { which: [], @@ -77,6 +85,7 @@ export default { const matched = match(e, action.patterns); if (matched) { + if (!action.allowRepeat && e.repeat) return; if (el._hotkey_global && match(e, targetReservedKeys)) return; e.preventDefault(); diff --git a/src/client/store.ts b/src/client/store.ts index 3064cfdec7..29709096ee 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -40,7 +40,6 @@ const defaultDeviceSettings = { animatedMfm: true, imageNewTab: false, showFixedPostForm: false, - useNotificationsPopup: true, sfxVolume: 0.3, sfxNote: 'syuilo/down', sfxNoteMy: 'syuilo/up', @@ -101,6 +100,7 @@ export default (os: MiOS) => new Vuex.Store({ ctx.commit('settings/init', {}); ctx.commit('deviceUser/init', {}); localStorage.removeItem('i'); + document.cookie = `igi=; path=/`; }, async switchAccount(ctx, i) { diff --git a/src/client/style.scss b/src/client/style.scss index 93d4159d4d..fee64c7ca8 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -3,7 +3,7 @@ :root { --radius: 8px; --marginFull: 16px; - --marginHalf: 8px; + --marginHalf: 10px; --margin: var(--marginFull); @@ -230,7 +230,6 @@ hr { @extend ._button; color: #fff; background: var(--accent); - box-shadow: 0 6px 16px var(--accentShadow); &:not(:disabled):hover { background: var(--jkhztclx); @@ -276,23 +275,29 @@ hr { } } -._shadow { - box-shadow: 0 8px 32px var(--shadow); - - @media (max-width: 700px) { - box-shadow: 0 4px 16px var(--shadow); - } - - @media (max-width: 500px) { - box-shadow: 0 2px 8px var(--shadow); - } -} - ._panel { - @extend ._shadow; position: relative; background: var(--panel); border-radius: var(--radius); + box-shadow: 0 0 0 1px var(--divider); +} + +main ._panel { + border-radius: 0; + box-shadow: 0 1px 0 0 var(--divider), 0 -1px 0 0 var(--divider); +} + +._panel ._panel { + border-radius: 0; + box-shadow: 0 1px 0 0 var(--divider), 0 -1px 0 0 var(--divider); +} + +._panel._button { + display: flex; + width: 100%; + min-height: 48px; + align-items: center; + justify-content: center; } ._card { diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5 index 5f30d2141b..bc7c0efc09 100644 --- a/src/client/themes/_dark.json5 +++ b/src/client/themes/_dark.json5 @@ -17,9 +17,10 @@ fgHighlighted: ':lighten<3<@fg', html: '@bg', indicator: '@accent', - panel: '#111213', + panel: '#000', shadow: 'rgba(0, 0, 0, 0.1)', header: 'rgba(20, 20, 20, 0.75)', + pageBg: ':lighten<5<@bg', navBg: '@panel', navFg: '@fg', navHoverFg: ':lighten<17<@fg', @@ -33,8 +34,7 @@ divider: 'rgba(255, 255, 255, 0.1)', scrollbarHandle: 'rgba(255, 255, 255, 0.2)', scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)', - dateLabelBg: 'rgba(255, 255, 255, 0.08)', - dateLabelFg: '#fff', + dateLabelFg: '@fg', infoBg: '#253142', infoFg: '#fff', infoWarnBg: '#42321c', @@ -51,14 +51,13 @@ driveFolderBg: ':alpha<0.3<@accent', wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', badge: '#31b1ce', + messageBg: ':lighten<5<@bg', bonzsgfz: ':alpha<0<@bg', pcncwizz: ':darken<2<@panel', - vocsgcxy: 'rgba(0, 0, 0, 0.5)', yrnqrguo: 'rgba(255, 255, 255, 0.05)', nwjktjjq: 'rgba(255, 255, 255, 0.1)', geavgsxy: 'rgba(255, 255, 255, 0.05)', nhzhphzx: 'rgba(255, 255, 255, 0.15)', - tyvedwbe: 'rgba(0, 0, 0, 0.5)', bwqtlupy: 'rgba(255, 255, 255, 0.05)', jkhztclx: ':lighten<5<@accent', zbqjwygh: ':darken<5<@accent', diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5 index 2b411fb28d..adb1280420 100644 --- a/src/client/themes/_light.json5 +++ b/src/client/themes/_light.json5 @@ -20,6 +20,7 @@ panel: '#fff', shadow: 'rgba(0, 0, 0, 0.1)', header: 'rgba(255, 255, 255, 0.75)', + pageBg: '@bg', navBg: '@panel', navFg: '@fg', navHoverFg: ':darken<17<@fg', @@ -33,8 +34,7 @@ divider: 'rgba(0, 0, 0, 0.1)', scrollbarHandle: 'rgba(0, 0, 0, 0.2)', scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)', - dateLabelBg: 'rgba(0, 0, 0, 0.5)', - dateLabelFg: '#fff', + dateLabelFg: '@fg', infoBg: '#e5f5ff', infoFg: '#72818a', infoWarnBg: '#fff0db', @@ -51,14 +51,13 @@ driveFolderBg: ':alpha<0.3<@accent', wallpaperOverlay: 'rgba(255, 255, 255, 0.5)', badge: '#31b1ce', + messageBg: '@panel', bonzsgfz: ':alpha<0<@bg', pcncwizz: ':darken<2<@panel', - vocsgcxy: 'rgba(255, 255, 255, 0.5)', yrnqrguo: 'rgba(0, 0, 0, 0.05)', nwjktjjq: 'rgba(0, 0, 0, 0.1)', geavgsxy: 'rgba(0, 0, 0, 0.05)', nhzhphzx: 'rgba(0, 0, 0, 0.25)', - tyvedwbe: 'rgba(0, 0, 0, 0.1)', bwqtlupy: 'rgba(0, 0, 0, 0.05)', jkhztclx: ':lighten<5<@accent', zbqjwygh: ':darken<5<@accent', diff --git a/src/client/themes/lavender.json5 b/src/client/themes/lavender.json5 index 4eb4a54749..faa4093612 100644 --- a/src/client/themes/lavender.json5 +++ b/src/client/themes/lavender.json5 @@ -14,6 +14,5 @@ link: '@accent', mention: '@accent', hashtag: '@accent', - dateLabelBg: 'rgb(204, 186, 188)', }, } diff --git a/src/client/widgets/notifications.vue b/src/client/widgets/notifications.vue index 2a718a6666..9c1bddb2ee 100644 --- a/src/client/widgets/notifications.vue +++ b/src/client/widgets/notifications.vue @@ -3,7 +3,7 @@ <mk-container :show-header="!props.compact" class="container"> <template #header><fa :icon="faBell"/>{{ $t('notifications') }}</template> - <div class="tl"> + <div> <x-notifications/> </div> </mk-container> @@ -81,10 +81,5 @@ export default define({ flex-grow: 1; } } - - .tl { - background: var(--bg); - padding: 8px; - } } </style> diff --git a/src/client/widgets/timeline.vue b/src/client/widgets/timeline.vue index ab5664a4d8..55f78f985f 100644 --- a/src/client/widgets/timeline.vue +++ b/src/client/widgets/timeline.vue @@ -14,7 +14,7 @@ </button> </template> - <div class="tl"> + <div> <x-timeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list" :antenna="props.antenna"/> </div> </mk-container> @@ -148,11 +148,5 @@ export default define({ flex-grow: 1; } } - - .tl { - padding: 8px; - background: var(--bg); - box-sizing: border-box; - } } </style> |