diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2019-02-25 19:45:00 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-25 19:45:00 +0900 |
| commit | c0a60260c25c8f7e0c4975b6a1a4342f2b430210 (patch) | |
| tree | 5bab5271e74eb52bd13fc79d359ef30f829d6dc3 /src/client | |
| parent | Fix error (diff) | |
| download | misskey-c0a60260c25c8f7e0c4975b6a1a4342f2b430210.tar.gz misskey-c0a60260c25c8f7e0c4975b6a1a4342f2b430210.tar.bz2 misskey-c0a60260c25c8f7e0c4975b6a1a4342f2b430210.zip | |
モバイル版でもデッキを使えるように (#4366)
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Fix bug
* wip
* Update notifications.vue
* Update user-menu.vue
* deck settings
* indicate
Diffstat (limited to 'src/client')
52 files changed, 693 insertions, 645 deletions
diff --git a/src/client/app/common/define-widget.ts b/src/client/app/common/define-widget.ts index 5eb9718446..1efdbb1880 100644 --- a/src/client/app/common/define-widget.ts +++ b/src/client/app/common/define-widget.ts @@ -9,6 +9,10 @@ export default function <T extends object>(data: { widget: { type: Object }, + column: { + type: Object, + default: null + }, platform: { type: String, required: true @@ -65,10 +69,14 @@ export default function <T extends object>(data: { this.bakeProps(); - this.$root.api('i/update_widget', { - id: this.id, - data: this.props - }); + if (this.platform == 'deck') { + this.$store.commit('device/updateDeckColumn', this.column); + } else { + this.$root.api('i/update_widget', { + id: this.id, + data: this.props + }); + } } } }); diff --git a/src/client/app/common/views/components/menu.vue b/src/client/app/common/views/components/menu.vue index b43bec1ca3..a2124c992d 100644 --- a/src/client/app/common/views/components/menu.vue +++ b/src/client/app/common/views/components/menu.vue @@ -64,6 +64,10 @@ export default Vue.extend({ this.hukidasi = false; } + if (top < 0) { + top = 0; + } + popover.style.left = left + 'px'; popover.style.top = top + 'px'; diff --git a/src/client/app/common/views/components/renote.vue b/src/client/app/common/views/components/renote.vue index 591c546eed..58a0a26593 100644 --- a/src/client/app/common/views/components/renote.vue +++ b/src/client/app/common/views/components/renote.vue @@ -1,5 +1,5 @@ <template> -<div class="puqkfets" :class="{ mini }"> +<div class="puqkfets" :class="{ mini: narrow }"> <mk-avatar class="avatar" :user="note.user"/> <fa icon="retweet"/> <i18n path="@.renoted-by" tag="span"> @@ -30,13 +30,13 @@ export default Vue.extend({ note: { type: Object, required: true - }, - mini: { - type: Boolean, - required: false, + } + }, + inject: { + narrow: { default: false } - } + }, }); </script> @@ -44,25 +44,20 @@ export default Vue.extend({ .puqkfets display flex align-items center - padding 16px 32px 8px 32px + padding 8px 16px line-height 28px white-space pre color var(--renoteText) background linear-gradient(to bottom, var(--renoteGradient) 0%, var(--face) 100%) - &.mini + &:not(.mini) padding 8px 16px @media (min-width 500px) - padding 16px + padding 8px 16px @media (min-width 600px) - padding 16px 32px - - > .avatar - @media (min-width 500px) - width 28px - height 28px + padding 16px 32px 8px 32px > .avatar flex-shrink 0 diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue index 18115a3cc2..de5631dc25 100644 --- a/src/client/app/common/views/components/url-preview.vue +++ b/src/client/app/common/views/components/url-preview.vue @@ -8,7 +8,7 @@ </blockquote> </div> <div v-else class="mk-url-preview"> - <a :class="{ mini, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> + <a :class="{ mini: narrow, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> <div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"></div> <article> <header> @@ -125,10 +125,10 @@ export default Vue.extend({ required: false, default: false }, + }, - mini: { - type: Boolean, - required: false, + inject: { + narrow: { default: false } }, @@ -350,7 +350,7 @@ export default Vue.extend({ &.compact > .thumbnail - position: absolute + position absolute width 56px height 100% @@ -368,7 +368,7 @@ export default Vue.extend({ &.compact > article > header h1, p, footer - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + overflow hidden + white-space nowrap + text-overflow ellipsis </style> diff --git a/src/client/app/common/views/components/user-menu.vue b/src/client/app/common/views/components/user-menu.vue index bcf0634fa6..93fd759fd9 100644 --- a/src/client/app/common/views/components/user-menu.vue +++ b/src/client/app/common/views/components/user-menu.vue @@ -66,10 +66,11 @@ export default Vue.extend({ }, async pushList() { + const t = this.$t('select-list'); // なぜか後で参照すると null になるので最初にメモリに確保しておく const lists = await this.$root.api('users/lists/list'); const { canceled, result: listId } = await this.$root.dialog({ type: null, - title: this.$t('select-list'), + title: t, select: { items: lists.map(list => ({ value: list.id, text: list.title diff --git a/src/client/app/desktop/views/deck/deck.column-core.vue b/src/client/app/common/views/deck/deck.column-core.vue index 974c58235d..974c58235d 100644 --- a/src/client/app/desktop/views/deck/deck.column-core.vue +++ b/src/client/app/common/views/deck/deck.column-core.vue diff --git a/src/client/app/desktop/views/deck/deck.column.vue b/src/client/app/common/views/deck/deck.column.vue index 52bd8732b1..2d5cfdd843 100644 --- a/src/client/app/desktop/views/deck/deck.column.vue +++ b/src/client/app/common/views/deck/deck.column.vue @@ -30,6 +30,8 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import Menu from '../../../common/views/components/menu.vue'; import { countIf } from '../../../../../prelude/array'; +import { faArrowUp, faArrowDown } from '@fortawesome/free-solid-svg-icons'; +import { faWindowMaximize } from '@fortawesome/free-regular-svg-icons'; export default Vue.extend({ i18n: i18n('deck'), @@ -71,7 +73,8 @@ export default Vue.extend({ active: true, dragging: false, draghover: false, - dropready: false + dropready: false, + faArrowUp, faArrowDown }; }, @@ -143,7 +146,7 @@ export default Vue.extend({ toggleActive() { if (!this.isStacked) return; - const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id)); + const vms = this.$store.state.device.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id)); if (this.active && countIf(vm => vm.$el.classList.contains('active'), vms) == 1) return; this.active = !this.active; }, @@ -176,50 +179,50 @@ export default Vue.extend({ } }).then(({ canceled, result: name }) => { if (canceled) return; - this.$store.dispatch('settings/renameDeckColumn', { id: this.column.id, name }); + this.$store.commit('device/renameDeckColumn', { id: this.column.id, name }); }); } }, null, { icon: 'arrow-left', text: this.$t('swap-left'), action: () => { - this.$store.dispatch('settings/swapLeftDeckColumn', this.column.id); + this.$store.commit('device/swapLeftDeckColumn', this.column.id); } }, { icon: 'arrow-right', text: this.$t('swap-right'), action: () => { - this.$store.dispatch('settings/swapRightDeckColumn', this.column.id); + this.$store.commit('device/swapRightDeckColumn', this.column.id); } }, this.isStacked ? { - icon: 'arrow-up', + icon: faArrowUp, text: this.$t('swap-up'), action: () => { - this.$store.dispatch('settings/swapUpDeckColumn', this.column.id); + this.$store.commit('device/swapUpDeckColumn', this.column.id); } } : undefined, this.isStacked ? { - icon: 'arrow-down', + icon: faArrowDown, text: this.$t('swap-down'), action: () => { - this.$store.dispatch('settings/swapDownDeckColumn', this.column.id); + this.$store.commit('device/swapDownDeckColumn', this.column.id); } } : undefined, null, { icon: ['far', 'window-restore'], text: this.$t('stack-left'), action: () => { - this.$store.dispatch('settings/stackLeftDeckColumn', this.column.id); + this.$store.commit('device/stackLeftDeckColumn', this.column.id); } }, this.isStacked ? { - icon: ['far', 'window-maximize'], + icon: faWindowMaximize, text: this.$t('pop-right'), action: () => { - this.$store.dispatch('settings/popRightDeckColumn', this.column.id); + this.$store.commit('device/popRightDeckColumn', this.column.id); } } : undefined, null, { icon: ['far', 'trash-alt'], text: this.$t('remove'), action: () => { - this.$store.dispatch('settings/removeDeckColumn', this.column.id); + this.$store.commit('device/removeDeckColumn', this.column.id); } }]; @@ -303,7 +306,7 @@ export default Vue.extend({ const id = e.dataTransfer.getData('mk-deck-column'); if (id != null && id != '') { - this.$store.dispatch('settings/swapDeckColumn', { + this.$store.commit('device/swapDeckColumn', { a: this.column.id, b: id }); @@ -418,5 +421,6 @@ export default Vue.extend({ height "calc(100% - %s)" % $header-height overflow auto overflow-x hidden + -webkit-overflow-scrolling touch </style> diff --git a/src/client/app/desktop/views/deck/deck.direct-column.vue b/src/client/app/common/views/deck/deck.direct-column.vue index c68a361a9f..c68a361a9f 100644 --- a/src/client/app/desktop/views/deck/deck.direct-column.vue +++ b/src/client/app/common/views/deck/deck.direct-column.vue diff --git a/src/client/app/desktop/views/deck/deck.direct.vue b/src/client/app/common/views/deck/deck.direct.vue index 2618363b14..2618363b14 100644 --- a/src/client/app/desktop/views/deck/deck.direct.vue +++ b/src/client/app/common/views/deck/deck.direct.vue diff --git a/src/client/app/desktop/views/deck/deck.explore-column.vue b/src/client/app/common/views/deck/deck.explore-column.vue index 53db677b37..53db677b37 100644 --- a/src/client/app/desktop/views/deck/deck.explore-column.vue +++ b/src/client/app/common/views/deck/deck.explore-column.vue diff --git a/src/client/app/desktop/views/deck/deck.favorites-column.vue b/src/client/app/common/views/deck/deck.favorites-column.vue index 67610f8b9f..67610f8b9f 100644 --- a/src/client/app/desktop/views/deck/deck.favorites-column.vue +++ b/src/client/app/common/views/deck/deck.favorites-column.vue diff --git a/src/client/app/desktop/views/deck/deck.featured-column.vue b/src/client/app/common/views/deck/deck.featured-column.vue index 776957cc4d..776957cc4d 100644 --- a/src/client/app/desktop/views/deck/deck.featured-column.vue +++ b/src/client/app/common/views/deck/deck.featured-column.vue diff --git a/src/client/app/desktop/views/deck/deck.hashtag-column.vue b/src/client/app/common/views/deck/deck.hashtag-column.vue index db67df5305..db67df5305 100644 --- a/src/client/app/desktop/views/deck/deck.hashtag-column.vue +++ b/src/client/app/common/views/deck/deck.hashtag-column.vue diff --git a/src/client/app/desktop/views/deck/deck.hashtag-tl.vue b/src/client/app/common/views/deck/deck.hashtag-tl.vue index 07d96f82c4..07d96f82c4 100644 --- a/src/client/app/desktop/views/deck/deck.hashtag-tl.vue +++ b/src/client/app/common/views/deck/deck.hashtag-tl.vue diff --git a/src/client/app/desktop/views/deck/deck.list-tl.vue b/src/client/app/common/views/deck/deck.list-tl.vue index d1887990f2..d1887990f2 100644 --- a/src/client/app/desktop/views/deck/deck.list-tl.vue +++ b/src/client/app/common/views/deck/deck.list-tl.vue diff --git a/src/client/app/desktop/views/deck/deck.mentions-column.vue b/src/client/app/common/views/deck/deck.mentions-column.vue index b7f3290d0d..b7f3290d0d 100644 --- a/src/client/app/desktop/views/deck/deck.mentions-column.vue +++ b/src/client/app/common/views/deck/deck.mentions-column.vue diff --git a/src/client/app/desktop/views/deck/deck.mentions.vue b/src/client/app/common/views/deck/deck.mentions.vue index 1efd778226..1efd778226 100644 --- a/src/client/app/desktop/views/deck/deck.mentions.vue +++ b/src/client/app/common/views/deck/deck.mentions.vue diff --git a/src/client/app/desktop/views/deck/deck.note-column.vue b/src/client/app/common/views/deck/deck.note-column.vue index 5c5022e30b..ca798707bb 100644 --- a/src/client/app/desktop/views/deck/deck.note-column.vue +++ b/src/client/app/common/views/deck/deck.note-column.vue @@ -11,7 +11,7 @@ <a :href="note.url || note.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> </details> </div> - <x-note :note="note" :detail="true" :mini="true"/> + <mk-note :note="note" :detail="true"/> </div> </x-column> </template> @@ -21,14 +21,12 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import XColumn from './deck.column.vue'; import XNotes from './deck.notes.vue'; -import XNote from '../components/note.vue'; export default Vue.extend({ i18n: i18n(), components: { XColumn, XNotes, - XNote }, data() { diff --git a/src/client/app/desktop/views/deck/deck.notes.vue b/src/client/app/common/views/deck/deck.notes.vue index 810addd505..f94eb8fd38 100644 --- a/src/client/app/desktop/views/deck/deck.notes.vue +++ b/src/client/app/common/views/deck/deck.notes.vue @@ -13,12 +13,12 @@ <!-- トランジションを有効にするとなぜかメモリリークする --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition notes" ref="notes" tag="div"> <template v-for="(note, i) in _notes"> - <x-note + <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" - :mini="true"/> + /> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span> @@ -39,17 +39,12 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import shouldMuteNote from '../../../common/scripts/should-mute-note'; -import XNote from '../components/note.vue'; const displayLimit = 20; export default Vue.extend({ i18n: i18n(), - components: { - XNote - }, - inject: ['column', 'isScrollTop', 'count'], props: { diff --git a/src/client/app/desktop/views/deck/deck.notification.vue b/src/client/app/common/views/deck/deck.notification.vue index 56915f2d9c..6a116260e5 100644 --- a/src/client/app/desktop/views/deck/deck.notification.vue +++ b/src/client/app/common/views/deck/deck.notification.vue @@ -81,15 +81,15 @@ </div> <template v-if="notification.type == 'quote'"> - <x-note :note="notification.note" @update:note="onNoteUpdated" :mini="true"/> + <mk-note :note="notification.note" @update:note="onNoteUpdated"/> </template> <template v-if="notification.type == 'reply'"> - <x-note :note="notification.note" @update:note="onNoteUpdated" :mini="true"/> + <mk-note :note="notification.note" @update:note="onNoteUpdated"/> </template> <template v-if="notification.type == 'mention'"> - <x-note :note="notification.note" @update:note="onNoteUpdated" :mini="true"/> + <mk-note :note="notification.note" @update:note="onNoteUpdated"/> </template> </div> </template> @@ -97,12 +97,8 @@ <script lang="ts"> import Vue from 'vue'; import getNoteSummary from '../../../../../misc/get-note-summary'; -import XNote from '../components/note.vue'; export default Vue.extend({ - components: { - XNote - }, props: ['notification'], data() { return { diff --git a/src/client/app/desktop/views/deck/deck.notifications-column.vue b/src/client/app/common/views/deck/deck.notifications-column.vue index c81c8b7733..c81c8b7733 100644 --- a/src/client/app/desktop/views/deck/deck.notifications-column.vue +++ b/src/client/app/common/views/deck/deck.notifications-column.vue diff --git a/src/client/app/desktop/views/deck/deck.notifications.vue b/src/client/app/common/views/deck/deck.notifications.vue index 5130140685..bb93e5e75e 100644 --- a/src/client/app/desktop/views/deck/deck.notifications.vue +++ b/src/client/app/common/views/deck/deck.notifications.vue @@ -199,7 +199,7 @@ export default Vue.extend({ display block width 100% padding 16px - color #555 + color var(--text) border-top solid var(--lineWidth) rgba(#000, 0.05) &:hover diff --git a/src/client/app/desktop/views/deck/deck.search-column.vue b/src/client/app/common/views/deck/deck.search-column.vue index fb0ba5f6e4..fb0ba5f6e4 100644 --- a/src/client/app/desktop/views/deck/deck.search-column.vue +++ b/src/client/app/common/views/deck/deck.search-column.vue diff --git a/src/client/app/desktop/views/deck/deck.tl-column.vue b/src/client/app/common/views/deck/deck.tl-column.vue index 10f007f599..d53aabaea5 100644 --- a/src/client/app/desktop/views/deck/deck.tl-column.vue +++ b/src/client/app/common/views/deck/deck.tl-column.vue @@ -90,7 +90,7 @@ export default Vue.extend({ methods: { onChangeSettings(v) { - this.$store.dispatch('settings/saveDeck'); + this.$store.commit('device/updateDeckColumn', this.column); }, focus() { diff --git a/src/client/app/desktop/views/deck/deck.tl.vue b/src/client/app/common/views/deck/deck.tl.vue index 35cdfa704f..35cdfa704f 100644 --- a/src/client/app/desktop/views/deck/deck.tl.vue +++ b/src/client/app/common/views/deck/deck.tl.vue diff --git a/src/client/app/desktop/views/deck/deck.user-column.home.vue b/src/client/app/common/views/deck/deck.user-column.home.vue index 93a5a56c03..15c7b794b9 100644 --- a/src/client/app/desktop/views/deck/deck.user-column.home.vue +++ b/src/client/app/common/views/deck/deck.user-column.home.vue @@ -3,7 +3,7 @@ <ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true"> <template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template> <div> - <x-note v-for="n in user.pinnedNotes" :key="n.id" :note="n" :mini="true"/> + <mk-note v-for="n in user.pinnedNotes" :key="n.id" :note="n"/> </div> </ui-container> <ui-container v-if="images.length > 0" :body-togglable="true" @@ -40,7 +40,6 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import XNotes from './deck.notes.vue'; -import XNote from '../components/note.vue'; import { concat } from '../../../../../prelude/array'; import ApexCharts from 'apexcharts'; @@ -51,7 +50,6 @@ export default Vue.extend({ components: { XNotes, - XNote }, props: { diff --git a/src/client/app/desktop/views/deck/deck.user-column.vue b/src/client/app/common/views/deck/deck.user-column.vue index 47c291db06..47c291db06 100644 --- a/src/client/app/desktop/views/deck/deck.user-column.vue +++ b/src/client/app/common/views/deck/deck.user-column.vue diff --git a/src/client/app/desktop/views/deck/deck.vue b/src/client/app/common/views/deck/deck.vue index 62ed0a8ce3..b91df45620 100644 --- a/src/client/app/desktop/views/deck/deck.vue +++ b/src/client/app/common/views/deck/deck.vue @@ -20,7 +20,6 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import XColumnCore from './deck.column-core.vue'; import Menu from '../../../common/views/components/menu.vue'; -import MkUserListsWindow from '../components/user-lists-window.vue'; import * as uuid from 'uuid'; @@ -32,14 +31,14 @@ export default Vue.extend({ computed: { columns(): any[] { - if (this.$store.state.settings.deck == null) return []; - return this.$store.state.settings.deck.columns; + if (this.$store.state.device.deck == null) return []; + return this.$store.state.device.deck.columns; }, layout(): any[] { - if (this.$store.state.settings.deck == null) return []; - if (this.$store.state.settings.deck.layout == null) return this.$store.state.settings.deck.columns.map(c => [c.id]); - return this.$store.state.settings.deck.layout; + if (this.$store.state.device.deck == null) return []; + if (this.$store.state.device.deck.layout == null) return this.$store.state.device.deck.columns.map(c => [c.id]); + return this.$store.state.device.deck.layout; }, style(): any { @@ -69,45 +68,50 @@ export default Vue.extend({ provide() { return { - getColumnVm: this.getColumnVm + getColumnVm: this.getColumnVm, + narrow: true }; }, created() { - if (this.$store.state.settings.deck == null) { + if (this.$store.state.device.deck == null) { const deck = { columns: [/*{ type: 'widgets', widgets: [] }, */{ id: uuid(), - type: 'home' + type: 'home', + name: null, }, { id: uuid(), - type: 'notifications' + type: 'notifications', + name: null, }, { id: uuid(), - type: 'local' + type: 'local', + name: null, }, { id: uuid(), - type: 'global' + type: 'global', + name: null, }] }; deck.layout = deck.columns.map(c => [c.id]); - this.$store.dispatch('settings/set', { + this.$store.commit('device/set', { key: 'deck', value: deck }); } // 互換性のため - if (this.$store.state.settings.deck != null && this.$store.state.settings.deck.layout == null) { - this.$store.dispatch('settings/set', { + if (this.$store.state.device.deck != null && this.$store.state.device.deck.layout == null) { + this.$store.commit('device/set', { key: 'deck', - value: Object.assign({}, this.$store.state.settings.deck, { - layout: this.$store.state.settings.deck.columns.map(c => [c.id]) + value: Object.assign({}, this.$store.state.device.deck, { + layout: this.$store.state.device.deck.columns.map(c => [c.id]) }) }); } @@ -134,7 +138,7 @@ export default Vue.extend({ icon: 'home', text: this.$t('@deck.home'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'home' }); @@ -143,7 +147,7 @@ export default Vue.extend({ icon: ['far', 'comments'], text: this.$t('@deck.local'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'local' }); @@ -152,7 +156,7 @@ export default Vue.extend({ icon: 'share-alt', text: this.$t('@deck.hybrid'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'hybrid' }); @@ -161,7 +165,7 @@ export default Vue.extend({ icon: 'globe', text: this.$t('@deck.global'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'global' }); @@ -170,7 +174,7 @@ export default Vue.extend({ icon: 'at', text: this.$t('@deck.mentions'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'mentions' }); @@ -179,7 +183,7 @@ export default Vue.extend({ icon: ['far', 'envelope'], text: this.$t('@deck.direct'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'direct' }); @@ -187,15 +191,23 @@ export default Vue.extend({ }, { icon: 'list', text: this.$t('@deck.list'), - action: () => { - const w = this.$root.new(MkUserListsWindow); - w.$once('choosen', list => { - this.$store.dispatch('settings/addDeckColumn', { - id: uuid(), - type: 'list', - list: list - }); - w.close(); + action: async () => { + const lists = await this.$root.api('users/lists/list'); + const { canceled, result: listId } = await this.$root.dialog({ + type: null, + title: this.$t('@deck.select-list'), + select: { + items: lists.map(list => ({ + value: list.id, text: list.title + })) + }, + showCancelButton: true + }); + if (canceled) return; + this.$store.commit('device/addDeckColumn', { + id: uuid(), + type: 'list', + list: lists.find(l => l.id === listId) }); } }, { @@ -207,7 +219,7 @@ export default Vue.extend({ input: true }).then(({ canceled, result: title }) => { if (canceled) return; - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'hashtag', tagTlId: this.$store.state.settings.tagTimelines.find(x => x.title == title).id @@ -218,7 +230,7 @@ export default Vue.extend({ icon: ['far', 'bell'], text: this.$t('@deck.notifications'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'notifications' }); @@ -227,7 +239,7 @@ export default Vue.extend({ icon: 'calculator', text: this.$t('@deck.widgets'), action: () => { - this.$store.dispatch('settings/addDeckColumn', { + this.$store.commit('device/addDeckColumn', { id: uuid(), type: 'widgets', widgets: [] @@ -316,6 +328,8 @@ export default Vue.extend({ flex 1 padding 16px 0 16px 16px overflow auto + overflow-y hidden + -webkit-overflow-scrolling touch > div margin-right 8px diff --git a/src/client/app/desktop/views/deck/deck.widgets-column.vue b/src/client/app/common/views/deck/deck.widgets-column.vue index 9c5619cc2d..47a584a53a 100644 --- a/src/client/app/desktop/views/deck/deck.widgets-column.vue +++ b/src/client/app/common/views/deck/deck.widgets-column.vue @@ -37,12 +37,12 @@ > <div v-for="widget in column.widgets" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="widgetFunc(widget.id)"> <button class="remove" @click="removeWidget(widget)"><fa icon="times"/></button> - <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/> + <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck" :column="column"/> </div> </x-draggable> </template> <template v-else> - <component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/> + <component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck" :column="column"/> </template> </div> </x-column> @@ -109,7 +109,7 @@ export default Vue.extend({ }, addWidget() { - this.$store.dispatch('settings/addDeckWidget', { + this.$store.commit('device/addDeckWidget', { id: this.column.id, widget: { name: this.widgetAdderSelected, @@ -122,14 +122,14 @@ export default Vue.extend({ }, removeWidget(widget) { - this.$store.dispatch('settings/removeDeckWidget', { + this.$store.commit('device/removeDeckWidget', { id: this.column.id, widget }); }, saveWidgets() { - this.$store.dispatch('settings/saveDeck'); + this.$store.commit('device/updateDeckColumn', this.column); } } }); diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index e9b6ebfc28..5d6bf1c448 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -13,7 +13,7 @@ import fuckAdBlock from '../common/scripts/fuck-ad-block'; import composeNotification from '../common/scripts/compose-notification'; import MkHome from './views/home/home.vue'; -import MkDeck from './views/deck/deck.vue'; +import MkDeck from '../common/views/deck/deck.vue'; import MkSelectDrive from './views/pages/selectdrive.vue'; import MkDrive from './views/pages/drive.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue'; @@ -123,30 +123,24 @@ init(async (launch, os) => { require('./views/components'); require('./views/widgets'); - os.store.commit('device/set', { - key: 'inDeckMode', - value: os.store.getters.isSignedIn && os.store.state.device.deckMode - && (document.location.pathname === '/' || window.performance.navigation.type === 1) - }); - // Init router const router = new VueRouter({ mode: 'history', routes: [ os.store.state.device.inDeckMode ? { path: '/', name: 'index', component: MkDeck, children: [ - { path: '/@:user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default), children: [ - { path: '', name: 'user', component: () => import('./views/deck/deck.user-column.home.vue').then(m => m.default) }, + { path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [ + { path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) }, { path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) }, { path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) }, ]}, - { path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) }, - { path: '/search', component: () => import('./views/deck/deck.search-column.vue').then(m => m.default) }, - { path: '/tags/:tag', name: 'tag', component: () => import('./views/deck/deck.hashtag-column.vue').then(m => m.default) }, - { path: '/featured', name: 'featured', component: () => import('./views/deck/deck.featured-column.vue').then(m => m.default) }, - { path: '/explore', name: 'explore', component: () => import('./views/deck/deck.explore-column.vue').then(m => m.default) }, - { path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('./views/deck/deck.explore-column.vue').then(m => m.default) }, - { path: '/i/favorites', component: () => import('./views/deck/deck.favorites-column.vue').then(m => m.default) } + { path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) }, + { path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) }, + { path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) }, + { path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) }, + { path: '/explore', name: 'explore', component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) }, + { path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) }, + { path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) } ]} : { path: '/', component: MkHome, children: [ { path: '', name: 'index', component: MkHomeTimeline }, diff --git a/src/client/app/desktop/views/components/index.ts b/src/client/app/desktop/views/components/index.ts index bee10b940a..76ab7b5fec 100644 --- a/src/client/app/desktop/views/components/index.ts +++ b/src/client/app/desktop/views/components/index.ts @@ -2,6 +2,7 @@ import Vue from 'vue'; import ui from './ui.vue'; import uiNotification from './ui-notification.vue'; +import note from './note.vue'; import notes from './notes.vue'; import subNoteContent from './sub-note-content.vue'; import window from './window.vue'; @@ -20,6 +21,7 @@ import uiContainer from './ui-container.vue'; Vue.component('mk-ui', ui); Vue.component('mk-ui-notification', uiNotification); +Vue.component('mk-note', note); Vue.component('mk-notes', notes); Vue.component('mk-sub-note-content', subNoteContent); Vue.component('mk-window', window); diff --git a/src/client/app/desktop/views/components/note-preview.vue b/src/client/app/desktop/views/components/note-preview.vue index 1bc09034e9..3b1e71e168 100644 --- a/src/client/app/desktop/views/components/note-preview.vue +++ b/src/client/app/desktop/views/components/note-preview.vue @@ -1,6 +1,6 @@ <template> <div class="qiziqtywpuaucsgarwajitwaakggnisj" :title="title"> - <mk-avatar class="avatar" :user="note.user" v-if="!mini"/> + <mk-avatar class="avatar" :user="note.user" v-if="!narrow"/> <div class="main"> <mk-note-header class="header" :note="note" :mini="true"/> <div class="body"> @@ -25,9 +25,10 @@ export default Vue.extend({ type: Object, required: true }, - mini: { - type: Boolean, - required: false, + }, + + inject: { + narrow: { default: false } }, diff --git a/src/client/app/desktop/views/components/note.sub.vue b/src/client/app/desktop/views/components/note.sub.vue index 11b209da22..bd2dd1bc08 100644 --- a/src/client/app/desktop/views/components/note.sub.vue +++ b/src/client/app/desktop/views/components/note.sub.vue @@ -1,5 +1,5 @@ <template> -<div class="tkfdzaxtkdeianobciwadajxzbddorql" :class="{ mini }" :title="title"> +<div class="tkfdzaxtkdeianobciwadajxzbddorql" :class="{ mini: narrow }" :title="title"> <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <mk-note-header class="header" :note="note"/> @@ -24,10 +24,11 @@ export default Vue.extend({ note: { type: Object, required: true - }, - mini: { - type: Boolean, - required: false, + } + }, + + inject: { + narrow: { default: false } }, diff --git a/src/client/app/desktop/views/components/note.vue b/src/client/app/desktop/views/components/note.vue index 9d968a86c9..2e9a1f9267 100644 --- a/src/client/app/desktop/views/components/note.vue +++ b/src/client/app/desktop/views/components/note.vue @@ -1,23 +1,23 @@ <template> <div class="note" - :class="{ mini }" + :class="{ mini: narrow }" v-show="(this.$store.state.settings.remainDeletedNote || appearNote.deletedAt == null) && !hideThisNote" :tabindex="appearNote.deletedAt == null ? '-1' : null" v-hotkey="keymap" :title="title" > <div class="conversation" v-if="detail && conversation.length > 0"> - <x-sub v-for="note in conversation" :key="note.id" :note="note" :mini="mini"/> + <x-sub v-for="note in conversation" :key="note.id" :note="note"/> </div> <div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)"> - <x-sub :note="appearNote.reply" :mini="mini"/> + <x-sub :note="appearNote.reply"/> </div> <mk-renote class="renote" v-if="isRenote" :note="note"/> - <article> + <article class="article"> <mk-avatar class="avatar" :user="appearNote.user"/> <div class="main"> - <mk-note-header class="header" :note="appearNote" :mini="mini"/> + <mk-note-header class="header" :note="appearNote"/> <div class="body" v-if="appearNote.deletedAt == null"> <p v-if="appearNote.cw != null" class="cw"> <mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" /> @@ -35,12 +35,12 @@ </div> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> - <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote" :mini="mini"/></div> - <mk-url-preview v-for="url in urls" :url="url" :key="url" :mini="mini" :compact="compact"/> + <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> + <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> </div> </div> <footer v-if="appearNote.deletedAt == null"> - <span class="app" v-if="appearNote.app && mini && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> + <span class="app" v-if="appearNote.app && narrow && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> <mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/> <button class="replyButton" @click="reply()" :title="$t('reply')"> <template v-if="appearNote.reply"><fa icon="reply-all"/></template> @@ -70,7 +70,7 @@ </div> </article> <div class="replies" v-if="detail && replies.length > 0"> - <x-sub v-for="note in replies" :key="note.id" :note="note" :mini="mini"/> + <x-sub v-for="note in replies" :key="note.id" :note="note"/> </div> </div> </template> @@ -110,9 +110,10 @@ export default Vue.extend({ required: false, default: false }, - mini: { - type: Boolean, - required: false, + }, + + inject: { + narrow: { default: false } }, @@ -152,7 +153,7 @@ export default Vue.extend({ border-bottom solid var(--lineWidth) var(--faceDivider) &.mini - font-size 14px + font-size 13px > .renote padding 8px 16px 0 16px @@ -161,7 +162,7 @@ export default Vue.extend({ width 20px height 20px - > article + > .article padding 16px 16px 4px > .avatar @@ -189,7 +190,7 @@ export default Vue.extend({ > .renote + article padding-top 8px - > article + > .article display flex padding 28px 32px 18px 32px diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index 198f29b69d..cf24c16883 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -17,7 +17,7 @@ <!-- トランジションを有効にするとなぜかメモリリークする --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes"> <template v-for="(note, i) in _notes"> - <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/> + <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span> @@ -39,17 +39,12 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import * as config from '../../../config'; import shouldMuteNote from '../../../common/scripts/should-mute-note'; -import XNote from './note.vue'; const displayLimit = 30; export default Vue.extend({ i18n: i18n(), - components: { - XNote - }, - props: { makePromise: { required: true diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index 41913f7eea..1c0bc9929d 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -115,18 +115,18 @@ <ui-switch v-model="remainDeletedNote">{{ $t('remain-deleted-note') }}</ui-switch> </section> <section> - <header>{{ $t('deck-column-align') }}</header> - <ui-radio v-model="deckColumnAlign" value="center">{{ $t('deck-column-align-center') }}</ui-radio> - <ui-radio v-model="deckColumnAlign" value="left">{{ $t('deck-column-align-left') }}</ui-radio> - <ui-radio v-model="deckColumnAlign" value="flexible">{{ $t('deck-column-align-flexible') }}</ui-radio> + <header>{{ $t('@.deck-column-align') }}</header> + <ui-radio v-model="deckColumnAlign" value="center">{{ $t('@.deck-column-align-center') }}</ui-radio> + <ui-radio v-model="deckColumnAlign" value="left">{{ $t('@.deck-column-align-left') }}</ui-radio> + <ui-radio v-model="deckColumnAlign" value="flexible">{{ $t('@.deck-column-align-flexible') }}</ui-radio> </section> <section> - <header>{{ $t('deck-column-width') }}</header> - <ui-radio v-model="deckColumnWidth" value="narrow">{{ $t('deck-column-width-narrow') }}</ui-radio> - <ui-radio v-model="deckColumnWidth" value="narrower">{{ $t('deck-column-width-narrower') }}</ui-radio> - <ui-radio v-model="deckColumnWidth" value="normal">{{ $t('deck-column-width-normal') }}</ui-radio> - <ui-radio v-model="deckColumnWidth" value="wider">{{ $t('deck-column-width-wider') }}</ui-radio> - <ui-radio v-model="deckColumnWidth" value="wide">{{ $t('deck-column-width-wide') }}</ui-radio> + <header>{{ $t('@.deck-column-width') }}</header> + <ui-radio v-model="deckColumnWidth" value="narrow">{{ $t('@.deck-column-width-narrow') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="narrower">{{ $t('@.deck-column-width-narrower') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="normal">{{ $t('@.deck-column-width-normal') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="wider">{{ $t('@.deck-column-width-wider') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="wide">{{ $t('@.deck-column-width-wide') }}</ui-radio> </section> <section> <ui-switch v-model="games_reversi_showBoardLabels">{{ $t('@.show-reversi-board-labels') }}</ui-switch> diff --git a/src/client/app/init.ts b/src/client/app/init.ts index 83ec30952b..fa1147f2b3 100644 --- a/src/client/app/init.ts +++ b/src/client/app/init.ts @@ -504,6 +504,13 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS) return [app, os] as [Vue, MiOS]; }; + // Deck mode + os.store.commit('device/set', { + key: 'inDeckMode', + value: os.store.getters.isSignedIn && os.store.state.device.deckMode + && (document.location.pathname === '/' || window.performance.navigation.type === 1) + }); + callback(launch, os); }); }; diff --git a/src/client/app/mios.ts b/src/client/app/mios.ts index 4d6f8a8246..eb8af96499 100644 --- a/src/client/app/mios.ts +++ b/src/client/app/mios.ts @@ -283,7 +283,7 @@ export default class MiOS extends EventEmitter { }); main.on('widgetUpdated', x => { - this.store.commit('settings/setWidget', { + this.store.commit('settings/updateWidget', { id: x.id, data: x.data }); diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts index fbe117c4ac..688beac9ee 100644 --- a/src/client/app/mobile/script.ts +++ b/src/client/app/mobile/script.ts @@ -11,10 +11,10 @@ import './style.styl'; import init from '../init'; import MkIndex from './views/pages/index.vue'; +import MkDeck from '../common/views/deck/deck.vue'; import MkSignup from './views/pages/signup.vue'; import MkSelectDrive from './views/pages/selectdrive.vue'; import MkDrive from './views/pages/drive.vue'; -import MkNotifications from './views/pages/notifications.vue'; import MkWidgets from './views/pages/widgets.vue'; import MkMessaging from './views/pages/messaging.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue'; @@ -37,7 +37,7 @@ import FolderChooser from './views/components/drive-folder-chooser.vue'; /** * init */ -init((launch) => { +init((launch, os) => { Vue.mixin({ data() { return { @@ -114,10 +114,26 @@ init((launch) => { const router = new VueRouter({ mode: 'history', routes: [ - { path: '/', name: 'index', component: MkIndex }, + ...(os.store.state.device.inDeckMode + ? [{ path: '/', name: 'index', component: MkDeck, children: [ + { path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [ + { path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) }, + { path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) }, + { path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) }, + ]}, + { path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) }, + { path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) }, + { path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) }, + { path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) }, + { path: '/explore', name: 'explore', component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) }, + { path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) }, + { path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) } + ]}] + : [ + { path: '/', name: 'index', component: MkIndex }, + ]), { path: '/signup', name: 'signup', component: MkSignup }, { path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) }, - { path: '/i/notifications', name: 'notifications', component: MkNotifications }, { path: '/i/favorites', name: 'favorites', component: MkFavorites }, { path: '/i/lists', name: 'user-lists', component: MkUserLists }, { path: '/i/lists/:list', name: 'user-list', component: MkUserList }, diff --git a/src/client/app/mobile/views/components/note-preview.vue b/src/client/app/mobile/views/components/note-preview.vue index 565a38de32..1dbbddaa62 100644 --- a/src/client/app/mobile/views/components/note-preview.vue +++ b/src/client/app/mobile/views/components/note-preview.vue @@ -1,6 +1,6 @@ <template> -<div class="yohlumlkhizgfkvvscwfcrcggkotpvry" :class="{ smart: $store.state.device.postStyle == 'smart' }"> - <mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/> +<div class="yohlumlkhizgfkvvscwfcrcggkotpvry" :class="{ smart: $store.state.device.postStyle == 'smart', mini: narrow }"> + <mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart' && !narrow"/> <div class="main"> <mk-note-header class="header" :note="note" :mini="true"/> <div class="body"> @@ -27,6 +27,12 @@ export default Vue.extend({ } }, + inject: { + narrow: { + default: false + } + }, + data() { return { showContent: false @@ -43,11 +49,25 @@ export default Vue.extend({ overflow hidden font-size 10px - @media (min-width 350px) - font-size 12px + &:not(.mini) + + @media (min-width 350px) + font-size 12px + + @media (min-width 500px) + font-size 14px - @media (min-width 500px) - font-size 14px + > .avatar + + @media (min-width 350px) + margin 0 10px 0 0 + width 44px + height 44px + + @media (min-width 500px) + margin 0 12px 0 0 + width 48px + height 48px &.smart > .main @@ -64,16 +84,6 @@ export default Vue.extend({ height 40px border-radius 8px - @media (min-width 350px) - margin 0 10px 0 0 - width 44px - height 44px - - @media (min-width 500px) - margin 0 12px 0 0 - width 48px - height 48px - > .main flex 1 min-width 0 diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue index 2d4bdbf420..0f7363b2aa 100644 --- a/src/client/app/mobile/views/components/note.sub.vue +++ b/src/client/app/mobile/views/components/note.sub.vue @@ -1,5 +1,5 @@ <template> -<div class="zlrxdaqttccpwhpaagdmkawtzklsccam" :class="{ smart: $store.state.device.postStyle == 'smart' }"> +<div class="zlrxdaqttccpwhpaagdmkawtzklsccam" :class="{ smart: $store.state.device.postStyle == 'smart', mini: narrow }"> <mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/> <div class="main"> <mk-note-header class="header" :note="note" :mini="true"/> @@ -32,6 +32,12 @@ export default Vue.extend({ } }, + inject: { + narrow: { + default: false + } + }, + data() { return { showContent: false @@ -47,14 +53,28 @@ export default Vue.extend({ font-size 10px background var(--subNoteBg) - @media (min-width 350px) - font-size 12px + &:not(.mini) + + @media (min-width 350px) + font-size 12px + + @media (min-width 500px) + font-size 14px - @media (min-width 500px) - font-size 14px + @media (min-width 600px) + padding 24px 32px - @media (min-width 600px) - padding 24px 32px + > .avatar + + @media (min-width 350px) + margin-right 10px + width 42px + height 42px + + @media (min-width 500px) + margin-right 14px + width 50px + height 50px &.smart > .main @@ -71,16 +91,6 @@ export default Vue.extend({ height 38px border-radius 8px - @media (min-width 350px) - margin-right 10px - width 42px - height 42px - - @media (min-width 500px) - margin-right 14px - width 50px - height 50px - > .main flex 1 min-width 0 diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 564069dade..16ee2677b4 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -3,14 +3,14 @@ class="note" v-show="appearNote.deletedAt == null && !hideThisNote" :tabindex="appearNote.deletedAt == null ? '-1' : null" - :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }" + :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart', mini: narrow }" v-hotkey="keymap" > <div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)"> <x-sub :note="appearNote.reply"/> </div> - <mk-renote class="renote" v-if="isRenote" :note="note" mini/> - <article> + <mk-renote class="renote" v-if="isRenote" :note="note"/> + <article class="article"> <mk-avatar class="avatar" :user="appearNote.user" v-if="$store.state.device.postStyle != 'smart'"/> <div class="main"> <mk-note-header class="header" :note="appearNote" :mini="true"/> @@ -30,7 +30,7 @@ <mk-media-list :media-list="appearNote.files"/> </div> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> - <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> + <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> </div> @@ -91,19 +91,20 @@ export default Vue.extend({ type: Object, required: true }, - compact: { - type: Boolean, - required: false, + }, + + inject: { + narrow: { default: false } - } + }, }); </script> <style lang="stylus" scoped> .note overflow hidden - font-size 12px + font-size 13px border-bottom solid var(--lineWidth) var(--faceDivider) &:focus @@ -123,29 +124,53 @@ export default Vue.extend({ &:last-of-type border-bottom none - @media (min-width 350px) - font-size 14px + &:not(.mini) + + @media (min-width 350px) + font-size 14px + + @media (min-width 500px) + font-size 16px - @media (min-width 500px) - font-size 16px + > .article + @media (min-width 600px) + padding 32px 32px 22px + + > .avatar + @media (min-width 350px) + width 48px + height 48px + border-radius 6px + + @media (min-width 500px) + margin-right 16px + width 58px + height 58px + border-radius 8px + + > .main + > .header + @media (min-width 500px) + margin-bottom 2px + + > .body + @media (min-width 700px) + font-size 1.1em &.smart - > article + > .article > .main > header align-items center margin-bottom 4px - > .renote + article + > .renote + .article padding-top 8px - > article + > .article display flex padding 16px 16px 9px - @media (min-width 600px) - padding 32px 32px 22px - > .avatar flex-shrink 0 display block @@ -157,29 +182,11 @@ export default Vue.extend({ //position sticky //top 62px - @media (min-width 350px) - width 48px - height 48px - border-radius 6px - - @media (min-width 500px) - margin-right 16px - width 58px - height 58px - border-radius 8px - > .main flex 1 min-width 0 - > .header - @media (min-width 500px) - margin-bottom 2px - > .body - @media (min-width 700px) - font-size 1.1em - > .cw cursor default display block diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index 0d85e321d5..a65482329f 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -13,7 +13,7 @@ <!-- トランジションを有効にするとなぜかメモリリークする --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div"> <template v-for="(note, i) in _notes"> - <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true"/> + <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span> diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue index 3f9cefd00c..5308d96533 100644 --- a/src/client/app/mobile/views/components/notification.vue +++ b/src/client/app/mobile/views/components/notification.vue @@ -116,15 +116,6 @@ export default Vue.extend({ font-size 12px overflow-wrap break-word - @media (min-width 350px) - font-size 14px - - @media (min-width 500px) - font-size 16px - - @media (min-width 600px) - padding 24px 32px - &:after content "" display block @@ -137,18 +128,11 @@ export default Vue.extend({ height 36px border-radius 6px - @media (min-width 500px) - width 42px - height 42px - > div float right width calc(100% - 36px) padding-left 8px - @media (min-width 500px) - width calc(100% - 42px) - > header display flex align-items baseline diff --git a/src/client/app/mobile/views/components/notifications.vue b/src/client/app/mobile/views/components/notifications.vue index 37f953edeb..cb8783fa3b 100644 --- a/src/client/app/mobile/views/components/notifications.vue +++ b/src/client/app/mobile/views/components/notifications.vue @@ -131,15 +131,6 @@ export default Vue.extend({ <style lang="stylus" scoped> .mk-notifications - margin 0 auto - background var(--face) - border-radius 8px - box-shadow 0 0 2px rgba(#000, 0.1) - overflow hidden - - @media (min-width 500px) - box-shadow 0 8px 32px rgba(#000, 0.1) - .transition .mk-notifications-enter .mk-notifications-leave-to @@ -187,10 +178,7 @@ export default Vue.extend({ color var(--text) > .placeholder - padding 16px + padding 32px opacity 0.3 - @media (min-width 500px) - padding 32px - </style> diff --git a/src/client/app/mobile/views/components/ui-container.vue b/src/client/app/mobile/views/components/ui-container.vue index 90b29d0c23..806dcc9a1d 100644 --- a/src/client/app/mobile/views/components/ui-container.vue +++ b/src/client/app/mobile/views/components/ui-container.vue @@ -1,5 +1,5 @@ <template> -<div class="ukygtjoj" :class="{ naked, hideHeader: !showHeader }"> +<div class="ukygtjoj" :class="{ naked, inDeck, hideHeader: !showHeader }"> <header v-if="showHeader"> <div class="title"><slot name="header"></slot></div> <slot name="func"></slot> @@ -35,6 +35,11 @@ export default Vue.extend({ default: true }, }, + inject: { + inDeck: { + default: false + } + }, data() { return { showBody: this.expanded @@ -50,49 +55,69 @@ export default Vue.extend({ <style lang="stylus" scoped> .ukygtjoj - background var(--face) - border-radius 8px - box-shadow 0 4px 16px rgba(#000, 0.1) overflow hidden - & + .ukygtjoj - margin-top 16px + &:not(.inDeck) + background var(--face) + border-radius 8px + box-shadow 0 4px 16px rgba(#000, 0.1) - @media (max-width 500px) - margin-top 8px + & + .ukygtjoj + margin-top 16px - &.naked - background transparent !important - box-shadow none !important + @media (max-width 500px) + margin-top 8px - > header - > .title - margin 0 - padding 8px 10px - font-size 15px - font-weight normal - color var(--faceHeaderText) - background var(--faceHeader) - border-radius 8px 8px 0 0 + &.naked + background transparent !important + box-shadow none !important + + > header + > .title + margin 0 + padding 8px 10px + font-size 15px + font-weight normal + color var(--faceHeaderText) + background var(--faceHeader) + border-radius 8px 8px 0 0 - > [data-icon] - margin-right 6px + > [data-icon] + margin-right 6px - &:empty - display none + &:empty + display none - > button - position absolute - z-index 2 - top 0 - right 0 - padding 0 - width 42px - height 100% - font-size 15px - color var(--faceTextButton) + > button + position absolute + z-index 2 + top 0 + right 0 + padding 0 + width 42px + height 100% + font-size 15px + color var(--faceTextButton) + + > div + color var(--text) + + &.inDeck + background var(--face) + + > header + margin 0 + padding 8px 16px + font-size 12px + color var(--text) + background var(--deckColumnBg) - > div - color var(--text) + > button + position absolute + top 0 + right 8px + padding 8px 6px + font-size 14px + color var(--text) </style> diff --git a/src/client/app/mobile/views/components/ui.header.vue b/src/client/app/mobile/views/components/ui.header.vue index d9994f236a..532720ceb4 100644 --- a/src/client/app/mobile/views/components/ui.header.vue +++ b/src/client/app/mobile/views/components/ui.header.vue @@ -5,7 +5,7 @@ <div class="backdrop"></div> <div class="content" ref="mainContainer"> <button class="nav" @click="$parent.isDrawerOpening = true"><fa icon="bars"/></button> - <i v-if="hasUnreadNotification || hasUnreadMessagingMessage || hasGameInvitation" class="circle"><fa icon="circle"/></i> + <i v-if="$parent.indicate" class="circle"><fa icon="circle"/></i> <h1> <slot>{{ $root.instanceName }}</slot> </h1> @@ -27,48 +27,13 @@ export default Vue.extend({ data() { return { - hasGameInvitation: false, - connection: null, env: env }; }, - computed: { - hasUnreadNotification(): boolean { - return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification; - }, - - hasUnreadMessagingMessage(): boolean { - return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage; - } - }, - mounted() { this.$store.commit('setUiHeaderHeight', 48); - - if (this.$store.getters.isSignedIn) { - this.connection = this.$root.stream.useSharedConnection('main'); - - this.connection.on('reversiInvited', this.onReversiInvited); - this.connection.on('reversiNoInvites', this.onReversiNoInvites); - } }, - - beforeDestroy() { - if (this.$store.getters.isSignedIn) { - this.connection.dispose(); - } - }, - - methods: { - onReversiInvited() { - this.hasGameInvitation = true; - }, - - onReversiNoInvites() { - this.hasGameInvitation = false; - } - } }); </script> diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue index 627a322e6c..4c33de8842 100644 --- a/src/client/app/mobile/views/components/ui.nav.vue +++ b/src/client/app/mobile/views/components/ui.nav.vue @@ -1,5 +1,5 @@ <template> -<div class="nav"> +<div class="fquwcbxs"> <transition name="back"> <div class="backdrop" v-if="isOpen" @@ -8,41 +8,52 @@ ></div> </transition> <transition name="nav"> - <div class="body" v-if="isOpen"> - <router-link class="me" v-if="$store.getters.isSignedIn" :to="`/@${$store.state.i.username}`"> - <img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/> - <p class="name"><mk-user-name :user="$store.state.i"/></p> - </router-link> - <div class="links"> - <ul> - <li><router-link to="/" :data-active="$route.name == 'index'"><i><fa icon="home" fixed-width/></i>{{ $t('timeline') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> - <li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/explore" :data-active="$route.name == 'explore' || $route.name == 'explore-tag'"><i><fa :icon="faHashtag" fixed-width/></i>{{ $t('@.explore') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> - </ul> - <ul> - <li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'"><i><fa :icon="['far', 'calendar-alt']" fixed-width/></i>{{ $t('widgets') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/i/favorites" :data-active="$route.name == 'favorites'"><i><fa icon="star" fixed-width/></i>{{ $t('favorites') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/i/lists" :data-active="$route.name == 'user-lists'"><i><fa icon="list" fixed-width/></i>{{ $t('user-lists') }}<i><fa icon="angle-right"/></i></router-link></li> - <li><router-link to="/i/drive" :data-active="$route.name == 'drive'"><i><fa icon="cloud" fixed-width/></i>{{ $t('@.drive') }}<i><fa icon="angle-right"/></i></router-link></li> - </ul> - <ul> - <li><a @click="search"><i><fa icon="search" fixed-width/></i>{{ $t('search') }}<i><fa icon="angle-right"/></i></a></li> - <li><router-link to="/i/settings" :data-active="$route.name == 'settings'"><i><fa icon="cog" fixed-width/></i>{{ $t('settings') }}<i><fa icon="angle-right"/></i></router-link></li> - <li v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)"><a href="/admin"><i><fa icon="terminal" fixed-width/></i><span>{{ $t('admin') }}</span><i><fa icon="angle-right"/></i></a></li> - <li @click="dark"><p><template><i><fa :icon="$store.state.device.darkmode ? faSun : faMoon" fixed-width/></i></template><span>{{ $store.state.device.darkmode ? $t('@.turn-off-darkmode') : $t('@.turn-on-darkmode') }}</span></p></li> - </ul> + <div class="body" :class="{ notifications: showNotifications }" v-if="isOpen"> + <div class="nav" v-show="!showNotifications"> + <router-link class="me" v-if="$store.getters.isSignedIn" :to="`/@${$store.state.i.username}`"> + <img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/> + <p class="name"><mk-user-name :user="$store.state.i"/></p> + </router-link> + <div class="links"> + <ul> + <li><router-link to="/" :data-active="$route.name == 'index'"><i><fa icon="home" fixed-width/></i>{{ $t('timeline') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><p @click="showNotifications = true"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li> + <li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> + <li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/explore" :data-active="$route.name == 'explore' || $route.name == 'explore-tag'"><i><fa :icon="faHashtag" fixed-width/></i>{{ $t('@.explore') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li> + </ul> + <ul> + <li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'"><i><fa :icon="['far', 'calendar-alt']" fixed-width/></i>{{ $t('widgets') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/i/favorites" :data-active="$route.name == 'favorites'"><i><fa icon="star" fixed-width/></i>{{ $t('favorites') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/i/lists" :data-active="$route.name == 'user-lists'"><i><fa icon="list" fixed-width/></i>{{ $t('user-lists') }}<i><fa icon="angle-right"/></i></router-link></li> + <li><router-link to="/i/drive" :data-active="$route.name == 'drive'"><i><fa icon="cloud" fixed-width/></i>{{ $t('@.drive') }}<i><fa icon="angle-right"/></i></router-link></li> + </ul> + <ul> + <li><a @click="search"><i><fa icon="search" fixed-width/></i>{{ $t('search') }}<i><fa icon="angle-right"/></i></a></li> + <li><router-link to="/i/settings" :data-active="$route.name == 'settings'"><i><fa icon="cog" fixed-width/></i>{{ $t('settings') }}<i><fa icon="angle-right"/></i></router-link></li> + <li v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)"><a href="/admin"><i><fa icon="terminal" fixed-width/></i><span>{{ $t('admin') }}</span><i><fa icon="angle-right"/></i></a></li> + </ul> + <ul> + <li @click="toggleDeckMode"><p><i><fa :icon="$store.state.device.inDeckMode ? faHome : faColumns"/></i><span>{{ $store.state.device.inDeckMode ? $t('@.home') : $t('@.deck') }}</span></p></li> + <li @click="dark"><p><i><fa :icon="$store.state.device.darkmode ? faSun : faMoon" fixed-width/></i><span>{{ $store.state.device.darkmode ? $t('@.turn-off-darkmode') : $t('@.turn-on-darkmode') }}</span></p></li> + </ul> + </div> + <div class="announcements" v-if="announcements && announcements.length > 0"> + <article v-for="announcement in announcements"> + <span v-html="announcement.title" class="title"></span> + <div v-html="announcement.text"></div> + </article> + </div> + <a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a> </div> - <div class="announcements" v-if="announcements && announcements.length > 0"> - <article v-for="announcement in announcements"> - <span v-html="announcement.title" class="title"></span> - <div v-html="announcement.text"></div> - </article> + <div class="notifications" v-if="showNotifications"> + <header> + <button @click="$parent.isDrawerOpening = false"><fa icon="times"/></button> + </header> + <mk-notifications/> </div> - <a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a> </div> </transition> </div> @@ -52,13 +63,18 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import { lang } from '../../../config'; -import { faNewspaper, faHashtag } from '@fortawesome/free-solid-svg-icons'; +import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; export default Vue.extend({ i18n: i18n('mobile/views/components/ui.nav.vue'), + props: ['isOpen'], + provide: { + narrow: true + }, + data() { return { hasGameInvitation: false, @@ -66,7 +82,8 @@ export default Vue.extend({ aboutUrl: `/docs/${lang}/about`, announcements: [], searching: false, - faNewspaper, faHashtag, faMoon, faSun + showNotifications: false, + faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns }; }, @@ -80,6 +97,12 @@ export default Vue.extend({ } }, + watch: { + isOpen() { + this.showNotifications = false; + } + }, + mounted() { this.$root.getMeta().then(meta => { this.announcements = meta.announcements; @@ -148,13 +171,18 @@ export default Vue.extend({ key: 'darkmode', value: !this.$store.state.device.darkmode }); - } + }, + + toggleDeckMode() { + this.$store.commit('device/set', { key: 'deckMode', value: !this.$store.state.device.inDeckMode }); + location.replace('/'); + }, } }); </script> <style lang="stylus" scoped> -.nav +.fquwcbxs $color = var(--text) .backdrop @@ -178,102 +206,126 @@ export default Vue.extend({ background var(--secondary) font-size 15px - .me - display block - margin 0 - padding 16px + &.notifications + width 340px - .avatar - display inline - max-width 64px - border-radius 32px - vertical-align middle + > .notifications + padding-top 42px - .name - display block - margin 0 16px - position absolute - top 0 - left 80px - padding 0 - width calc(100% - 112px) - color $color - line-height 96px - overflow hidden - text-overflow ellipsis - white-space nowrap + > header + position fixed + top 0 + left 0 + z-index 1000 + width 340px + line-height 42px + background var(--secondary) - ul - display block - margin 16px 0 - padding 0 - list-style none + > button + display block + padding 0 14px + font-size 20px + line-height 42px + color var(--text) - &:first-child - margin-top 0 + > .nav + + > .me + display block + margin 0 + padding 16px - &:last-child - margin-bottom 0 + .avatar + display inline + max-width 64px + border-radius 32px + vertical-align middle - > li - display block - font-size 1em - line-height 1em + .name + display block + margin 0 16px + position absolute + top 0 + left 80px + padding 0 + width calc(100% - 112px) + color $color + line-height 96px + overflow hidden + text-overflow ellipsis + white-space nowrap - a, p + ul display block - margin 0 - padding 0 20px - line-height 3rem - line-height calc(1rem + 30px) - color $color - text-decoration none + margin 16px 0 + padding 0 + list-style none - &[data-active] - color var(--primaryForeground) - background var(--primary) + &:first-child + margin-top 0 - > i:last-child - color var(--primaryForeground) + &:last-child + margin-bottom 0 - > i:first-child - margin-right 0.5em - width 20px - text-align center + > li + display block + font-size 1em + line-height 1em - > i.circle - margin-left 6px - font-size 10px - color var(--notificationIndicator) + a, p + display block + margin 0 + padding 0 20px + line-height 3rem + line-height calc(1rem + 30px) + color $color + text-decoration none - > i:last-child - position absolute - top 0 - right 0 - padding 0 20px - font-size 1.2em - line-height calc(1rem + 30px) - color $color - opacity 0.5 + &[data-active] + color var(--primaryForeground) + background var(--primary) + + > i:last-child + color var(--primaryForeground) + + > i:first-child + margin-right 0.5em + width 20px + text-align center - .announcements - > article - background var(--mobileAnnouncement) - color var(--mobileAnnouncementFg) - padding 16px - margin 8px 0 - font-size 12px + > i.circle + margin-left 6px + font-size 10px + color var(--notificationIndicator) - > .title - font-weight bold + > i:last-child + position absolute + top 0 + right 0 + padding 0 20px + font-size 1.2em + line-height calc(1rem + 30px) + color $color + opacity 0.5 - .about - margin 0 0 8px 0 - padding 1em 0 - text-align center - font-size 0.8em - color $color - opacity 0.5 + .announcements + > article + background var(--mobileAnnouncement) + color var(--mobileAnnouncementFg) + padding 16px + margin 8px 0 + font-size 12px + + > .title + font-weight bold + + .about + margin 0 0 8px 0 + padding 1em 0 + text-align center + font-size 0.8em + color $color + opacity 0.5 .nav-enter-active, .nav-leave-active { diff --git a/src/client/app/mobile/views/components/ui.vue b/src/client/app/mobile/views/components/ui.vue index 6cd42b0207..7ae7dd5f78 100644 --- a/src/client/app/mobile/views/components/ui.vue +++ b/src/client/app/mobile/views/components/ui.vue @@ -1,6 +1,6 @@ <template> -<div class="mk-ui"> - <x-header> +<div class="mk-ui" :class="{ deck: $store.state.device.inDeckMode }"> + <x-header v-if="!$store.state.device.inDeckMode"> <template #func><slot name="func"></slot></template> <slot name="header"></slot> </x-header> @@ -9,6 +9,8 @@ <slot></slot> </div> <mk-stream-indicator v-if="$store.getters.isSignedIn"/> + <button class="nav button" v-if="$store.state.device.inDeckMode" @click="isDrawerOpening = !isDrawerOpening"><fa icon="bars"/><i v-if="indicate"><fa icon="circle"/></i></button> + <button class="post button" v-if="$store.state.device.inDeckMode" @click="$post()"><fa icon="pencil-alt"/></button> </div> </template> @@ -28,11 +30,26 @@ export default Vue.extend({ data() { return { + hasGameInvitation: false, isDrawerOpening: false, connection: null }; }, + computed: { + hasUnreadNotification(): boolean { + return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification; + }, + + hasUnreadMessagingMessage(): boolean { + return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage; + }, + + indicate(): boolean { + return this.hasUnreadNotification || this.hasUnreadMessagingMessage || this.hasGameInvitation; + } + }, + watch: { '$store.state.uiHeaderHeight'() { this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px'; @@ -46,6 +63,8 @@ export default Vue.extend({ this.connection = this.$root.stream.useSharedConnection('main'); this.connection.on('notification', this.onNotification); + this.connection.on('reversiInvited', this.onReversiInvited); + this.connection.on('reversiNoInvites', this.onReversiNoInvites); } }, @@ -65,6 +84,14 @@ export default Vue.extend({ this.$root.new(MkNotify, { notification }); + }, + + onReversiInvited() { + this.hasGameInvitation = true; + }, + + onReversiNoInvites() { + this.hasGameInvitation = false; } } }); @@ -72,13 +99,37 @@ export default Vue.extend({ <style lang="stylus" scoped> .mk-ui - display flex - flex 1 - flex-direction column - padding-top 48px + &:not(.deck) + padding-top 48px + + > .button + position fixed + z-index 1000 + bottom 28px + padding 0 + 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 24px + + &.nav + left 28px + background var(--secondary) + color var(--text) + + > i + position absolute + top 0 + left 0 + color var(--primary) + font-size 16px + + &.post + right 28px + background var(--primary) + color var(--primaryForeground) - > .content - display flex - flex 1 - flex-direction column </style> diff --git a/src/client/app/mobile/views/pages/notifications.vue b/src/client/app/mobile/views/pages/notifications.vue deleted file mode 100644 index 8472a623c1..0000000000 --- a/src/client/app/mobile/views/pages/notifications.vue +++ /dev/null @@ -1,41 +0,0 @@ -<template> -<mk-ui> - <template #header><span style="margin-right:4px;"><fa :icon="['far', 'bell']"/></span>{{ $t('notifications') }}</template> - <template #func><button @click="fn"><fa icon="check"/></button></template> - - <main> - <mk-notifications @fetched="onFetched"/> - </main> -</mk-ui> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import i18n from '../../../i18n'; -import Progress from '../../../common/scripts/loading'; - -export default Vue.extend({ - i18n: i18n('mobile/views/pages/notifications.vue'), - mounted() { - document.title = this.$t('notifications'); - - Progress.start(); - }, - methods: { - fn() { - this.$root.dialog({ - type: 'warning', - text: this.$t('read-all'), - showCancelButton: true - }).then(({ canceled }) => { - if (canceled) return; - - this.$root.api('notifications/mark_all_as_read'); - }); - }, - onFetched() { - Progress.done(); - } - } -}); -</script> diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index 4a1e18540c..17f9f65881 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -60,6 +60,21 @@ <ui-radio v-model="mobileNotificationPosition" value="bottom">{{ $t('notification-position-bottom') }}</ui-radio> <ui-radio v-model="mobileNotificationPosition" value="top">{{ $t('notification-position-top') }}</ui-radio> </section> + + <section> + <header>{{ $t('@.deck-column-align') }}</header> + <ui-radio v-model="deckColumnAlign" value="center">{{ $t('@.deck-column-align-center') }}</ui-radio> + <ui-radio v-model="deckColumnAlign" value="left">{{ $t('@.deck-column-align-left') }}</ui-radio> + <ui-radio v-model="deckColumnAlign" value="flexible">{{ $t('@.deck-column-align-flexible') }}</ui-radio> + </section> + <section> + <header>{{ $t('@.deck-column-width') }}</header> + <ui-radio v-model="deckColumnWidth" value="narrow">{{ $t('@.deck-column-width-narrow') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="narrower">{{ $t('@.deck-column-width-narrower') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="normal">{{ $t('@.deck-column-width-normal') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="wider">{{ $t('@.deck-column-width-wider') }}</ui-radio> + <ui-radio v-model="deckColumnWidth" value="wide">{{ $t('@.deck-column-width-wide') }}</ui-radio> + </section> </ui-card> <ui-card> @@ -244,6 +259,16 @@ export default Vue.extend({ set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); } }, + deckColumnAlign: { + get() { return this.$store.state.device.deckColumnAlign; }, + set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); } + }, + + deckColumnWidth: { + get() { return this.$store.state.device.deckColumnWidth; }, + set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); } + }, + fetchOnScroll: { get() { return this.$store.state.settings.fetchOnScroll; }, set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); } diff --git a/src/client/app/store.ts b/src/client/app/store.ts index 842563f246..eee4a4d7ca 100644 --- a/src/client/app/store.ts +++ b/src/client/app/store.ts @@ -9,7 +9,6 @@ import getNoteSummary from '../../misc/get-note-summary'; const defaultSettings = { home: null, mobileHome: [], - deck: null, keepCw: false, tagTimelines: [], fetchOnScroll: true, @@ -45,6 +44,10 @@ const defaultSettings = { }; const defaultDeviceSettings = { + deck: null, + deckMode: false, + deckColumnAlign: 'center', + deckColumnWidth: 'normal', reduceMotion: false, autoPopout: false, darkmode: false, @@ -63,10 +66,7 @@ const defaultDeviceSettings = { alwaysShowNsfw: false, postStyle: 'standard', navbar: 'top', - deckColumnAlign: 'center', - deckColumnWidth: 'normal', mobileNotificationPosition: 'bottom', - deckMode: false, useOsDefaultEmojis: false, disableShowingAnimatedImages: false, expandUsersPhotos: true, @@ -162,73 +162,10 @@ export default (os: MiOS) => new Vuex.Store({ setVisibility(state, visibility) { state.visibility = visibility; - } - } - }, - - settings: { - namespaced: true, - - state: defaultSettings, - - mutations: { - set(state, x: { key: string; value: any }) { - nestedProperty.set(state, x.key, x.value); - }, - - setHome(state, data) { - state.home = data; - }, - - addHomeWidget(state, widget) { - state.home.unshift(widget); - }, - - setMobileHome(state, data) { - state.mobileHome = data; - }, - - setWidget(state, x) { - let w; - - //#region Decktop home - if (state.home) { - w = state.home.find(w => w.id == x.id); - if (w) { - w.data = x.data; - } - } - //#endregion - - //#region Mobile home - if (state.mobileHome) { - w = state.mobileHome.find(w => w.id == x.id); - if (w) { - w.data = x.data; - } - } - //#endregion - - //#region Deck - if (state.deck && state.deck.columns) { - for (const c of state.deck.columns.filter(c => c.type == 'widgets')) { - for (const w of c.widgets.filter(w => w.id == x.id)) { - w.data = x.data; - } - } - } - //#endregion - }, - - addMobileHomeWidget(state, widget) { - state.mobileHome.unshift(widget); - }, - - removeMobileHomeWidget(state, widget) { - state.mobileHome = state.mobileHome.filter(w => w.id != widget.id); }, addDeckColumn(state, column) { + if (column.name == undefined) column.name = null; state.deck.columns.push(column); state.deck.layout.push([column.id]); }, @@ -255,8 +192,11 @@ export default (os: MiOS) => new Vuex.Store({ if (ids.indexOf(id) != -1) { const left = state.deck.layout[i - 1]; if (left) { - state.deck.layout[i - 1] = state.deck.layout[i]; - state.deck.layout[i] = left; + // https://vuejs.org/v2/guide/list.html#Caveats + //state.deck.layout[i - 1] = state.deck.layout[i]; + //state.deck.layout[i] = left; + state.deck.layout.splice(i - 1, 1, state.deck.layout[i]); + state.deck.layout.splice(i, 1, left); } return true; } @@ -268,8 +208,11 @@ export default (os: MiOS) => new Vuex.Store({ if (ids.indexOf(id) != -1) { const right = state.deck.layout[i + 1]; if (right) { - state.deck.layout[i + 1] = state.deck.layout[i]; - state.deck.layout[i] = right; + // https://vuejs.org/v2/guide/list.html#Caveats + //state.deck.layout[i + 1] = state.deck.layout[i]; + //state.deck.layout[i] = right; + state.deck.layout.splice(i + 1, 1, state.deck.layout[i]); + state.deck.layout.splice(i, 1, right); } return true; } @@ -282,8 +225,11 @@ export default (os: MiOS) => new Vuex.Store({ if (x == id) { const up = ids[i - 1]; if (up) { - ids[i - 1] = id; - ids[i] = up; + // https://vuejs.org/v2/guide/list.html#Caveats + //ids[i - 1] = id; + //ids[i] = up; + ids.splice(i - 1, 1, id); + ids.splice(i, 1, up); } return true; } @@ -296,8 +242,11 @@ export default (os: MiOS) => new Vuex.Store({ if (x == id) { const down = ids[i + 1]; if (down) { - ids[i + 1] = id; - ids[i] = down; + // https://vuejs.org/v2/guide/list.html#Caveats + //ids[i + 1] = id; + //ids[i] = down; + ids.splice(i + 1, 1, id); + ids.splice(i, 1, down); } return true; } @@ -335,93 +284,86 @@ export default (os: MiOS) => new Vuex.Store({ const column = state.deck.columns.find(c => c.id == x.id); if (column == null) return; column.name = x.name; - } - }, - - actions: { - merge(ctx, settings) { - if (settings == null) return; - for (const [key, value] of Object.entries(settings)) { - ctx.commit('set', { key, value }); - } }, - set(ctx, x) { - ctx.commit('set', x); + updateDeckColumn(state, x) { + let column = state.deck.columns.find(c => c.id == x.id); + if (column == null) return; + column = x; + } + } + }, - if (ctx.rootGetters.isSignedIn) { - os.api('i/update_client_setting', { - name: x.key, - value: x.value - }); - } - }, + settings: { + namespaced: true, - saveDeck(ctx) { - os.api('i/update_client_setting', { - name: 'deck', - value: ctx.state.deck - }); - }, + state: defaultSettings, - addDeckColumn(ctx, column) { - ctx.commit('addDeckColumn', column); - ctx.dispatch('saveDeck'); + mutations: { + set(state, x: { key: string; value: any }) { + nestedProperty.set(state, x.key, x.value); }, - removeDeckColumn(ctx, id) { - ctx.commit('removeDeckColumn', id); - ctx.dispatch('saveDeck'); + setHome(state, data) { + state.home = data; }, - swapDeckColumn(ctx, id) { - ctx.commit('swapDeckColumn', id); - ctx.dispatch('saveDeck'); + addHomeWidget(state, widget) { + state.home.unshift(widget); }, - swapLeftDeckColumn(ctx, id) { - ctx.commit('swapLeftDeckColumn', id); - ctx.dispatch('saveDeck'); + setMobileHome(state, data) { + state.mobileHome = data; }, - swapRightDeckColumn(ctx, id) { - ctx.commit('swapRightDeckColumn', id); - ctx.dispatch('saveDeck'); - }, + updateWidget(state, x) { + let w; - swapUpDeckColumn(ctx, id) { - ctx.commit('swapUpDeckColumn', id); - ctx.dispatch('saveDeck'); - }, + //#region Desktop home + if (state.home) { + w = state.home.find(w => w.id == x.id); + if (w) { + w.data = x.data; + } + } + //#endregion - swapDownDeckColumn(ctx, id) { - ctx.commit('swapDownDeckColumn', id); - ctx.dispatch('saveDeck'); + //#region Mobile home + if (state.mobileHome) { + w = state.mobileHome.find(w => w.id == x.id); + if (w) { + w.data = x.data; + } + } + //#endregion }, - stackLeftDeckColumn(ctx, id) { - ctx.commit('stackLeftDeckColumn', id); - ctx.dispatch('saveDeck'); + addMobileHomeWidget(state, widget) { + state.mobileHome.unshift(widget); }, - popRightDeckColumn(ctx, id) { - ctx.commit('popRightDeckColumn', id); - ctx.dispatch('saveDeck'); + removeMobileHomeWidget(state, widget) { + state.mobileHome = state.mobileHome.filter(w => w.id != widget.id); }, + }, - addDeckWidget(ctx, x) { - ctx.commit('addDeckWidget', x); - ctx.dispatch('saveDeck'); + actions: { + merge(ctx, settings) { + if (settings == null) return; + for (const [key, value] of Object.entries(settings)) { + ctx.commit('set', { key, value }); + } }, - removeDeckWidget(ctx, x) { - ctx.commit('removeDeckWidget', x); - ctx.dispatch('saveDeck'); - }, + set(ctx, x) { + ctx.commit('set', x); - renameDeckColumn(ctx, x) { - ctx.commit('renameDeckColumn', x); - ctx.dispatch('saveDeck'); + if (ctx.rootGetters.isSignedIn) { + os.api('i/update_client_setting', { + name: x.key, + value: x.value + }); + } }, addHomeWidget(ctx, widget) { |