summaryrefslogtreecommitdiff
path: root/src/client/app/mobile
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2020-01-30 04:37:25 +0900
committerGitHub <noreply@github.com>2020-01-30 04:37:25 +0900
commitf6154dc0af1a0d65819e87240f4385f9573095cb (patch)
tree699a5ca07d6727b7f8497d4769f25d6d62f94b5a /src/client/app/mobile
parentAdd Event activity-type support (#5785) (diff)
downloadmisskey-f6154dc0af1a0d65819e87240f4385f9573095cb.tar.gz
misskey-f6154dc0af1a0d65819e87240f4385f9573095cb.tar.bz2
misskey-f6154dc0af1a0d65819e87240f4385f9573095cb.zip
v12 (#5712)
Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com> Co-authored-by: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com>
Diffstat (limited to 'src/client/app/mobile')
-rw-r--r--src/client/app/mobile/script.ts184
-rw-r--r--src/client/app/mobile/style.styl23
-rw-r--r--src/client/app/mobile/views/components/detail-notes.vue52
-rw-r--r--src/client/app/mobile/views/components/drive-file-chooser.vue106
-rw-r--r--src/client/app/mobile/views/components/drive-folder-chooser.vue83
-rw-r--r--src/client/app/mobile/views/components/drive.file-detail.vue253
-rw-r--r--src/client/app/mobile/views/components/drive.file.vue155
-rw-r--r--src/client/app/mobile/views/components/drive.folder.vue56
-rw-r--r--src/client/app/mobile/views/components/drive.vue618
-rw-r--r--src/client/app/mobile/views/components/index.ts31
-rw-r--r--src/client/app/mobile/views/components/media-video.vue74
-rw-r--r--src/client/app/mobile/views/components/note-card.vue93
-rw-r--r--src/client/app/mobile/views/components/note-detail.vue351
-rw-r--r--src/client/app/mobile/views/components/note-preview.vue114
-rw-r--r--src/client/app/mobile/views/components/note.sub.vue124
-rw-r--r--src/client/app/mobile/views/components/note.vue302
-rw-r--r--src/client/app/mobile/views/components/notes.vue167
-rw-r--r--src/client/app/mobile/views/components/notification-preview.vue137
-rw-r--r--src/client/app/mobile/views/components/notification.vue199
-rw-r--r--src/client/app/mobile/views/components/notifications.vue167
-rw-r--r--src/client/app/mobile/views/components/notify.vue73
-rw-r--r--src/client/app/mobile/views/components/post-form-dialog.vue120
-rw-r--r--src/client/app/mobile/views/components/post-form.vue244
-rw-r--r--src/client/app/mobile/views/components/sub-note-content.vue47
-rw-r--r--src/client/app/mobile/views/components/ui-container.vue127
-rw-r--r--src/client/app/mobile/views/components/ui.header.vue142
-rw-r--r--src/client/app/mobile/views/components/ui.nav.vue346
-rw-r--r--src/client/app/mobile/views/components/ui.vue136
-rw-r--r--src/client/app/mobile/views/components/user-list-timeline.vue76
-rw-r--r--src/client/app/mobile/views/components/user-timeline.vue43
-rw-r--r--src/client/app/mobile/views/directives/index.ts6
-rw-r--r--src/client/app/mobile/views/directives/user-preview.ts2
-rw-r--r--src/client/app/mobile/views/pages/drive.vue147
-rw-r--r--src/client/app/mobile/views/pages/games/reversi.vue31
-rw-r--r--src/client/app/mobile/views/pages/home.timeline.vue143
-rw-r--r--src/client/app/mobile/views/pages/home.vue249
-rw-r--r--src/client/app/mobile/views/pages/index.vue16
-rw-r--r--src/client/app/mobile/views/pages/messaging-room.vue71
-rw-r--r--src/client/app/mobile/views/pages/messaging.vue30
-rw-r--r--src/client/app/mobile/views/pages/note.vue67
-rw-r--r--src/client/app/mobile/views/pages/notifications.vue71
-rw-r--r--src/client/app/mobile/views/pages/search.vue47
-rw-r--r--src/client/app/mobile/views/pages/selectdrive.vue101
-rw-r--r--src/client/app/mobile/views/pages/settings.vue86
-rw-r--r--src/client/app/mobile/views/pages/signup.vue29
-rw-r--r--src/client/app/mobile/views/pages/tag.vue40
-rw-r--r--src/client/app/mobile/views/pages/ui.vue38
-rw-r--r--src/client/app/mobile/views/pages/user/home.notes.vue59
-rw-r--r--src/client/app/mobile/views/pages/user/home.photos.vue99
-rw-r--r--src/client/app/mobile/views/pages/user/home.vue70
-rw-r--r--src/client/app/mobile/views/pages/user/index.vue349
-rw-r--r--src/client/app/mobile/views/pages/welcome.vue310
-rw-r--r--src/client/app/mobile/views/pages/widgets.vue192
-rw-r--r--src/client/app/mobile/views/widgets/activity.vue38
-rw-r--r--src/client/app/mobile/views/widgets/index.ts7
-rw-r--r--src/client/app/mobile/views/widgets/profile.vue65
56 files changed, 0 insertions, 7006 deletions
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
deleted file mode 100644
index 26ef740811..0000000000
--- a/src/client/app/mobile/script.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * Mobile Client
- */
-
-import Vue from 'vue';
-import VueRouter from 'vue-router';
-
-// Style
-import './style.styl';
-
-import init from '../init';
-
-import MkIndex from './views/pages/index.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 MkMessaging from './views/pages/messaging.vue';
-import MkMessagingRoom from './views/pages/messaging-room.vue';
-import MkNote from './views/pages/note.vue';
-import MkSearch from './views/pages/search.vue';
-import UI from './views/pages/ui.vue';
-import MkReversi from './views/pages/games/reversi.vue';
-import MkTag from './views/pages/tag.vue';
-import MkShare from '../common/views/pages/share.vue';
-import MkFollow from '../common/views/pages/follow.vue';
-import MkNotFound from '../common/views/pages/not-found.vue';
-import DeckColumn from '../common/views/deck/deck.column-template.vue';
-import PostFormDialog from './views/components/post-form-dialog.vue';
-
-import FileChooser from './views/components/drive-file-chooser.vue';
-import FolderChooser from './views/components/drive-folder-chooser.vue';
-
-/**
- * init
- */
-init((launch, os) => {
- Vue.mixin({
- data() {
- return {
- isMobile: true
- };
- },
-
- methods: {
- $post(opts) {
- const o = opts || {};
-
- document.documentElement.style.overflow = 'hidden';
-
- function recover() {
- document.documentElement.style.overflow = 'auto';
- }
-
- const vm = this.$root.new(PostFormDialog, {
- reply: o.reply,
- mention: o.mention,
- renote: o.renote,
- initialText: o.initialText,
- instant: o.instant,
- initialNote: o.initialNote,
- });
- vm.$once('cancel', recover);
- vm.$once('posted', recover);
- if (o.cb) vm.$once('closed', o.cb);
- (vm as any).focus();
- },
-
- $chooseDriveFile(opts) {
- return new Promise((res, rej) => {
- const o = opts || {};
- const vm = this.$root.new(FileChooser, {
- title: o.title,
- multiple: o.multiple,
- initFolder: o.currentFolder
- });
- vm.$once('selected', file => {
- res(file);
- });
- });
- },
-
- $chooseDriveFolder(opts) {
- return new Promise((res, rej) => {
- const o = opts || {};
- const vm = this.$root.new(FolderChooser, {
- title: o.title,
- initFolder: o.currentFolder
- });
- vm.$once('selected', folder => {
- res(folder);
- });
- });
- },
-
- $notify(message) {
- alert(message);
- }
- }
- });
-
- // Register directives
- require('./views/directives');
-
- // Register components
- require('./views/components');
- require('./views/widgets');
-
- // http://qiita.com/junya/items/3ff380878f26ca447f85
- document.body.setAttribute('ontouchstart', '');
-
- // Init router
- const router = new VueRouter({
- mode: 'history',
- routes: [
- ...(os.store.state.device.inDeckMode
- ? [{ path: '/', name: 'index', component: () => import('../common/views/deck/deck.vue').then(m => m.default), 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: DeckColumn, props: route => ({ component: () => import('../common/views/pages/featured.vue').then(m => m.default), platform: 'deck' }) },
- { path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
- { path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
- { path: '/i/favorites', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'deck' }) },
- { path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
- { path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
- { path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
- { path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
- { path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
- { path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
- { path: '/@:username/pages/:pageName', name: 'page', props: true, component: () => import('../common/views/deck/deck.page-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/settings/:page', redirect: '/i/settings' },
- { path: '/i/favorites', name: 'favorites', component: UI, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'mobile' }) },
- { path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
- { path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
- { path: '/i/lists/:list', component: UI, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.list }) },
- { path: '/i/groups', name: 'user-groups', component: UI, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
- { path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) },
- { path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
- { path: '/i/widgets', name: 'widgets', component: () => import('./views/pages/widgets.vue').then(m => m.default) },
- { path: '/i/notifications', name: 'notifications', component: MkNotifications },
- { path: '/i/messaging', name: 'messaging', component: MkMessaging },
- { path: '/i/messaging/group/:group', component: MkMessagingRoom },
- { path: '/i/messaging/:user', component: MkMessagingRoom },
- { path: '/i/drive', name: 'drive', component: MkDrive },
- { path: '/i/drive/folder/:folder', component: MkDrive },
- { path: '/i/drive/file/:file', component: MkDrive },
- { path: '/i/pages/new', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }) },
- { path: '/i/pages/edit/:pageId', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initPageId: route.params.pageId }) },
- { path: '/selectdrive', component: MkSelectDrive },
- { path: '/search', component: MkSearch },
- { path: '/tags/:tag', component: MkTag },
- { path: '/featured', name: 'featured', component: UI, props: route => ({ component: () => import('../common/views/pages/featured.vue').then(m => m.default), platform: 'mobile' }) },
- { path: '/explore', name: 'explore', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
- { path: '/explore/tags/:tag', name: 'explore-tag', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
- { path: '/share', component: MkShare },
- { path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
- { path: '/@:user', name: 'user', component: () => import('./views/pages/user/index.vue').then(m => m.default), children: [
- { 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: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
- { path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
- { path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
- { path: '/notes/:note', component: MkNote },
- { path: '/authorize-follow', component: MkFollow },
- { path: '*', component: MkNotFound }
- ]
- });
-
- // Launch the app
- launch(router);
-}, true);
diff --git a/src/client/app/mobile/style.styl b/src/client/app/mobile/style.styl
deleted file mode 100644
index 3a4fc9c0c6..0000000000
--- a/src/client/app/mobile/style.styl
+++ /dev/null
@@ -1,23 +0,0 @@
-@import "../app"
-@import "../reset"
-
-#wait
- top auto
- bottom 15px
- left 15px
-
-html
- height 100%
- background var(--bg)
-
-main
- width 100%
- max-width 680px
- margin 0 auto
- padding 8px
-
- @media (min-width 500px)
- padding 16px
-
- @media (min-width 600px)
- padding 32px
diff --git a/src/client/app/mobile/views/components/detail-notes.vue b/src/client/app/mobile/views/components/detail-notes.vue
deleted file mode 100644
index bab7949534..0000000000
--- a/src/client/app/mobile/views/components/detail-notes.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<template>
-<div class="fdcvngpy">
- <sequential-entrance animation="entranceFromTop" delay="25">
- <template v-for="note in notes">
- <mk-note-detail class="post" :note="note" :key="note.id"/>
- </template>
- </sequential-entrance>
- <ui-button v-if="more" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import paging from '../../../common/scripts/paging';
-
-export default Vue.extend({
- i18n: i18n(),
-
- mixins: [
- paging({
- captureWindowScroll: true,
- }),
- ],
-
- props: {
- pagination: {
- required: true
- },
- extract: {
- required: false
- }
- },
-
- computed: {
- notes() {
- return this.extract ? this.extract(this.items) : this.items;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.fdcvngpy
- > * > .post
- margin-bottom 8px
-
- @media (min-width 500px)
- > * > .post
- margin-bottom 16px
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive-file-chooser.vue b/src/client/app/mobile/views/components/drive-file-chooser.vue
deleted file mode 100644
index 8795102f97..0000000000
--- a/src/client/app/mobile/views/components/drive-file-chooser.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-<div class="cdxzvcfawjxdyxsekbxbfgtplebnoneb">
- <div class="body">
- <header>
- <h1>{{ $t('select-file') }}<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
- <button class="close" @click="cancel"><fa icon="times"/></button>
- <button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
- </header>
- <x-drive class="drive" ref="browser"
- :select-file="true"
- :type="type"
- :multiple="multiple"
- @change-selection="onChangeSelection"
- @selected="onSelected"
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/drive-file-chooser.vue'),
- components: {
- XDrive: () => import('./drive.vue').then(m => m.default),
- },
- props: ['type', 'multiple'],
- data() {
- return {
- files: []
- };
- },
- methods: {
- onChangeSelection(files) {
- this.files = files;
- },
- onSelected(file) {
- this.$emit('selected', file);
- this.destroyDom();
- },
- cancel() {
- this.$emit('canceled');
- this.destroyDom();
- },
- ok() {
- this.$emit('selected', this.files);
- this.destroyDom();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.cdxzvcfawjxdyxsekbxbfgtplebnoneb
- position fixed
- z-index 20000
- top 0
- left 0
- width 100%
- height 100%
- padding 8px
- background rgba(#000, 0.2)
-
- > .body
- width 100%
- height 100%
- background var(--faceHeader)
-
- > header
- border-bottom solid 1px var(--faceDivider)
- color var(--text)
-
- > h1
- margin 0
- padding 0
- text-align center
- line-height 42px
- font-size 1em
- font-weight normal
-
- > .count
- margin-left 4px
- opacity 0.5
-
- > .close
- position absolute
- top 0
- left 0
- line-height 42px
- width 42px
-
- > .ok
- position absolute
- top 0
- right 0
- line-height 42px
- width 42px
-
- > .drive
- height calc(100% - 42px)
- overflow scroll
- -webkit-overflow-scrolling touch
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive-folder-chooser.vue b/src/client/app/mobile/views/components/drive-folder-chooser.vue
deleted file mode 100644
index 250a7aca2c..0000000000
--- a/src/client/app/mobile/views/components/drive-folder-chooser.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<template>
-<div class="mk-drive-folder-chooser">
- <div class="body">
- <header>
- <h1>{{ $t('select-folder') }}</h1>
- <button class="close" @click="cancel"><fa icon="times"/></button>
- <button class="ok" @click="ok"><fa icon="check"/></button>
- </header>
- <x-drive ref="browser"
- select-folder
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-export default Vue.extend({
- i18n: i18n('mobile/views/components/drive-folder-chooser.vue'),
- components: {
- XDrive: () => import('./drive.vue').then(m => m.default),
- },
- methods: {
- cancel() {
- this.$emit('canceled');
- this.destroyDom();
- },
- ok() {
- this.$emit('selected', (this.$refs.browser as any).folder);
- this.destroyDom();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-drive-folder-chooser
- position fixed
- z-index 2048
- top 0
- left 0
- width 100%
- height 100%
- padding 8px
- background rgba(#000, 0.2)
-
- > .body
- width 100%
- height 100%
- background #fff
-
- > header
- border-bottom solid 1px #eee
-
- > h1
- margin 0
- padding 0
- text-align center
- line-height 42px
- font-size 1em
- font-weight normal
-
- > .close
- position absolute
- top 0
- left 0
- line-height 42px
- width 42px
-
- > .ok
- position absolute
- top 0
- right 0
- line-height 42px
- width 42px
-
- > .mk-drive
- height calc(100% - 42px)
- overflow scroll
- -webkit-overflow-scrolling touch
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive.file-detail.vue b/src/client/app/mobile/views/components/drive.file-detail.vue
deleted file mode 100644
index 328982a16b..0000000000
--- a/src/client/app/mobile/views/components/drive.file-detail.vue
+++ /dev/null
@@ -1,253 +0,0 @@
-<template>
-<div class="pyvicwrksnfyhpfgkjwqknuururpaztw">
- <div class="preview">
- <x-file-thumbnail class="preview" :file="file" :detail="true"/>
- <template v-if="kind != 'image'"><fa icon="file"/></template>
- <footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height">
- <span class="size">
- <span class="width">{{ file.properties.width }}</span>
- <span class="time">×</span>
- <span class="height">{{ file.properties.height }}</span>
- <span class="px">px</span>
- </span>
- <span class="separator"></span>
- <span class="aspect-ratio">
- <span class="width">{{ file.properties.width / gcd(file.properties.width, file.properties.height) }}</span>
- <span class="colon">:</span>
- <span class="height">{{ file.properties.height / gcd(file.properties.width, file.properties.height) }}</span>
- </span>
- </footer>
- </div>
- <div class="info">
- <div>
- <span class="type"><mk-file-type-icon :type="file.type"/> {{ file.type }}</span>
- <span class="separator"></span>
- <span class="data-size">{{ file.size | bytes }}</span>
- <span class="separator"></span>
- <span class="created-at" @click="showCreatedAt"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span>
- <template v-if="file.isSensitive">
- <span class="separator"></span>
- <span class="nsfw"><fa :icon="['far', 'eye-slash']"/> {{ $t('nsfw') }}</span>
- </template>
- </div>
- </div>
- <div class="menu">
- <div>
- <ui-input readonly :value="file.url">URL</ui-input>
- <ui-button link :href="dlUrl" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
- <ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
- <ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
- <ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button>
- <ui-button @click="toggleSensitive" v-else><fa :icon="['far', 'eye-slash']"/> {{ $t('mark-as-sensitive') }}</ui-button>
- <ui-button @click="del"><fa :icon="['far', 'trash-alt']"/> {{ $t('delete') }}</ui-button>
- </div>
- </div>
- <div class="hash">
- <div>
- <p>
- <fa icon="hashtag"/>{{ $t('hash') }}
- </p>
- <code>{{ file.md5 }}</code>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import { gcd } from '../../../../../prelude/math';
-import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/drive.file-detail.vue'),
- props: ['file'],
-
- components: {
- XFileThumbnail
- },
-
- data() {
- return {
- gcd,
- exif: null
- };
- },
-
- computed: {
- browser(): any {
- return this.$parent;
- },
-
- kind(): string {
- return this.file.type.split('/')[0];
- },
-
- style(): any {
- return this.file.properties.avgColor ? {
- 'background-color': this.file.properties.avgColor
- } : {};
- },
-
- dlUrl(): string {
- return this.file.url;
- }
- },
-
- methods: {
- rename() {
- const name = window.prompt(this.$t('rename'), this.file.name);
- if (name == null || name == '' || name == this.file.name) return;
- this.$root.api('drive/files/update', {
- fileId: this.file.id,
- name: name
- }).then(() => {
- this.browser.cf(this.file, true);
- });
- },
-
- move() {
- this.$chooseDriveFolder().then(folder => {
- this.$root.api('drive/files/update', {
- fileId: this.file.id,
- folderId: folder == null ? null : folder.id
- }).then(() => {
- this.browser.cf(this.file, true);
- });
- });
- },
-
- del() {
- this.$root.api('drive/files/delete', {
- fileId: this.file.id
- }).then(() => {
- this.browser.cd(this.file.folderId);
- });
- },
-
- toggleSensitive() {
- this.$root.api('drive/files/update', {
- fileId: this.file.id,
- isSensitive: !this.file.isSensitive
- });
-
- this.file.isSensitive = !this.file.isSensitive;
- },
-
- showCreatedAt() {
- this.$root.dialog({
- type: 'info',
- text: (new Date(this.file.createdAt)).toLocaleString()
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.pyvicwrksnfyhpfgkjwqknuururpaztw
- > .preview
- padding 8px
- background var(--bg)
-
- > .preview
- width fit-content
- width -moz-fit-content
- max-width 100%
- margin 0 auto
- box-shadow 1px 1px 4px rgba(#000, 0.2)
- overflow hidden
- color var(--driveFileIcon)
- justify-content center
-
- > footer
- padding 8px 8px 0 8px
- text-align center
- font-size 0.8em
- color var(--text)
- opacity 0.7
-
- > .separator
- display inline
- padding 0 4px
-
- > .size
- display inline
-
- .time
- margin 0 2px
-
- .px
- margin-left 4px
-
- > .aspect-ratio
- display inline
- opacity 0.7
-
- &:before
- content "("
-
- &:after
- content ")"
-
- > .info
- padding 14px
- font-size 0.8em
- border-top solid 1px var(--faceDivider)
-
- > div
- max-width 500px
- margin 0 auto
- color var(--text)
-
- > .separator
- padding 0 4px
-
- > .created-at
-
- > [data-icon]
- margin-right 2px
-
- > .nsfw
- color #bf4633
-
- > .menu
- padding 0 14px 14px 14px
- border-top solid 1px var(--faceDivider)
-
- > div
- max-width 500px
- margin 0 auto
-
- > .hash
- padding 14px
- border-top solid 1px var(--faceDivider)
-
- > div
- max-width 500px
- margin 0 auto
-
- > p
- display block
- margin 0
- padding 0
- color var(--text)
- font-size 0.9em
-
- > [data-icon]
- margin-right 4px
-
- > code
- display block
- width 100%
- margin 6px 0 0 0
- padding 8px
- white-space nowrap
- overflow auto
- font-size 0.8em
- color #222
- border solid 1px #dfdfdf
- border-radius 2px
- background #f5f5f5
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive.file.vue b/src/client/app/mobile/views/components/drive.file.vue
deleted file mode 100644
index ed95537f9c..0000000000
--- a/src/client/app/mobile/views/components/drive.file.vue
+++ /dev/null
@@ -1,155 +0,0 @@
-<template>
-<a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
- <div class="container">
- <x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
- <div class="body">
- <p class="name">
- <span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
- <span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
- </p>
- <footer>
- <span class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</span>
- <span class="separator"></span>
- <span class="data-size">{{ file.size | bytes }}</span>
- <span class="separator"></span>
- <span class="created-at"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span>
- <template v-if="file.isSensitive">
- <span class="separator"></span>
- <span class="nsfw"><fa :icon="['far', 'eye-slash']"/> {{ $t('nsfw') }}</span>
- </template>
- </footer>
- </div>
- </div>
-</a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/drive.file.vue'),
- props: ['file'],
- components: {
- XFileThumbnail
- },
- data() {
- return {
- isSelected: false
- };
- },
- computed: {
- browser(): any {
- return this.$parent;
- }
- },
- created() {
- this.isSelected = this.browser.selectedFiles.some(f => f.id == this.file.id)
-
- this.browser.$on('change-selection', this.onBrowserChangeSelection);
- },
- beforeDestroy() {
- this.browser.$off('change-selection', this.onBrowserChangeSelection);
- },
- methods: {
- onBrowserChangeSelection(selections) {
- this.isSelected = selections.some(f => f.id == this.file.id);
- },
- onClick() {
- this.browser.chooseFile(this.file);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.vupkuhvjnjyqaqhsiogfbywvjxynrgsm
- display block
- text-decoration none !important
-
- *
- user-select none
- pointer-events none
-
- > .container
- display grid
- max-width 500px
- margin 0 auto
- padding 16px
- grid-template-columns 64px 1fr
- grid-column-gap 10px
-
- &:after
- content ""
- display block
- clear both
-
- > .thumbnail
- width 64px
- height 64px
- color var(--driveFileIcon)
-
- > .body
- display block
- word-break break-all
-
- > .name
- display block
- margin 0
- padding 0
- font-size 0.9em
- font-weight bold
- color var(--text)
- word-break break-word
-
- > .ext
- opacity 0.5
-
- > .tags
- display block
- margin 4px 0 0 0
- padding 0
- list-style none
- font-size 0.5em
-
- > .tag
- display inline-block
- margin 0 5px 0 0
- padding 1px 5px
- border-radius 2px
-
- > footer
- display block
- margin 4px 0 0 0
- font-size 0.7em
- color var(--text)
-
- > .separator
- padding 0 4px
-
- > .type
- opacity 0.7
-
- > .mk-file-type-icon
- margin-right 4px
-
- > .data-size
- opacity 0.7
-
- > .created-at
- opacity 0.7
-
- > [data-icon]
- margin-right 2px
-
- > .nsfw
- color #bf4633
-
- &[data-is-selected]
- background var(--primary)
-
- &, *
- color var(--primaryForeground) !important
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive.folder.vue b/src/client/app/mobile/views/components/drive.folder.vue
deleted file mode 100644
index 0959c1e7d4..0000000000
--- a/src/client/app/mobile/views/components/drive.folder.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<template>
-<a class="jvwxssxsytqlqvrpiymarjlzlsxskqsr" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`">
- <div class="container">
- <p class="name"><fa icon="folder"/>{{ folder.name }}</p><fa icon="angle-right"/>
- </div>
-</a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['folder'],
- computed: {
- browser(): any {
- return this.$parent;
- }
- },
- methods: {
- onClick() {
- this.browser.cd(this.folder);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.jvwxssxsytqlqvrpiymarjlzlsxskqsr
- display block
- color var(--text)
- text-decoration none !important
-
- *
- user-select none
- pointer-events none
-
- > .container
- max-width 500px
- margin 0 auto
- padding 16px
-
- > .name
- display block
- margin 0
- padding 0
-
- > [data-icon]
- margin-right 6px
-
- > [data-icon]
- position absolute
- top 0
- bottom 0
- right 20px
- height 100%
-
-</style>
diff --git a/src/client/app/mobile/views/components/drive.vue b/src/client/app/mobile/views/components/drive.vue
deleted file mode 100644
index fe193f311a..0000000000
--- a/src/client/app/mobile/views/components/drive.vue
+++ /dev/null
@@ -1,618 +0,0 @@
-<template>
-<div class="kmmwchoexgckptowjmjgfsygeltxfeqs">
- <nav ref="nav">
- <a @click.prevent="goRoot()" href="/i/drive"><fa icon="cloud"/>{{ $t('@.drive') }}</a>
- <template v-for="folder in hierarchyFolders">
- <span :key="folder.id + '>'"><fa icon="angle-right"/></span>
- <a :key="folder.id" @click.prevent="cd(folder)" :href="`/i/drive/folder/${folder.id}`">{{ folder.name }}</a>
- </template>
- <template v-if="folder != null">
- <span><fa icon="angle-right"/></span>
- <p>{{ folder.name }}</p>
- </template>
- <template v-if="file != null">
- <span><fa icon="angle-right"/></span>
- <p>{{ file.name }}</p>
- </template>
- </nav>
- <mk-uploader ref="uploader"/>
- <div class="browser" :class="{ fetching }" v-if="file == null">
- <div class="info" v-if="info">
- <p v-if="folder == null">{{ (info.usage / info.capacity * 100).toFixed(1) }}% {{ $t('used') }}</p>
- <p v-if="folder != null && (folder.foldersCount > 0 || folder.filesCount > 0)">
- <template v-if="folder.foldersCount > 0">{{ folder.foldersCount }} {{ $t('folder-count') }}</template>
- <template v-if="folder.foldersCount > 0 && folder.filesCount > 0">{{ $t('count-separator') }}</template>
- <template v-if="folder.filesCount > 0">{{ folder.filesCount }} {{ $t('file-count') }}</template>
- </p>
- </div>
- <div class="folders" v-if="folders.length > 0 || moreFolders">
- <x-folder class="folder" v-for="folder in folders" :key="folder.id" :folder="folder"/>
- <p v-if="moreFolders">{{ $t('@.load-more') }}</p>
- </div>
- <div class="files" v-if="files.length > 0 || moreFiles">
- <x-file class="file" v-for="file in files" :key="file.id" :file="file"/>
- <button class="more" v-if="moreFiles" @click="fetchMoreFiles">
- {{ fetchingMoreFiles ? this.$t('@.loading') : this.$t('@.load-more') }}
- </button>
- </div>
- <div class="empty" v-if="files.length == 0 && !moreFiles && folders.length == 0 && !moreFolders && !fetching">
- <p v-if="folder == null">{{ $t('nothing-in-drive') }}</p>
- <p v-if="folder != null">{{ $t('folder-is-empty') }}</p>
- </div>
- </div>
- <div class="fetching" v-if="fetching && file == null && files.length == 0 && folders.length == 0">
- <div class="spinner">
- <div class="dot1"></div>
- <div class="dot2"></div>
- </div>
- </div>
- <input ref="file" class="file" type="file" multiple="multiple" @change="onChangeLocalFile"/>
- <x-file-detail v-if="file != null" :file="file"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import XFolder from './drive.folder.vue';
-import XFile from './drive.file.vue';
-import XFileDetail from './drive.file-detail.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/drive.vue'),
- components: {
- XFolder,
- XFile,
- XFileDetail
- },
- props: ['initFolder', 'initFile', 'selectFile', 'multiple', 'isNaked', 'top'],
- data() {
- return {
- /**
- * 現在の階層(フォルダ)
- * * null でルートを表す
- */
- folder: null,
-
- file: null,
-
- files: [],
- folders: [],
- moreFiles: false,
- moreFolders: false,
- hierarchyFolders: [],
- selectedFiles: [],
- info: null,
- connection: null,
-
- fetching: true,
- fetchingMoreFiles: false,
- fetchingMoreFolders: false
- };
- },
- computed: {
- isFileSelectMode(): boolean {
- return this.selectFile;
- }
- },
- watch: {
- top() {
- if (this.isNaked) {
- (this.$refs.nav as any).style.top = `${this.top}px`;
- }
- }
- },
- mounted() {
- this.connection = this.$root.stream.useSharedConnection('drive');
-
- this.connection.on('fileCreated', this.onStreamDriveFileCreated);
- this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
- this.connection.on('fileDeleted', this.onStreamDriveFileDeleted);
- this.connection.on('folderCreated', this.onStreamDriveFolderCreated);
- this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated);
-
- if (this.initFolder) {
- this.cd(this.initFolder, true);
- } else if (this.initFile) {
- this.cf(this.initFile, true);
- } else {
- this.fetch();
- }
-
- if (this.isNaked) {
- (this.$refs.nav as any).style.top = `${this.top}px`;
- }
- },
- beforeDestroy() {
- this.connection.dispose();
- },
- methods: {
- onStreamDriveFileCreated(file) {
- this.addFile(file, true);
- },
-
- onStreamDriveFileUpdated(file) {
- const current = this.folder ? this.folder.id : null;
- if (current != file.folderId) {
- this.removeFile(file);
- } else {
- this.addFile(file, true);
- }
- },
-
- onStreamDriveFileDeleted(fileId) {
- this.removeFile(fileId);
- },
-
- onStreamDriveFolderCreated(folder) {
- this.addFolder(folder, true);
- },
-
- onStreamDriveFolderUpdated(folder) {
- const current = this.folder ? this.folder.id : null;
- if (current != folder.parentId) {
- this.removeFolder(folder);
- } else {
- this.addFolder(folder, true);
- }
- },
-
- dive(folder) {
- this.hierarchyFolders.unshift(folder);
- if (folder.parent) this.dive(folder.parent);
- },
-
- cd(target, silent = false) {
- if (target == null) {
- this.goRoot(silent);
- return;
- } else if (typeof target == 'object') {
- target = target.id;
- }
-
- this.file = null;
- this.fetching = true;
-
- this.$root.api('drive/folders/show', {
- folderId: target
- }).then(folder => {
- this.folder = folder;
- this.hierarchyFolders = [];
-
- if (folder.parent) this.dive(folder.parent);
-
- this.$emit('open-folder', this.folder, silent);
- this.fetch();
- });
- },
-
- addFolder(folder, unshift = false) {
- const current = this.folder ? this.folder.id : null;
- // 追加しようとしているフォルダが、今居る階層とは違う階層のものだったら中断
- if (current != folder.parentId) return;
-
- // 追加しようとしているフォルダを既に所有してたら中断
- if (this.folders.some(f => f.id == folder.id)) return;
-
- if (unshift) {
- this.folders.unshift(folder);
- } else {
- this.folders.push(folder);
- }
- },
-
- addFile(file, unshift = false) {
- const current = this.folder ? this.folder.id : null;
- // 追加しようとしているファイルが、今居る階層とは違う階層のものだったら中断
- if (current != file.folderId) return;
-
- if (this.files.some(f => f.id == file.id)) {
- const exist = this.files.map(f => f.id).indexOf(file.id);
- Vue.set(this.files, exist, file);
- return;
- }
-
- if (unshift) {
- this.files.unshift(file);
- } else {
- this.files.push(file);
- }
- },
-
- removeFolder(folder) {
- if (typeof folder == 'object') folder = folder.id;
- this.folders = this.folders.filter(f => f.id != folder);
- },
-
- removeFile(file) {
- if (typeof file == 'object') file = file.id;
- this.files = this.files.filter(f => f.id != file);
- },
-
- appendFile(file) {
- this.addFile(file);
- },
- appendFolder(folder) {
- this.addFolder(folder);
- },
- prependFile(file) {
- this.addFile(file, true);
- },
- prependFolder(folder) {
- this.addFolder(folder, true);
- },
-
- goRoot(silent = false) {
- // すでにrootにいるなら何もしない
- if (this.folder == null && this.file == null) return;
-
- this.file = null;
- this.folder = null;
- this.hierarchyFolders = [];
- this.$emit('move-root', silent);
- this.fetch();
- },
-
- fetch() {
- this.folders = [];
- this.files = [];
- this.moreFolders = false;
- this.moreFiles = false;
- this.fetching = true;
-
- this.$emit('begin-fetch');
-
- let fetchedFolders = null;
- let fetchedFiles = null;
-
- const foldersMax = 20;
- const filesMax = 20;
-
- // フォルダ一覧取得
- this.$root.api('drive/folders', {
- folderId: this.folder ? this.folder.id : null,
- limit: foldersMax + 1
- }).then(folders => {
- if (folders.length == foldersMax + 1) {
- this.moreFolders = true;
- folders.pop();
- }
- fetchedFolders = folders;
- complete();
- });
-
- // ファイル一覧取得
- this.$root.api('drive/files', {
- folderId: this.folder ? this.folder.id : null,
- limit: filesMax + 1
- }).then(files => {
- if (files.length == filesMax + 1) {
- this.moreFiles = true;
- files.pop();
- }
- fetchedFiles = files;
- complete();
- });
-
- let flag = false;
- const complete = () => {
- if (flag) {
- for (const x of fetchedFolders) this.appendFolder(x);
- for (const x of fetchedFiles) this.appendFile(x);
- this.fetching = false;
-
- // 一連の読み込みが完了したイベントを発行
- this.$emit('fetched');
- } else {
- flag = true;
- // 一連の読み込みが半分完了したイベントを発行
- this.$emit('fetch-mid');
- }
- };
-
- if (this.folder == null) {
- // Fetch addtional drive info
- this.$root.api('drive').then(info => {
- this.info = info;
- });
- }
- },
-
- fetchMoreFiles() {
- this.fetching = true;
- this.fetchingMoreFiles = true;
-
- const max = 30;
-
- // ファイル一覧取得
- this.$root.api('drive/files', {
- folderId: this.folder ? this.folder.id : null,
- limit: max + 1,
- untilId: this.files[this.files.length - 1].id
- }).then(files => {
- if (files.length == max + 1) {
- this.moreFiles = true;
- files.pop();
- } else {
- this.moreFiles = false;
- }
- for (const x of files) this.appendFile(x);
- this.fetching = false;
- this.fetchingMoreFiles = false;
- });
- },
-
- chooseFile(file) {
- if (this.isFileSelectMode) {
- if (this.multiple) {
- if (this.selectedFiles.some(f => f.id == file.id)) {
- this.selectedFiles = this.selectedFiles.filter(f => f.id != file.id);
- } else {
- this.selectedFiles.push(file);
- }
- this.$emit('change-selection', this.selectedFiles);
- } else {
- this.$emit('selected', file);
- }
- } else {
- this.cf(file);
- }
- },
-
- cf(file, silent = false) {
- if (typeof file == 'object') file = file.id;
-
- this.fetching = true;
-
- this.$root.api('drive/files/show', {
- fileId: file
- }).then(file => {
- this.file = file;
- this.folder = null;
- this.hierarchyFolders = [];
-
- if (file.folder) this.dive(file.folder);
-
- this.fetching = false;
-
- this.$emit('open-file', this.file, silent);
- });
- },
-
- selectLocalFile() {
- (this.$refs.file as any).click();
- },
-
- createFolder() {
- this.$root.dialog({
- title: this.$t('folder-name'),
- input: {
- default: this.folder.name
- }
- }).then(({ result: name }) => {
- if (!name) {
- this.$root.dialog({
- type: 'error',
- text: this.$t('folder-name-cannot-empty')
- });
- return;
- }
- this.$root.api('drive/folders/create', {
- name: name,
- parentId: this.folder ? this.folder.id : undefined
- }).then(folder => {
- this.addFolder(folder, true);
- });
- });
- },
-
- renameFolder() {
- if (this.folder == null) {
- this.$root.dialog({
- type: 'error',
- text: this.$t('here-is-root')
- });
- return;
- }
- this.$root.dialog({
- title: this.$t('folder-name'),
- input: {
- default: this.folder.name
- }
- }).then(({ result: name }) => {
- if (!name) {
- this.$root.dialog({
- type: 'error',
- text: this.$t('cannot-empty')
- });
- return;
- }
- this.$root.api('drive/folders/update', {
- name: name,
- folderId: this.folder.id
- }).then(folder => {
- this.cd(folder);
- });
- });
- },
-
- moveFolder() {
- if (this.folder == null) {
- this.$root.dialog({
- type: 'error',
- text: this.$t('here-is-root')
- });
- return;
- }
- this.$chooseDriveFolder().then(folder => {
- this.$root.api('drive/folders/update', {
- parentId: folder ? folder.id : null,
- folderId: this.folder.id
- }).then(folder => {
- this.cd(folder);
- });
- });
- },
-
- urlUpload() {
- const url = window.prompt(this.$t('url-prompt'));
- if (url == null || url == '') return;
- this.$root.api('drive/files/upload_from_url', {
- url: url,
- folderId: this.folder ? this.folder.id : undefined
- });
- this.$root.dialog({
- type: 'info',
- text: this.$t('uploading')
- });
- },
-
- onChangeLocalFile() {
- for (const f of Array.from((this.$refs.file as any).files)) {
- (this.$refs.uploader as any).upload(f, this.folder);
- }
- },
-
- deleteFolder() {
- if (this.folder == null) {
- this.$root.dialog({
- type: 'error',
- text: this.$t('here-is-root')
- });
- return;
- }
- this.$root.api('drive/folders/delete', {
- folderId: this.folder.id
- }).then(folder => {
- this.cd(this.folder.parentId);
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.kmmwchoexgckptowjmjgfsygeltxfeqs
- background var(--face)
-
- > nav
- display block
- position sticky
- position -webkit-sticky
- top 0
- z-index 1
- width 100%
- padding 10px 12px
- overflow auto
- white-space nowrap
- font-size 0.9em
- color var(--text)
- -webkit-backdrop-filter blur(12px)
- backdrop-filter blur(12px)
- background-color var(--mobileDriveNavBg)
- border-bottom solid 1px rgba(#000, 0.13)
-
- > p
- > a
- display inline
- margin 0
- padding 0
- text-decoration none !important
- color inherit
-
- &:last-child
- font-weight bold
-
- > [data-icon]
- margin-right 4px
-
- > span
- margin 0 8px
- opacity 0.5
-
- > .browser
- &.fetching
- opacity 0.5
-
- > .info
- border-bottom solid 1px var(--faceDivider)
-
- &:empty
- display none
-
- > p
- display block
- max-width 500px
- margin 0 auto
- padding 4px 16px
- font-size 10px
- color var(--text)
-
- > .folders
- > .folder
- border-bottom solid 1px var(--faceDivider)
-
- > .files
- > .file
- border-bottom solid 1px var(--faceDivider)
-
- > .more
- display block
- width 100%
- padding 16px
- font-size 16px
- color #555
-
- > .empty
- padding 16px
- text-align center
- color #999
- pointer-events none
-
- > p
- margin 0
-
- > .fetching
- .spinner
- margin 100px auto
- width 40px
- height 40px
- text-align center
-
- animation sk-rotate 2.0s infinite linear
-
- .dot1, .dot2
- width 60%
- height 60%
- display inline-block
- position absolute
- top 0
- background rgba(#000, 0.2)
- border-radius 100%
-
- animation sk-bounce 2.0s infinite ease-in-out
-
- .dot2
- top auto
- bottom 0
- animation-delay -1.0s
-
- @keyframes sk-rotate {
- 100% {
- transform: rotate(360deg);
- }
- }
-
- @keyframes sk-bounce {
- 0%, 100% {
- transform: scale(0.0);
- }
- 50% {
- transform: scale(1.0);
- }
- }
-
- > .file
- display none
-
-</style>
diff --git a/src/client/app/mobile/views/components/index.ts b/src/client/app/mobile/views/components/index.ts
deleted file mode 100644
index 4e10d80f92..0000000000
--- a/src/client/app/mobile/views/components/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import Vue from 'vue';
-
-import ui from './ui.vue';
-import note from './note.vue';
-import notes from './notes.vue';
-import mediaVideo from './media-video.vue';
-import notePreview from './note-preview.vue';
-import subNoteContent from './sub-note-content.vue';
-import noteCard from './note-card.vue';
-import noteDetail from './note-detail.vue';
-import notification from './notification.vue';
-import notifications from './notifications.vue';
-import notificationPreview from './notification-preview.vue';
-import userTimeline from './user-timeline.vue';
-import userListTimeline from './user-list-timeline.vue';
-import uiContainer from './ui-container.vue';
-
-Vue.component('mk-ui', ui);
-Vue.component('mk-note', note);
-Vue.component('mk-notes', notes);
-Vue.component('mk-media-video', mediaVideo);
-Vue.component('mk-note-preview', notePreview);
-Vue.component('mk-sub-note-content', subNoteContent);
-Vue.component('mk-note-card', noteCard);
-Vue.component('mk-note-detail', noteDetail);
-Vue.component('mk-notification', notification);
-Vue.component('mk-notifications', notifications);
-Vue.component('mk-notification-preview', notificationPreview);
-Vue.component('mk-user-timeline', userTimeline);
-Vue.component('mk-user-list-timeline', userListTimeline);
-Vue.component('ui-container', uiContainer);
diff --git a/src/client/app/mobile/views/components/media-video.vue b/src/client/app/mobile/views/components/media-video.vue
deleted file mode 100644
index 044bb4c106..0000000000
--- a/src/client/app/mobile/views/components/media-video.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<template>
-<div class="icozogqfvdetwohsdglrbswgrejoxbdj" v-if="video.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
- <div>
- <b><fa icon="exclamation-triangle"/> {{ $t('sensitive') }}</b>
- <span>{{ $t('click-to-show') }}</span>
- </div>
-</div>
-<a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else
- :href="video.url"
- rel="nofollow noopener"
- target="_blank"
- :style="imageStyle"
- :title="video.name"
->
- <fa :icon="['far', 'play-circle']"/>
-</a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/media-video.vue'),
- props: {
- video: {
- type: Object,
- required: true
- }
- },
- data() {
- return {
- hide: true
- };
- },
- computed: {
- imageStyle(): any {
- return {
- 'background-image': `url(${this.video.thumbnailUrl})`
- };
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.kkjnbbplepmiyuadieoenjgutgcmtsvu
- display flex
- justify-content center
- align-items center
-
- font-size 3.5em
- overflow hidden
- background-position center
- background-size cover
- width 100%
- height 100%
-
-.icozogqfvdetwohsdglrbswgrejoxbdj
- display flex
- justify-content center
- align-items center
- background #111
- color #fff
-
- > div
- display table-cell
- text-align center
- font-size 12px
-
- > b
- display block
-
-</style>
diff --git a/src/client/app/mobile/views/components/note-card.vue b/src/client/app/mobile/views/components/note-card.vue
deleted file mode 100644
index 2704820318..0000000000
--- a/src/client/app/mobile/views/components/note-card.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<template>
-<div class="mk-note-card">
- <a :href="note | notePage">
- <header>
- <img :src="avator" alt="avatar"/>
- <h3><mk-user-name :user="note.user"/></h3>
- </header>
- <div>
- {{ text }}
- </div>
- <mk-time :time="note.createdAt"/>
- </a>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import summary from '../../../../../misc/get-note-summary';
-import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
-
-export default Vue.extend({
- props: ['note'],
- computed: {
- text(): string {
- return summary(this.note);
- },
- avator(): string {
- return this.$store.state.device.disableShowingAnimatedImages
- ? getStaticImageUrl(this.note.user.avatarUrl)
- : this.note.user.avatarUrl;
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-note-card
- display inline-block
- width 150px
- //height 120px
- font-size 12px
- background var(--face)
- border-radius 4px
- box-shadow 0 2px 8px rgba(0, 0, 0, 0.2)
-
- > a
- display block
- color var(--noteText)
-
- &:hover
- text-decoration none
-
- > header
- > img
- position absolute
- top 8px
- left 8px
- width 28px
- height 28px
- border-radius 6px
-
- > h3
- display inline-block
- overflow hidden
- width calc(100% - 45px)
- margin 8px 0 0 42px
- line-height 28px
- white-space nowrap
- text-overflow ellipsis
- font-size 12px
-
- > div
- padding 2px 8px 8px 8px
- height 60px
- overflow hidden
- white-space normal
-
- &:after
- content ""
- display block
- position absolute
- top 40px
- left 0
- width 100%
- height 20px
- background linear-gradient(to bottom, transparent 0%, var(--face) 100%)
-
- > .mk-time
- display inline-block
- padding 8px
- color var(--text)
-
-</style>
diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue
deleted file mode 100644
index 358b827a5c..0000000000
--- a/src/client/app/mobile/views/components/note-detail.vue
+++ /dev/null
@@ -1,351 +0,0 @@
-<template>
-<div class="mk-note-detail" tabindex="-1" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
- <button
- class="more"
- v-if="appearNote.reply && appearNote.reply.replyId && conversation.length == 0"
- @click="fetchConversation"
- :disabled="conversationFetching"
- >
- <template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template>
- <template v-if="conversationFetching"><fa icon="spinner" pulse/></template>
- </button>
- <div class="conversation">
- <x-sub v-for="note in conversation" :key="note.id" :note="note"/>
- </div>
- <div class="reply-to" v-if="appearNote.reply">
- <x-sub :note="appearNote.reply"/>
- </div>
- <mk-renote class="renote" v-if="isRenote" :note="note" mini/>
- <article>
- <header>
- <mk-avatar class="avatar" :user="appearNote.user"/>
- <div>
- <router-link class="name" :to="appearNote.user | userPage"><mk-user-name :user="appearNote.user"/></router-link>
- <span class="username"><mk-acct :user="appearNote.user"/></span>
- </div>
- </header>
- <div class="body">
- <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" />
- <mk-cw-button v-model="showContent" :note="appearNote"/>
- </p>
- <div class="content" v-show="appearNote.cw == null || showContent">
- <div class="text">
- <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
- <span v-if="appearNote.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span>
- <mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
- </div>
- <div class="files" v-if="appearNote.files.length > 0">
- <mk-media-list :media-list="appearNote.files" :raw="true"/>
- </div>
- <mk-poll v-if="appearNote.poll" :note="appearNote"/>
- <mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
- <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a>
- <div class="map" v-if="appearNote.geo" ref="map"></div>
- <div class="renote" v-if="appearNote.renote">
- <mk-note-preview :note="appearNote.renote"/>
- </div>
- </div>
- </div>
- <router-link class="time" :to="appearNote | notePage">
- <mk-time :time="appearNote.createdAt" mode="detail"/>
- </router-link>
- <div class="visibility-info">
- <span class="visibility" v-if="appearNote.visibility != 'public'">
- <fa v-if="appearNote.visibility == 'home'" icon="home"/>
- <fa v-if="appearNote.visibility == 'followers'" icon="unlock"/>
- <fa v-if="appearNote.visibility == 'specified'" icon="envelope"/>
- </span>
- <span class="localOnly" v-if="appearNote.localOnly == true"><fa icon="heart"/></span>
- </div>
- <footer>
- <mk-reactions-viewer :note="appearNote"/>
- <button @click="reply()" :title="$t('title')">
- <template v-if="appearNote.reply"><fa icon="reply-all"/></template>
- <template v-else><fa icon="reply"/></template>
- <p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
- </button>
- <button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" title="Renote">
- <fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
- </button>
- <button v-else>
- <fa icon="ban"/>
- </button>
- <button v-if="!isMyNote && appearNote.myReaction == null" class="reactionButton" @click="react()" ref="reactButton">
- <fa icon="plus"/>
- </button>
- <button v-if="!isMyNote && appearNote.myReaction != null" class="reactionButton reacted" @click="undoReact(appearNote)" ref="reactButton">
- <fa icon="minus"/>
- </button>
- <button @click="menu()" ref="menuButton">
- <fa icon="ellipsis-h"/>
- </button>
- </footer>
- </article>
- <div class="replies" v-if="!compact">
- <x-sub v-for="note in replies" :key="note.id" :note="note"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import XSub from './note.sub.vue';
-import noteSubscriber from '../../../common/scripts/note-subscriber';
-import noteMixin from '../../../common/scripts/note-mixin';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/note-detail.vue'),
-
- components: {
- XSub
- },
-
- mixins: [noteMixin(), noteSubscriber('note')],
-
- props: {
- note: {
- type: Object,
- required: true
- },
- compact: {
- default: false
- }
- },
-
- data() {
- return {
- conversation: [],
- conversationFetching: false,
- replies: []
- };
- },
-
- watch: {
- note() {
- this.fetchReplies();
- }
- },
-
- mounted() {
- this.fetchReplies();
- },
-
- methods: {
- fetchReplies() {
- if (this.compact) return;
- this.$root.api('notes/children', {
- noteId: this.appearNote.id,
- limit: 30
- }).then(replies => {
- this.replies = replies;
- });
- },
-
- fetchConversation() {
- this.conversationFetching = true;
-
- // Fetch conversation
- this.$root.api('notes/conversation', {
- noteId: this.appearNote.replyId
- }).then(conversation => {
- this.conversationFetching = false;
- this.conversation = conversation.reverse();
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-note-detail
- overflow hidden
- width 100%
- text-align left
- background var(--face)
-
- &.round
- border-radius 8px
-
- &.shadow
- box-shadow 0 4px 16px rgba(#000, 0.1)
-
- @media (min-width 500px)
- box-shadow 0 8px 32px rgba(#000, 0.1)
-
- > .fetching
- padding 64px 0
-
- > .more
- display block
- margin 0
- padding 10px 0
- width 100%
- font-size 1em
- text-align center
- color var(--text)
- cursor pointer
- background var(--subNoteBg)
- outline none
- border none
- border-bottom solid 1px var(--faceDivider)
- border-radius 6px 6px 0 0
- box-shadow none
-
- &:hover
- box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
-
- &:active
- box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
-
- > .conversation
- > *
- border-bottom 1px solid var(--faceDivider)
-
- > .renote + article
- padding-top 8px
-
- > .reply-to
- border-bottom 1px solid var(--faceDivider)
-
- > article
- padding 14px 16px 9px 16px
-
- @media (min-width 500px)
- padding 28px 32px 18px 32px
-
- > header
- display flex
- line-height 1.1em
-
- > .avatar
- display block
- margin 0 12px 0 0
- width 54px
- height 54px
- border-radius 8px
-
- @media (min-width 500px)
- width 60px
- height 60px
-
- > div
- min-width 0
-
- > .name
- display inline-block
- margin .4em 0
- color var(--noteHeaderName)
- font-size 16px
- font-weight bold
- text-align left
- text-decoration none
-
- &:hover
- text-decoration underline
-
- > .username
- display block
- text-align left
- margin 0
- color var(--noteHeaderAcct)
-
- > .body
- padding 8px 0
-
- > .cw
- cursor default
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- color var(--noteText)
-
- > .text
- margin-right 8px
-
- > .content
-
- > .text
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- font-size 16px
- color var(--noteText)
-
- @media (min-width 500px)
- font-size 24px
-
- > .renote
- margin 8px 0
-
- > *
- padding 16px
- border dashed 1px var(--quoteBorder)
- border-radius 8px
-
- > .location
- margin 4px 0
- font-size 12px
- color var(--text)
-
- > .map
- width 100%
- height 200px
-
- &:empty
- display none
-
- > .mk-url-preview
- margin-top 8px
-
- > .files
- > img
- display block
- max-width 100%
-
- > .time
- font-size 16px
- color var(--noteHeaderInfo)
-
- > .visibility-info
- color var(--noteHeaderInfo)
-
- > .localOnly
- margin-left 4px
-
- > footer
- font-size 1.2em
-
- > button
- margin 0
- padding 8px
- background transparent
- border none
- box-shadow none
- font-size 1em
- color var(--noteActions)
- cursor pointer
-
- &:not(:last-child)
- margin-right 28px
-
- &:hover
- color var(--noteActionsHover)
-
- > .count
- display inline
- margin 0 0 0 8px
- color var(--text)
- opacity 0.7
-
- &.reacted
- color var(--primary)
-
- > .replies
- > *
- border-top 1px solid var(--faceDivider)
-
-</style>
diff --git a/src/client/app/mobile/views/components/note-preview.vue b/src/client/app/mobile/views/components/note-preview.vue
deleted file mode 100644
index 1dbbddaa62..0000000000
--- a/src/client/app/mobile/views/components/note-preview.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-<template>
-<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">
- <p v-if="note.cw != null" class="cw">
- <span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
- <mk-cw-button v-model="showContent" :note="note"/>
- </p>
- <div class="content" v-show="note.cw == null || showContent">
- <mk-sub-note-content class="text" :note="note"/>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- note: {
- type: Object,
- required: true
- }
- },
-
- inject: {
- narrow: {
- default: false
- }
- },
-
- data() {
- return {
- showContent: false
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.yohlumlkhizgfkvvscwfcrcggkotpvry
- display flex
- margin 0
- padding 0
- overflow hidden
- font-size 10px
-
- &:not(.mini)
-
- @media (min-width 350px)
- font-size 12px
-
- @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
- width 100%
-
- > header
- align-items center
-
- > .avatar
- flex-shrink 0
- display block
- margin 0 10px 0 0
- width 40px
- height 40px
- border-radius 8px
-
- > .main
- flex 1
- min-width 0
-
- > .header
- margin-bottom 2px
-
- > .body
-
- > .cw
- cursor default
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- color var(--noteText)
-
- > .text
- margin-right 8px
-
- > .content
- > .text
- cursor default
- margin 0
- padding 0
- color var(--subNoteText)
-
-</style>
diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue
deleted file mode 100644
index b951947f2a..0000000000
--- a/src/client/app/mobile/views/components/note.sub.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<template>
-<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"/>
- <div class="body">
- <p v-if="note.cw != null" class="cw">
- <mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis" />
- <mk-cw-button v-model="showContent" :note="note"/>
- </p>
- <div class="content" v-show="note.cw == null || showContent">
- <mk-sub-note-content class="text" :note="note"/>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- note: {
- type: Object,
- required: true
- },
- // TODO
- truncate: {
- type: Boolean,
- default: true
- }
- },
-
- inject: {
- narrow: {
- default: false
- }
- },
-
- data() {
- return {
- showContent: false
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.zlrxdaqttccpwhpaagdmkawtzklsccam
- display flex
- padding 16px
- font-size 10px
- background var(--subNoteBg)
-
- &:not(.mini)
-
- @media (min-width 350px)
- font-size 12px
-
- @media (min-width 500px)
- font-size 14px
-
- @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
- width 100%
-
- > header
- align-items center
-
- > .avatar
- flex-shrink 0
- display block
- margin 0 8px 0 0
- width 38px
- height 38px
- border-radius 8px
-
- > .main
- flex 1
- min-width 0
-
- > .header
- margin-bottom 2px
-
- > .body
- > .cw
- cursor default
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- color var(--noteText)
-
- > .text
- margin-right 8px
-
- > .content
- > .text
- margin 0
- padding 0
- color var(--subNoteText)
- font-size calc(1em + var(--fontSize))
-
- pre
- max-height 120px
- font-size 80%
-
-</style>
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
deleted file mode 100644
index 01514f05fc..0000000000
--- a/src/client/app/mobile/views/components/note.vue
+++ /dev/null
@@ -1,302 +0,0 @@
-<template>
-<div
- class="note"
- v-show="appearNote.deletedAt == null && !hideThisNote"
- :tabindex="appearNote.deletedAt == null ? '-1' : null"
- :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart', mini: narrow }"
- v-hotkey="keymap"
->
- <x-sub v-for="note in conversation" :key="note.id" :note="note"/>
- <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"/>
- <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"/>
- <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" />
- <mk-cw-button v-model="showContent" :note="appearNote"/>
- </p>
- <div class="content" v-show="appearNote.cw == null || showContent">
- <div class="text">
- <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
- <a class="reply" v-if="appearNote.reply"><fa icon="reply"/></a>
- <mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
- <a class="rp" v-if="appearNote.renote != null">RN:</a>
- </div>
- <div class="files" v-if="appearNote.files.length > 0">
- <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="true"/>
- <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" 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>
- <span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span>
- </div>
- <footer v-if="appearNote.deletedAt == null" class="footer">
- <mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
- <button @click="reply()" class="button">
- <template v-if="appearNote.reply"><fa icon="reply-all"/></template>
- <template v-else><fa icon="reply"/></template>
- <p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
- </button>
- <button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" title="Renote" class="button">
- <fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
- </button>
- <button v-else class="button">
- <fa icon="ban"/>
- </button>
- <button v-if="!isMyNote && appearNote.myReaction == null" class="button" @click="react()" ref="reactButton">
- <fa icon="plus"/>
- </button>
- <button v-if="!isMyNote && appearNote.myReaction != null" class="button reacted" @click="undoReact(appearNote)" ref="reactButton">
- <fa icon="minus"/>
- </button>
- <button class="button" @click="menu()" ref="menuButton">
- <fa icon="ellipsis-h"/>
- </button>
- </footer>
- <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"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-import XSub from './note.sub.vue';
-import noteMixin from '../../../common/scripts/note-mixin';
-import noteSubscriber from '../../../common/scripts/note-subscriber';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/note.vue'),
- components: {
- XSub
- },
-
- mixins: [
- noteMixin({
- mobile: true
- }),
- noteSubscriber('note')
- ],
-
- props: {
- note: {
- type: Object,
- required: true
- },
- detail: {
- type: Boolean,
- required: false,
- default: false
- },
- },
-
- inject: {
- narrow: {
- default: false
- }
- },
-
- data() {
- return {
- conversation: [],
- replies: []
- };
- },
-
- created() {
- if (this.detail) {
- this.$root.api('notes/children', {
- noteId: this.appearNote.id,
- limit: 30
- }).then(replies => {
- this.replies = replies;
- });
-
- this.$root.api('notes/conversation', {
- noteId: this.appearNote.replyId
- }).then(conversation => {
- this.conversation = conversation.reverse();
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.note
- overflow hidden
- font-size 13px
- border-bottom solid var(--lineWidth) var(--faceDivider)
-
- &:last-of-type
- border-bottom none
-
- &:not(.mini)
-
- @media (min-width 350px)
- font-size 14px
-
- @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
- > .main
- > header
- align-items center
- margin-bottom 4px
-
- > .renote + .article
- padding-top 8px
-
- > .article
- display flex
- padding 16px 16px 9px
-
- > .avatar
- flex-shrink 0
- display block
- margin 0 10px 8px 0
- width 42px
- height 42px
- border-radius 6px
- //position -webkit-sticky
- //position sticky
- //top 62px
-
- > .main
- flex 1
- min-width 0
-
- > .body
- > .cw
- cursor default
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- color var(--noteText)
-
- > .text
- margin-right 8px
-
- > .content
-
- > .text
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- color var(--noteText)
- font-size calc(1em + var(--fontSize))
-
- > .reply
- margin-right 8px
- color var(--noteText)
-
- > .rp
- margin-left 4px
- font-style oblique
- color var(--renoteText)
-
- .mk-url-preview
- margin-top 8px
-
- > .files
- > img
- display block
- max-width 100%
-
- > .location
- margin 4px 0
- font-size 12px
- color #ccc
-
- > .map
- width 100%
- height 200px
-
- &:empty
- display none
-
- > .mk-poll
- font-size 80%
-
- > .renote
- margin 8px 0
-
- > *
- padding 16px
- border dashed var(--lineWidth) var(--quoteBorder)
- border-radius 8px
-
- > .app
- font-size 12px
- color #ccc
-
- > .footer
- > .button
- margin 0
- padding 8px
- background transparent
- border none
- box-shadow none
- font-size 1em
- color var(--noteActions)
- cursor pointer
-
- &:not(:last-child)
- margin-right 28px
-
- &:hover
- color var(--noteActionsHover)
-
- > .count
- display inline
- margin 0 0 0 8px
- color var(--text)
- opacity 0.7
-
- &.reacted
- color var(--primary)
-
- > .deleted
- color var(--noteText)
- opacity 0.7
-
-</style>
diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue
deleted file mode 100644
index 1a0cd5cc24..0000000000
--- a/src/client/app/mobile/views/components/notes.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<template>
-<div class="ivaojijs" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
- <div class="empty" v-if="empty">{{ $t('@.no-notes') }}</div>
-
- <mk-error v-if="error" @retry="init()"/>
-
- <div class="placeholder" v-if="fetching">
- <template v-for="i in 10">
- <mk-note-skeleton :key="i"/>
- </template>
- </div>
-
- <!-- トランジションを有効にするとなぜかメモリリークする -->
- <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"/>
- <p class="date" :key="note.id + '_date'" v-if="i != items.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>
- </p>
- </template>
- </component>
-
- <footer v-if="more">
- <button @click="fetchMore()" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
- <template v-if="!moreFetching">{{ $t('@.load-more') }}</template>
- <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
- </button>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import shouldMuteNote from '../../../common/scripts/should-mute-note';
-import paging from '../../../common/scripts/paging';
-
-export default Vue.extend({
- i18n: i18n(),
-
- mixins: [
- paging({
- captureWindowScroll: true,
-
- onQueueChanged: (self, x) => {
- if (x.length > 0) {
- self.$store.commit('indicate', true);
- } else {
- self.$store.commit('indicate', false);
- }
- },
-
- onPrepend: (self, note) => {
- // 弾く
- if (shouldMuteNote(self.$store.state.i, self.$store.state.settings, note)) return false;
-
- // タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
- if (document.hidden || !self.isScrollTop()) {
- self.$store.commit('pushBehindNote', note);
- }
- },
-
- onInited: (self) => {
- self.$emit('loaded');
- }
- }),
- ],
-
- props: {
- pagination: {
- required: true
- },
- },
-
- computed: {
- _notes(): any[] {
- return (this.items as any).map(item => {
- const date = new Date(item.createdAt).getDate();
- const month = new Date(item.createdAt).getMonth() + 1;
- item._date = date;
- item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
- return item;
- });
- }
- },
-});
-</script>
-
-<style lang="stylus" scoped>
-.ivaojijs
- overflow hidden
- background var(--face)
-
- &.round
- border-radius 8px
-
- &.shadow
- box-shadow 0 4px 16px rgba(#000, 0.1)
-
- @media (min-width 500px)
- box-shadow 0 8px 32px rgba(#000, 0.1)
-
- > .empty
- padding 16px
- text-align center
- color var(--text)
-
- .transition
- .mk-notes-enter
- .mk-notes-leave-to
- opacity 0
- transform translateY(-30px)
-
- > *
- transition transform .3s ease, opacity .3s ease
-
- > .date
- display block
- margin 0
- line-height 32px
- text-align center
- font-size 0.9em
- color var(--dateDividerFg)
- background var(--dateDividerBg)
- border-bottom solid var(--lineWidth) var(--faceDivider)
-
- span
- margin 0 16px
-
- [data-icon]
- margin-right 8px
-
- > .placeholder
- padding 16px
- opacity 0.3
-
- @media (min-width 500px)
- padding 32px
-
- > .empty
- margin 0 auto
- padding 32px
- max-width 400px
- text-align center
- color var(--text)
-
- > footer
- text-align center
- border-top solid var(--lineWidth) var(--faceDivider)
-
- &:empty
- display none
-
- > button
- margin 0
- padding 16px
- width 100%
- color var(--text)
-
- @media (min-width 500px)
- padding 20px
-
- &:disabled
- opacity 0.7
-
-</style>
diff --git a/src/client/app/mobile/views/components/notification-preview.vue b/src/client/app/mobile/views/components/notification-preview.vue
deleted file mode 100644
index 8422c73420..0000000000
--- a/src/client/app/mobile/views/components/notification-preview.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-<template>
-<div class="mk-notification-preview" :class="notification.type">
- <template v-if="notification.type == 'reaction'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div class="text">
- <p><mk-reaction-icon :reaction="notification.reaction"/><mk-user-name :user="notification.user"/></p>
- <p class="note-ref"><fa icon="quote-left"/>{{ getNoteSummary(notification.note) }}<fa icon="quote-right"/></p>
- </div>
- </template>
-
- <template v-if="notification.type == 'renote'">
- <mk-avatar class="avatar" :user="notification.note.user"/>
- <div class="text">
- <p><fa icon="retweet"/><mk-user-name :user="notification.note.user"/></p>
- <p class="note-ref"><fa icon="quote-left"/>{{ getNoteSummary(notification.note.renote) }}<fa icon="quote-right"/></p>
- </div>
- </template>
-
- <template v-if="notification.type == 'quote'">
- <mk-avatar class="avatar" :user="notification.note.user"/>
- <div class="text">
- <p><fa icon="quote-left"/><mk-user-name :user="notification.note.user"/></p>
- <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'follow'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div class="text">
- <p><fa icon="user-plus"/><mk-user-name :user="notification.user"/></p>
- </div>
- </template>
-
- <template v-if="notification.type == 'receiveFollowRequest'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div class="text">
- <p><fa icon="user-clock"/><mk-user-name :user="notification.user"/></p>
- </div>
- </template>
-
- <template v-if="notification.type == 'reply'">
- <mk-avatar class="avatar" :user="notification.note.user"/>
- <div class="text">
- <p><fa icon="reply"/><mk-user-name :user="notification.note.user"/></p>
- <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'mention'">
- <mk-avatar class="avatar" :user="notification.note.user"/>
- <div class="text">
- <p><fa icon="at"/><mk-user-name :user="notification.note.user"/></p>
- <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'pollVote'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div class="text">
- <p><fa icon="chart-pie"/><mk-user-name :user="notification.user"/></p>
- <p class="note-ref"><fa icon="quote-left"/>{{ getNoteSummary(notification.note) }}<fa icon="quote-right"/></p>
- </div>
- </template>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getNoteSummary from '../../../../../misc/get-note-summary';
-
-export default Vue.extend({
- props: ['notification'],
- data() {
- return {
- getNoteSummary
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notification-preview
- margin 0
- padding 8px
- color #fff
- overflow-wrap break-word
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar
- display block
- float left
- width 36px
- height 36px
- border-radius 6px
-
- > .text
- float right
- width calc(100% - 36px)
- padding-left 8px
-
- p
- margin 0
-
- [data-icon], mk-reaction-icon
- margin-right 4px
-
- .note-ref
-
- [data-icon]
- font-size 1em
- font-weight normal
- font-style normal
- display inline-block
- margin-right 3px
-
- &.renote, &.quote
- .text p [data-icon]
- color #77B255
-
- &.follow
- .text p [data-icon]
- color #53c7ce
-
- &.receiveFollowRequest
- .text p [data-icon]
- color #888
-
- &.reply, &.mention
- .text p [data-icon]
- color #fff
-
-</style>
-
diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue
deleted file mode 100644
index 2defef4777..0000000000
--- a/src/client/app/mobile/views/components/notification.vue
+++ /dev/null
@@ -1,199 +0,0 @@
-<template>
-<div class="mk-notification">
- <div class="notification reaction" v-if="notification.type == 'reaction'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div>
- <header>
- <mk-reaction-icon :reaction="notification.reaction"/>
- <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
- <mk-time :time="notification.createdAt"/>
- </header>
- <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
- <fa icon="quote-left"/>
- <mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
- <fa icon="quote-right"/>
- </router-link>
- </div>
- </div>
-
- <div class="notification renote" v-if="notification.type == 'renote'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div>
- <header>
- <fa icon="retweet"/>
- <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
- <mk-time :time="notification.createdAt"/>
- </header>
- <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
- <fa icon="quote-left"/>
- <mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/>
- <fa icon="quote-right"/>
- </router-link>
- </div>
- </div>
-
- <div class="notification follow" v-if="notification.type == 'follow'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div>
- <header>
- <fa icon="user-plus"/>
- <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
- <mk-time :time="notification.createdAt"/>
- </header>
- </div>
- </div>
-
- <div class="notification followRequest" v-if="notification.type == 'receiveFollowRequest'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div>
- <header>
- <fa icon="user-clock"/>
- <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
- <mk-time :time="notification.createdAt"/>
- </header>
- </div>
- </div>
-
- <div class="notification pollVote" v-if="notification.type == 'pollVote'">
- <mk-avatar class="avatar" :user="notification.user"/>
- <div>
- <header>
- <fa icon="chart-pie"/>
- <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
- <mk-time :time="notification.createdAt"/>
- </header>
- <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
- <fa icon="quote-left"/>
- <mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
- <fa icon="quote-right"/>
- </router-link>
- </div>
- </div>
-
- <template v-if="notification.type == 'quote'">
- <mk-note :note="notification.note"/>
- </template>
-
- <template v-if="notification.type == 'reply'">
- <mk-note :note="notification.note"/>
- </template>
-
- <template v-if="notification.type == 'mention'">
- <mk-note :note="notification.note"/>
- </template>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getNoteSummary from '../../../../../misc/get-note-summary';
-
-export default Vue.extend({
- props: ['notification'],
- data() {
- return {
- getNoteSummary
- };
- },
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notification
-
- &.wide
- > .notification
- @media (min-width 350px)
- font-size 14px
-
- @media (min-width 500px)
- font-size 16px
-
- @media (min-width 600px)
- padding 24px 32px
-
- > .avatar
- @media (min-width 500px)
- width 42px
- height 42px
-
- > div
- @media (min-width 500px)
- width calc(100% - 42px)
-
- > .notification
- padding 16px
- font-size 12px
- overflow-wrap break-word
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar
- display block
- float left
- width 36px
- height 36px
- border-radius 6px
-
- > div
- float right
- width calc(100% - 36px)
- padding-left 8px
-
- > header
- display flex
- align-items baseline
- white-space nowrap
-
- [data-icon], .mk-reaction-icon
- margin-right 4px
-
- > .name
- text-overflow ellipsis
- white-space nowrap
- min-width 0
- overflow hidden
-
- > .mk-time
- margin-left auto
- color var(--noteHeaderInfo)
- font-size 0.9em
-
- > .note-preview
- color var(--noteText)
-
- > .note-ref
- color var(--noteText)
- display inline-block
- width: 100%
- overflow hidden
- white-space nowrap
- text-overflow ellipsis
-
- [data-icon]
- font-size 1em
- font-weight normal
- font-style normal
- display inline-block
- margin-right 3px
-
- &.reaction
- > div > header
- align-items normal
-
- &.renote
- > div > header [data-icon]
- color #77B255
-
- &.follow
- > div > header [data-icon]
- color #53c7ce
-
- &.receiveFollowRequest
- > div > header [data-icon]
- color #888
-
-</style>
diff --git a/src/client/app/mobile/views/components/notifications.vue b/src/client/app/mobile/views/components/notifications.vue
deleted file mode 100644
index ca6a8beca3..0000000000
--- a/src/client/app/mobile/views/components/notifications.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<template>
-<div class="mk-notifications">
- <div class="placeholder" v-if="fetching">
- <template v-for="i in 10">
- <mk-note-skeleton :key="i"/>
- </template>
- </div>
-
- <!-- トランジションを有効にするとなぜかメモリリークする -->
- <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
- <template v-for="(notification, i) in _notifications">
- <mk-notification :notification="notification" :key="notification.id" :class="{ wide: wide }"/>
- <p class="date" :key="notification.id + '_date'" v-if="i != items.length - 1 && notification._date != _notifications[i + 1]._date">
- <span><fa icon="angle-up"/>{{ notification._datetext }}</span>
- <span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
- </p>
- </template>
- </component>
-
- <button class="more" v-if="more" @click="fetchMore" :disabled="moreFetching">
- <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
- {{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
- </button>
-
- <p class="empty" v-if="empty">{{ $t('empty') }}</p>
-
- <mk-error v-if="error" @retry="init()"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import paging from '../../../common/scripts/paging';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/notifications.vue'),
-
- mixins: [
- paging({
- beforeInit: (self) => {
- self.$emit('beforeInit');
- },
- onInited: (self) => {
- self.$emit('inited');
- }
- }),
- ],
-
- props: {
- type: {
- type: String,
- required: false
- },
- wide: {
- type: Boolean,
- required: false,
- default: false
- }
- },
-
- data() {
- return {
- connection: null,
- pagination: {
- endpoint: 'i/notifications',
- limit: 15,
- params: () => ({
- includeTypes: this.type ? [this.type] : undefined
- })
- }
- };
- },
-
- computed: {
- _notifications(): any[] {
- return (this.items as any).map(notification => {
- const date = new Date(notification.createdAt).getDate();
- const month = new Date(notification.createdAt).getMonth() + 1;
- notification._date = date;
- notification._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
- return notification;
- });
- }
- },
-
- watch: {
- type() {
- this.reload();
- }
- },
-
- mounted() {
- this.connection = this.$root.stream.useSharedConnection('main');
- this.connection.on('notification', this.onNotification);
- },
-
- beforeDestroy() {
- this.connection.dispose();
- },
-
- methods: {
- onNotification(notification) {
- // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
- this.$root.stream.send('readNotification', {
- id: notification.id
- });
-
- this.prepend(notification);
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notifications
- .transition
- .mk-notifications-enter
- .mk-notifications-leave-to
- opacity 0
- transform translateY(-30px)
-
- > *
- transition transform .3s ease, opacity .3s ease
-
- > .notifications
-
- > .mk-notification:not(:last-child)
- border-bottom solid var(--lineWidth) var(--faceDivider)
-
- > .date
- display block
- margin 0
- line-height 32px
- text-align center
- font-size 0.8em
- color var(--dateDividerFg)
- background var(--dateDividerBg)
- border-bottom solid var(--lineWidth) var(--faceDivider)
-
- span
- margin 0 16px
-
- [data-icon]
- margin-right 8px
-
- > .more
- display block
- width 100%
- padding 16px
- color var(--text)
- border-top solid var(--lineWidth) rgba(#000, 0.05)
-
- > [data-icon]
- margin-right 4px
-
- > .empty
- margin 0
- padding 16px
- text-align center
- color var(--text)
-
- > .placeholder
- padding 32px
- opacity 0.3
-
-</style>
diff --git a/src/client/app/mobile/views/components/notify.vue b/src/client/app/mobile/views/components/notify.vue
deleted file mode 100644
index c6e1df0fde..0000000000
--- a/src/client/app/mobile/views/components/notify.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
-<div class="mk-notify" :class="pos">
- <div>
- <mk-notification-preview :notification="notification"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import anime from 'animejs';
-
-export default Vue.extend({
- props: ['notification'],
- computed: {
- pos() {
- return this.$store.state.device.mobileNotificationPosition;
- }
- },
- mounted() {
- this.$nextTick(() => {
- anime({
- targets: this.$el,
- [this.pos]: '0px',
- duration: 500,
- easing: 'easeOutQuad'
- });
-
- setTimeout(() => {
- anime({
- targets: this.$el,
- [this.pos]: `-${this.$el.offsetHeight}px`,
- duration: 500,
- easing: 'easeOutQuad',
- complete: () => this.destroyDom()
- });
- }, 6000);
- });
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notify
- $height = 78px
-
- position fixed
- z-index 10000
- left 0
- right 0
- width 100%
- max-width 500px
- height $height
- margin 0 auto
- padding 8px
- pointer-events none
- font-size 80%
-
- &.bottom
- bottom -($height)
-
- &.top
- top -($height)
-
- > div
- height 100%
- -webkit-backdrop-filter blur(2px)
- backdrop-filter blur(2px)
- background-color rgba(#000, 0.5)
- border-radius 7px
- overflow hidden
-
-</style>
diff --git a/src/client/app/mobile/views/components/post-form-dialog.vue b/src/client/app/mobile/views/components/post-form-dialog.vue
deleted file mode 100644
index 4ae79dbd7b..0000000000
--- a/src/client/app/mobile/views/components/post-form-dialog.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<template>
-<ui-modal
- ref="modal"
- :close-on-bg-click="false"
- :close-anime-duration="300"
- @before-close="onBeforeClose">
- <div class="main" ref="main">
- <x-post-form ref="form"
- :reply="reply"
- :renote="renote"
- :mention="mention"
- :initial-text="initialText"
- :initial-note="initialNote"
- :instant="instant"
- @posted="onPosted"
- @cancel="onCanceled"/>
- </div>
-</ui-modal>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import anime from 'animejs';
-import XPostForm from './post-form.vue';
-
-export default Vue.extend({
- components: {
- XPostForm
- },
-
- props: {
- reply: {
- type: Object,
- required: false
- },
- renote: {
- type: Object,
- required: false
- },
- mention: {
- type: Object,
- required: false
- },
- initialText: {
- type: String,
- required: false
- },
- initialNote: {
- type: Object,
- required: false
- },
- instant: {
- type: Boolean,
- required: false,
- default: false
- }
- },
-
- mounted() {
- this.$nextTick(() => {
- anime({
- targets: this.$refs.main,
- opacity: 1,
- translateY: [-16, 0],
- duration: 300,
- easing: 'easeOutQuad'
- });
- });
- },
-
- methods: {
- focus() {
- this.$refs.form.focus();
- },
-
- onBeforeClose() {
- (this.$refs.main as any).style.pointerEvents = 'none';
-
- anime({
- targets: this.$refs.main,
- opacity: 0,
- translateY: 16,
- duration: 300,
- easing: 'easeOutQuad'
- });
- },
-
- close() {
- (this.$refs.modal as any).close();
- },
-
- onPosted() {
- this.$emit('posted');
- this.close();
- },
-
- onCanceled() {
- this.$emit('cancel');
- this.close();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-
-.main
- display block
- position fixed
- z-index 10000
- top 0
- left 0
- right 0
- height 100%
- overflow auto
- margin 0 auto 0 auto
- opacity 0
- transform translateY(-16px)
-
-</style>
diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue
deleted file mode 100644
index 38c6a42dd5..0000000000
--- a/src/client/app/mobile/views/components/post-form.vue
+++ /dev/null
@@ -1,244 +0,0 @@
-<template>
-<div class="gafaadew">
- <div class="form"
- @dragover.stop="onDragover"
- @dragenter="onDragenter"
- @dragleave="onDragleave"
- @drop.stop="onDrop"
- >
- <header>
- <button class="cancel" @click="cancel"><fa icon="times"/></button>
- <div>
- <span class="text-count" :class="{ over: trimmedLength(text) > maxNoteTextLength }">{{ maxNoteTextLength - trimmedLength(text) }}</span>
- <span class="geo" v-if="geo"><fa icon="map-marker-alt"/></span>
- <button class="submit" :disabled="!canPost" @click="post">{{ submitText }}</button>
- </div>
- </header>
- <div class="form">
- <mk-note-preview class="preview" v-if="reply" :note="reply"/>
- <mk-note-preview class="preview" v-if="renote" :note="renote"/>
- <div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
- <div v-if="visibility === 'specified'" class="to-specified">
- <fa icon="envelope"/> {{ $t('@.post-form.specified-recipient') }}
- <div class="visibleUsers">
- <span v-for="u in visibleUsers">
- <mk-user-name :user="u"/>
- <button @click="removeVisibleUser(u)"><fa icon="times"/></button>
- </span>
- <button @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</button>
- </div>
- </div>
- <div class="local-only" v-if="localOnly === true"><fa icon="heart"/> {{ $t('@.post-form.local-only-message') }}</div>
- <input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }">
- <textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }" @paste="onPaste"></textarea>
- <x-post-form-attaches class="attaches" :files="files"/>
- <x-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
- <mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
- <footer>
- <button class="upload" @click="chooseFile"><fa icon="upload"/></button>
- <button class="drive" @click="chooseFileFromDrive"><fa icon="cloud"/></button>
- <button class="kao" @click="kao"><fa :icon="['far', 'smile']"/></button>
- <button class="poll" @click="poll = true"><fa icon="chart-pie"/></button>
- <button class="poll" @click="useCw = !useCw"><fa :icon="['far', 'eye-slash']"/></button>
- <button class="geo" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button>
- <button class="visibility" @click="setVisibility" ref="visibilityButton">
- <span v-if="visibility === 'public'"><fa icon="globe"/></span>
- <span v-if="visibility === 'home'"><fa icon="home"/></span>
- <span v-if="visibility === 'followers'"><fa icon="unlock"/></span>
- <span v-if="visibility === 'specified'"><fa icon="envelope"/></span>
- </button>
- </footer>
- <input ref="file" class="file" type="file" multiple="multiple" @change="onChangeFile"/>
- </div>
- </div>
- <div class="hashtags" v-if="recentHashtags.length > 0 && $store.state.settings.suggestRecentHashtags">
- <a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)">#{{ tag }}</a>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import form from '../../../common/scripts/post-form';
-
-export default Vue.extend({
- i18n: i18n(),
-
- mixins: [
- form({
- mobile: true
- }),
- ],
-
- methods: {
- cancel() {
- this.$emit('cancel');
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.gafaadew
- max-width 500px
- width calc(100% - 16px)
- margin 8px auto
-
- @media (min-width 500px)
- margin 16px auto
- width calc(100% - 32px)
-
- > .form
- box-shadow 0 8px 32px rgba(#000, 0.1)
-
- @media (min-width 600px)
- margin 32px auto
-
- > .form
- background var(--face)
- border-radius 8px
- box-shadow 0 0 2px rgba(#000, 0.1)
-
- > header
- z-index 1000
- height 50px
- box-shadow 0 1px 0 0 var(--mobilePostFormDivider)
-
- > .cancel
- padding 0
- width 50px
- line-height 50px
- font-size 24px
- color var(--text)
-
- > div
- position absolute
- top 0
- right 0
- color var(--text)
-
- > .text-count
- line-height 50px
-
- > .geo
- margin 0 8px
- line-height 50px
-
- > .submit
- margin 8px
- padding 0 16px
- line-height 34px
- vertical-align bottom
- color var(--primaryForeground)
- background var(--primary)
- border-radius 4px
-
- &:disabled
- opacity 0.7
-
- > .form
- max-width 500px
- margin 0 auto
-
- > .preview
- padding 16px
-
- > .with-quote
- margin 0 0 8px 0
- color var(--primary)
-
- > button
- padding 4px 8px
- color var(--primaryAlpha04)
-
- &:hover
- color var(--primaryAlpha06)
-
- &:active
- color var(--primaryDarken30)
-
- > .to-specified
- margin 0 0 8px 0
- color var(--primary)
-
- > .visibleUsers
- display inline
- top -1px
- font-size 14px
-
- > span
- margin-left 14px
-
- > button
- padding 4px 8px
- color var(--primaryAlpha04)
-
- &:hover
- color var(--primaryAlpha06)
-
- &:active
- color var(--primaryDarken30)
-
- > .local-only
- margin 0 0 8px 0
- color var(--primary)
-
- > input
- z-index 1
-
- > input
- > textarea
- display block
- padding 12px
- margin 0
- width 100%
- font-size 16px
- color var(--inputText)
- background var(--mobilePostFormTextareaBg)
- border none
- border-radius 0
- box-shadow 0 1px 0 0 var(--mobilePostFormDivider)
-
- &:disabled
- opacity 0.5
-
- > textarea
- max-width 100%
- min-width 100%
- min-height 80px
-
- > .mk-uploader
- margin 8px 0 0 0
- padding 8px
-
- > .file
- display none
-
- > footer
- white-space nowrap
- overflow auto
- -webkit-overflow-scrolling touch
- overflow-scrolling touch
-
- > *
- display inline-block
- padding 0
- margin 0
- width 48px
- height 48px
- font-size 20px
- color var(--mobilePostFormButton)
- background transparent
- outline none
- border none
- border-radius 0
- box-shadow none
-
- > .hashtags
- margin 8px
-
- > *
- margin-right 8px
-
-</style>
diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue
deleted file mode 100644
index 66dbb90ebb..0000000000
--- a/src/client/app/mobile/views/components/sub-note-content.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<template>
-<div class="mk-sub-note-content">
- <div class="body">
- <span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
- <span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span>
- <a class="reply" v-if="note.replyId"><fa icon="reply"/></a>
- <mfm v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/>
- <a class="rp" v-if="note.renoteId">RN: ...</a>
- </div>
- <details v-if="note.files.length > 0">
- <summary>({{ $t('media-count').replace('{}', note.files.length) }})</summary>
- <mk-media-list :media-list="note.files"/>
- </details>
- <details v-if="note.poll">
- <summary>{{ $t('poll') }}</summary>
- <mk-poll :note="note"/>
- </details>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-export default Vue.extend({
- i18n: i18n('mobile/views/components/sub-note-content.vue'),
- props: ['note']
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-sub-note-content
- overflow-wrap break-word
-
- > .body
- > .reply
- margin-right 6px
- color #717171
-
- > .rp
- margin-left 4px
- font-style oblique
- color var(--renoteText)
-
- mk-poll
- font-size 80%
-
-</style>
diff --git a/src/client/app/mobile/views/components/ui-container.vue b/src/client/app/mobile/views/components/ui-container.vue
deleted file mode 100644
index 08af7035f9..0000000000
--- a/src/client/app/mobile/views/components/ui-container.vue
+++ /dev/null
@@ -1,127 +0,0 @@
-<template>
-<div class="ukygtjoj" :class="{ naked, inNakedDeckColumn, hideHeader: !showHeader, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
- <header v-if="showHeader" @click="() => showBody = !showBody">
- <div class="title"><slot name="header"></slot></div>
- <slot name="func"></slot>
- <button v-if="bodyTogglable">
- <template v-if="showBody"><fa icon="angle-up"/></template>
- <template v-else><fa icon="angle-down"/></template>
- </button>
- </header>
- <div v-show="showBody">
- <slot></slot>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: {
- showHeader: {
- type: Boolean,
- default: true
- },
- naked: {
- type: Boolean,
- default: false
- },
- bodyTogglable: {
- type: Boolean,
- default: false
- },
- expanded: {
- type: Boolean,
- default: true
- },
- },
- inject: {
- inNakedDeckColumn: {
- default: false
- }
- },
- data() {
- return {
- showBody: this.expanded
- };
- },
- methods: {
- toggleContent(show: boolean) {
- if (!this.bodyTogglable) return;
- this.showBody = show;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.ukygtjoj
- overflow hidden
-
- &:not(.inNakedDeckColumn)
- background var(--face)
-
- &.round
- border-radius 8px
-
- &.shadow
- box-shadow 0 4px 16px rgba(#000, 0.1)
-
- & + .ukygtjoj
- margin-top 16px
-
- @media (max-width 500px)
- margin-top 8px
-
- &.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)
-
- > [data-icon]
- margin-right 6px
-
- &: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)
-
- > div
- color var(--text)
-
- &.inNakedDeckColumn
- background var(--face)
-
- > header
- margin 0
- padding 8px 16px
- font-size 12px
- color var(--text)
- background var(--deckColumnBg)
-
- > 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
deleted file mode 100644
index f20f64e7ff..0000000000
--- a/src/client/app/mobile/views/components/ui.header.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-<template>
-<div class="header" ref="root" :class="{ shadow: $store.state.device.useShadow }">
- <div class="main" ref="main">
- <div class="backdrop"></div>
- <div class="content" ref="mainContainer">
- <button class="nav" @click="$parent.isDrawerOpening = true"><fa icon="bars"/></button>
- <i v-if="$parent.indicate" class="circle"><fa icon="circle"/></i>
- <h1>
- <slot>{{ $root.instanceName }}</slot>
- </h1>
- <slot name="func"></slot>
- </div>
- </div>
- <div class="indicator" v-show="$store.state.indicate"></div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import { env } from '../../../config';
-
-export default Vue.extend({
- i18n: i18n(),
- props: ['func'],
-
- data() {
- return {
- env: env
- };
- },
-
- mounted() {
- this.$store.commit('setUiHeaderHeight', 48);
- },
-});
-</script>
-
-<style lang="stylus" scoped>
-.header
- $height = 48px
-
- position fixed
- top 0
- left -8px
- z-index 1024
- width calc(100% + 16px)
- padding 0 8px
-
- &.shadow
- box-shadow 0 0 8px rgba(0, 0, 0, 0.25)
-
- &, *
- user-select none
-
- > .indicator
- height 3px
- background var(--primary)
-
- > .warn
- display block
- margin 0
- padding 4px
- text-align center
- font-size 12px
- background #f00
- color #fff
-
- > .main
- color var(--mobileHeaderFg)
-
- > .backdrop
- position absolute
- top 0
- z-index 1000
- width 100%
- height $height
- -webkit-backdrop-filter blur(12px)
- backdrop-filter blur(12px)
- background-color var(--mobileHeaderBg)
-
- > .content
- z-index 1001
-
- > h1
- display block
- margin 0 auto
- padding 0
- width 100%
- max-width calc(100% - 112px)
- text-align center
- font-size 1.1em
- font-weight normal
- line-height $height
- white-space nowrap
- overflow hidden
- text-overflow ellipsis
-
- > img
- display inline-block
- vertical-align bottom
- width ($height - 16px)
- height ($height - 16px)
- margin 8px
- border-radius 6px
-
- > .nav
- display block
- position absolute
- top 0
- left 0
- padding 0
- width $height
- font-size 1.4em
- line-height $height
- border-right solid 1px rgba(#000, 0.1)
-
- > [data-icon]
- transition all 0.2s ease
-
- > i.circle
- position absolute
- top 8px
- left 8px
- pointer-events none
- font-size 10px
- color var(--notificationIndicator)
-
- > button:last-child
- display block
- position absolute
- top 0
- right 0
- padding 0
- width $height
- text-align center
- font-size 1.4em
- color inherit
- line-height $height
- border-left solid 1px rgba(#000, 0.1)
-
-</style>
diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue
deleted file mode 100644
index db250ec6f8..0000000000
--- a/src/client/app/mobile/views/components/ui.nav.vue
+++ /dev/null
@@ -1,346 +0,0 @@
-<template>
-<div class="fquwcbxs">
- <transition name="back">
- <div class="backdrop"
- v-if="isOpen"
- @click="$parent.isDrawerOpening = false"
- @touchstart="$parent.isDrawerOpening = false"
- ></div>
- </transition>
- <transition name="nav">
- <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 v-if="$store.state.device.enableMobileQuickNotificationView"><p @click="showNotifications = true"><i><fa :icon="faBell" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li>
- <li v-else><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="faBell" 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/follow-requests" :data-active="$route.name == '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/groups" :data-active="$route.name == 'user-groups'"><i><fa :icon="faUsers" fixed-width/></i>{{ $t('user-groups') }}<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>
- <li><router-link to="/i/pages" :data-active="$route.name == 'pages'"><i><fa :icon="faStickyNote" fixed-width/></i>{{ $t('@.pages') }}<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" fixed-width/></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><mfm :text="announcement.text"/></div>
- <img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
- </article>
- </div>
- <a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a>
- </div>
- <div class="notifications" v-if="showNotifications">
- <header>
- <button @click="showNotifications = false"><fa icon="times"/></button>
- <i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i>
- </header>
- <mk-notifications/>
- </div>
- </div>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import { lang } from '../../../config';
-import { faNewspaper, faHashtag, faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
-import { faMoon, faSun, faStickyNote, faBell } from '@fortawesome/free-regular-svg-icons';
-import { search } from '../../../common/scripts/search';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/ui.nav.vue'),
-
- props: ['isOpen'],
-
- provide: {
- narrow: true
- },
-
- data() {
- return {
- hasGameInvitation: false,
- connection: null,
- aboutUrl: `/docs/${lang}/about`,
- announcements: [],
- searching: false,
- showNotifications: false,
- faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns, faStickyNote, faUsers, faBell,
- };
- },
-
- 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;
- }
- },
-
- watch: {
- isOpen() {
- this.showNotifications = false;
- }
- },
-
- mounted() {
- this.$root.getMeta().then(meta => {
- this.announcements = meta.announcements;
- });
-
- 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: {
- search() {
- if (this.searching) return;
-
- this.$root.dialog({
- title: this.$t('search'),
- input: true
- }).then(async ({ canceled, result: query }) => {
- if (canceled) return;
-
- this.searching = true;
- search(this, query).finally(() => {
- this.searching = false;
- });
- });
- },
-
- onReversiInvited() {
- this.hasGameInvitation = true;
- },
-
- onReversiNoInvites() {
- this.hasGameInvitation = false;
- },
-
- dark() {
- this.$store.commit('device/set', {
- 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>
-.fquwcbxs
- $color = var(--text)
-
- .backdrop
- position fixed
- top 0
- left 0
- z-index 1025
- width 100%
- height 100%
- background var(--mobileNavBackdrop)
-
- .body
- position fixed
- top 0
- left 0
- z-index 1026
- width 240px
- height 100%
- overflow auto
- -webkit-overflow-scrolling touch
- background var(--secondary)
- font-size 15px
-
- &.notifications
- width 330px
-
- > .notifications
- padding-top 42px
-
- > header
- position fixed
- top 0
- left 0
- z-index 1000
- width 330px
- line-height 42px
- background var(--secondary)
-
- > button
- display block
- padding 0 14px
- font-size 20px
- line-height 42px
- color var(--text)
-
- > i
- position absolute
- top 0
- right 16px
- font-size 12px
- color var(--notificationIndicator)
-
- > .nav
-
- > .me
- display block
- margin 0
- padding 16px
-
- .avatar
- display inline
- max-width 64px
- border-radius 32px
- vertical-align middle
-
- .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
-
- ul
- display block
- margin 16px 0
- padding 0
- list-style none
-
- &:first-child
- margin-top 0
-
- &:last-child
- margin-bottom 0
-
- > li
- display block
- font-size 1em
- line-height 1em
-
- a, p
- display block
- margin 0
- padding 0 20px
- line-height 3rem
- line-height calc(1rem + 30px)
- color $color
- text-decoration none
-
- &[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
-
- > i.circle
- margin-left 6px
- font-size 10px
- color var(--notificationIndicator)
-
- > 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
-
- .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 {
- opacity: 1;
- transform: translateX(0);
- transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.nav-enter,
-.nav-leave-active {
- opacity: 0;
- transform: translateX(-240px);
-}
-
-.back-enter-active,
-.back-leave-active {
- opacity: 1;
- transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.back-enter,
-.back-leave-active {
- opacity: 0;
-}
-
-</style>
diff --git a/src/client/app/mobile/views/components/ui.vue b/src/client/app/mobile/views/components/ui.vue
deleted file mode 100644
index 05c886a497..0000000000
--- a/src/client/app/mobile/views/components/ui.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<template>
-<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>
- <x-nav :is-open="isDrawerOpening"/>
- <div class="content">
- <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>
-
-<script lang="ts">
-import Vue from 'vue';
-import MkNotify from './notify.vue';
-import XHeader from './ui.header.vue';
-import XNav from './ui.nav.vue';
-
-export default Vue.extend({
- components: {
- XHeader,
- XNav
- },
-
- props: ['title'],
-
- 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';
- }
- },
-
- mounted() {
- this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
-
- if (this.$store.getters.isSignedIn) {
- 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);
- }
- },
-
- beforeDestroy() {
- if (this.$store.getters.isSignedIn) {
- this.connection.dispose();
- }
- },
-
- methods: {
- onNotification(notification) {
- // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
- this.$root.stream.send('readNotification', {
- id: notification.id
- });
-
- this.$root.new(MkNotify, {
- notification
- });
- },
-
- onReversiInvited() {
- this.hasGameInvitation = true;
- },
-
- onReversiNoInvites() {
- this.hasGameInvitation = false;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-ui
- &: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(--notificationIndicator)
- font-size 16px
- animation blink 1s infinite
-
- &.post
- right 28px
- background var(--primary)
- color var(--primaryForeground)
-
-</style>
diff --git a/src/client/app/mobile/views/components/user-list-timeline.vue b/src/client/app/mobile/views/components/user-list-timeline.vue
deleted file mode 100644
index d9aa1dad8a..0000000000
--- a/src/client/app/mobile/views/components/user-list-timeline.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: ['list'],
-
- data() {
- return {
- connection: null,
- date: null,
- pagination: {
- endpoint: 'notes/user-list-timeline',
- limit: 10,
- params: init => ({
- listId: this.list.id,
- untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes
- })
- }
- };
- },
-
- watch: {
- $route: 'init'
- },
-
- mounted() {
- this.init();
-
- this.$root.$on('warp', this.warp);
- this.$once('hook:beforeDestroy', () => {
- this.$root.$off('warp', this.warp);
- });
- },
-
- beforeDestroy() {
- this.connection.dispose();
- },
-
- methods: {
- init() {
- if (this.connection) this.connection.dispose();
- this.connection = this.$root.stream.connectToChannel('userList', {
- listId: this.list.id
- });
- this.connection.on('note', this.onNote);
- this.connection.on('userAdded', this.onUserAdded);
- this.connection.on('userRemoved', this.onUserRemoved);
- },
-
- onNote(note) {
- // Prepend a note
- (this.$refs.timeline as any).prepend(note);
- },
-
- onUserAdded() {
- (this.$refs.timeline as any).reload();
- },
-
- onUserRemoved() {
- (this.$refs.timeline as any).reload();
- },
-
- warp(date) {
- this.date = date;
- (this.$refs.timeline as any).reload();
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue
deleted file mode 100644
index 3b6baa76be..0000000000
--- a/src/client/app/mobile/views/components/user-timeline.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<template>
-<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/components/user-timeline.vue'),
-
- props: ['user', 'withMedia'],
-
- data() {
- return {
- date: null,
- pagination: {
- endpoint: 'users/notes',
- limit: 10,
- params: init => ({
- userId: this.user.id,
- withFiles: this.withMedia,
- untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
- })
- }
- };
- },
-
- created() {
- this.$root.$on('warp', this.warp);
- this.$once('hook:beforeDestroy', () => {
- this.$root.$off('warp', this.warp);
- });
- },
-
- methods: {
- warp(date) {
- this.date = date;
- (this.$refs.timeline as any).reload();
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/directives/index.ts b/src/client/app/mobile/views/directives/index.ts
deleted file mode 100644
index 324e07596d..0000000000
--- a/src/client/app/mobile/views/directives/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import Vue from 'vue';
-
-import userPreview from './user-preview';
-
-Vue.directive('userPreview', userPreview);
-Vue.directive('user-preview', userPreview);
diff --git a/src/client/app/mobile/views/directives/user-preview.ts b/src/client/app/mobile/views/directives/user-preview.ts
deleted file mode 100644
index 1a54abc20d..0000000000
--- a/src/client/app/mobile/views/directives/user-preview.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// nope
-export default {};
diff --git a/src/client/app/mobile/views/pages/drive.vue b/src/client/app/mobile/views/pages/drive.vue
deleted file mode 100644
index 05163c6ed9..0000000000
--- a/src/client/app/mobile/views/pages/drive.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<mk-ui>
- <template #header>
- <template v-if="folder"><span style="margin-right:4px;"><fa :icon="['far', 'folder-open']"/></span>{{ folder.name }}</template>
- <template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
- <template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
- </template>
- <template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
- <x-drive
- ref="browser"
- :init-folder="initFolder"
- :init-file="initFile"
- :is-naked="true"
- :top="$store.state.uiHeaderHeight"
- @begin-fetch="Progress.start()"
- @fetched-mid="Progress.set(0.5)"
- @fetched="Progress.done()"
- @move-root="onMoveRoot"
- @open-folder="onOpenFolder"
- @open-file="onOpenFile"
- />
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import Progress from '../../../common/scripts/loading';
-import XMenu from '../../../common/views/components/menu.vue';
-import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
-import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/drive.vue'),
- components: {
- XDrive: () => import('../components/drive.vue').then(m => m.default),
- },
- data() {
- return {
- Progress,
- folder: null,
- file: null,
- initFolder: null,
- initFile: null
- };
- },
- created() {
- this.initFolder = this.$route.params.folder;
- this.initFile = this.$route.params.file;
-
- window.addEventListener('popstate', this.onPopState);
- },
- mounted() {
- document.title = `${this.$root.instanceName} Drive`;
- },
- beforeDestroy() {
- window.removeEventListener('popstate', this.onPopState);
- },
- methods: {
- onPopState() {
- if (this.$route.params.folder) {
- (this.$refs as any).browser.cd(this.$route.params.folder, true);
- } else if (this.$route.params.file) {
- (this.$refs as any).browser.cf(this.$route.params.file, true);
- } else {
- (this.$refs as any).browser.goRoot(true);
- }
- },
- onMoveRoot(silent) {
- const title = `${this.$root.instanceName} Drive`;
-
- if (!silent) {
- // Rewrite URL
- history.pushState(null, title, '/i/drive');
- }
-
- document.title = title;
-
- this.file = null;
- this.folder = null;
- },
- onOpenFolder(folder, silent) {
- const title = `${folder.name} | ${this.$root.instanceName} Drive`;
-
- if (!silent) {
- // Rewrite URL
- history.pushState(null, title, `/i/drive/folder/${folder.id}`);
- }
-
- document.title = title;
-
- this.file = null;
- this.folder = folder;
- },
- onOpenFile(file, silent) {
- const title = `${file.name} | ${this.$root.instanceName} Drive`;
-
- if (!silent) {
- // Rewrite URL
- history.pushState(null, title, `/i/drive/file/${file.id}`);
- }
-
- document.title = title;
-
- this.file = file;
- this.folder = null;
- },
- openContextMenu() {
- this.$root.new(XMenu, {
- items: [{
- type: 'item',
- text: this.$t('contextmenu.upload'),
- icon: 'upload',
- action: this.$refs.browser.selectLocalFile
- }, {
- type: 'item',
- text: this.$t('contextmenu.url-upload'),
- icon: faCloudUploadAlt,
- action: this.$refs.browser.urlUpload
- }, {
- type: 'item',
- text: this.$t('contextmenu.create-folder'),
- icon: ['far', 'folder'],
- action: this.$refs.browser.createFolder
- }, ...(this.folder ? [{
- type: 'item',
- text: this.$t('contextmenu.rename-folder'),
- icon: 'i-cursor',
- action: this.$refs.browser.renameFolder
- }, {
- type: 'item',
- text: this.$t('contextmenu.move-folder'),
- icon: ['far', 'folder-open'],
- action: this.$refs.browser.moveFolder
- }, {
- type: 'item',
- text: this.$t('contextmenu.delete-folder'),
- icon: faTrashAlt,
- action: this.$refs.browser.deleteFolder
- }] : [])],
- source: this.$refs.contextSource,
- });
- }
- }
-});
-</script>
-
diff --git a/src/client/app/mobile/views/pages/games/reversi.vue b/src/client/app/mobile/views/pages/games/reversi.vue
deleted file mode 100644
index 69b7bdffb4..0000000000
--- a/src/client/app/mobile/views/pages/games/reversi.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa icon="gamepad"/></span>{{ $t('reversi') }}</template>
- <x-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/games/reversi.vue'),
- components: {
- XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
- },
- mounted() {
- document.title = `${this.$root.instanceName} | ${this.$t('reversi')}`;
- },
- methods: {
- nav(game, actualNav) {
- if (actualNav) {
- this.$router.push(`/games/reversi/${game.id}`);
- } else {
- // TODO: https://github.com/vuejs/vue-router/issues/703
- this.$router.push(`/games/reversi/${game.id}`);
- }
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue
deleted file mode 100644
index f115458092..0000000000
--- a/src/client/app/mobile/views/pages/home.timeline.vue
+++ /dev/null
@@ -1,143 +0,0 @@
-<template>
-<div>
- <ui-container v-if="src == 'home' && alone" :show-header="false" style="margin-bottom:8px;">
- <div class="zrzngnxs">
- <p>{{ $t('@.empty-timeline-info.follow-users-to-make-your-timeline') }}</p>
- <router-link to="/explore">{{ $t('@.empty-timeline-info.explore') }}</router-link>
- </div>
- </ui-container>
-
- <mk-notes ref="timeline" :pagination="pagination" @loaded="() => $emit('loaded')"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/home.timeline.vue'),
-
- props: {
- src: {
- type: String,
- required: true
- },
- tagTl: {
- required: false
- }
- },
-
- data() {
- return {
- streamManager: null,
- connection: null,
- unreadCount: 0,
- date: null,
- baseQuery: {
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes
- },
- query: {},
- endpoint: null,
- pagination: null
- };
- },
-
- computed: {
- alone(): boolean {
- return this.$store.state.i.followingCount == 0;
- }
- },
-
- created() {
- this.$root.$on('warp', this.warp);
- this.$once('hook:beforeDestroy', () => {
- this.$root.$off('warp', this.warp);
- this.connection.dispose();
- });
-
- const prepend = note => {
- (this.$refs.timeline as any).prepend(note);
- };
-
- if (this.src == 'tag') {
- this.endpoint = 'notes/search-by-tag';
- this.query = {
- query: this.tagTl.query
- };
- this.connection = this.$root.stream.connectToChannel('hashtag', { q: this.tagTl.query });
- this.connection.on('note', prepend);
- } else if (this.src == 'home') {
- this.endpoint = 'notes/timeline';
- const onChangeFollowing = () => {
- this.fetch();
- };
- this.connection = this.$root.stream.useSharedConnection('homeTimeline');
- this.connection.on('note', prepend);
- this.connection.on('follow', onChangeFollowing);
- this.connection.on('unfollow', onChangeFollowing);
- } else if (this.src == 'local') {
- this.endpoint = 'notes/local-timeline';
- this.connection = this.$root.stream.useSharedConnection('localTimeline');
- this.connection.on('note', prepend);
- } else if (this.src == 'hybrid') {
- this.endpoint = 'notes/hybrid-timeline';
- this.connection = this.$root.stream.useSharedConnection('hybridTimeline');
- this.connection.on('note', prepend);
- } else if (this.src == 'global') {
- this.endpoint = 'notes/global-timeline';
- this.connection = this.$root.stream.useSharedConnection('globalTimeline');
- this.connection.on('note', prepend);
- } else if (this.src == 'mentions') {
- this.endpoint = 'notes/mentions';
- this.connection = this.$root.stream.useSharedConnection('main');
- this.connection.on('mention', prepend);
- } else if (this.src == 'messages') {
- this.endpoint = 'notes/mentions';
- this.query = {
- visibility: 'specified'
- };
- const onNote = note => {
- if (note.visibility == 'specified') {
- prepend(note);
- }
- };
- this.connection = this.$root.stream.useSharedConnection('main');
- this.connection.on('mention', onNote);
- }
-
- this.pagination = {
- endpoint: this.endpoint,
- limit: 10,
- params: init => ({
- untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
- ...this.baseQuery, ...this.query
- })
- };
- },
-
- methods: {
- focus() {
- (this.$refs.timeline as any).focus();
- },
-
- warp(date) {
- this.date = date;
- (this.$refs.timeline as any).reload();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.zrzngnxs
- padding 16px
- text-align center
- font-size 14px
-
- > p
- margin 0 0 8px 0
-
-</style>
diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue
deleted file mode 100644
index 0d110bf2ee..0000000000
--- a/src/client/app/mobile/views/pages/home.vue
+++ /dev/null
@@ -1,249 +0,0 @@
-<template>
-<mk-ui>
- <template #header>
- <span @click="showNav = true">
- <span :class="$style.title">
- <span v-if="src == 'home'"><fa icon="home"/>{{ $t('home') }}</span>
- <span v-if="src == 'local'"><fa :icon="['far', 'comments']"/>{{ $t('local') }}</span>
- <span v-if="src == 'hybrid'"><fa icon="share-alt"/>{{ $t('hybrid') }}</span>
- <span v-if="src == 'global'"><fa icon="globe"/>{{ $t('global') }}</span>
- <span v-if="src == 'mentions'"><fa icon="at"/>{{ $t('mentions') }}</span>
- <span v-if="src == 'messages'"><fa :icon="['far', 'envelope']"/>{{ $t('messages') }}</span>
- <span v-if="src == 'list'"><fa icon="list"/>{{ list.name }}</span>
- <span v-if="src == 'tag'"><fa icon="hashtag"/>{{ tagTl.title }}</span>
- </span>
- <span style="margin-left:8px">
- <template v-if="!showNav"><fa icon="angle-down"/></template>
- <template v-else><fa icon="angle-up"/></template>
- </span>
- <i :class="$style.badge" v-if="$store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i>
- </span>
- </template>
-
- <template #func>
- <button @click="fn"><fa icon="pencil-alt"/></button>
- </template>
-
- <main>
- <div class="nav" v-if="showNav">
- <div class="bg" @click="showNav = false"></div>
- <div class="pointer"></div>
- <div class="body">
- <div>
- <span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
- <span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
- <span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
- <span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
- <div class="hr"></div>
- <span :data-active="src == 'mentions'" @click="src = 'mentions'"><fa icon="at"/> {{ $t('mentions') }}<i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></span>
- <span :data-active="src == 'messages'" @click="src = 'messages'"><fa :icon="['far', 'envelope']"/> {{ $t('messages') }}<i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></span>
- <template v-if="lists">
- <div class="hr" v-if="lists.length > 0"></div>
- <span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id"><fa icon="list"/> {{ l.name }}</span>
- </template>
- <div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
- <span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id"><fa icon="hashtag"/> {{ tl.title }}</span>
- </div>
- </div>
- </div>
-
- <div class="tl">
- <x-tl v-if="src == 'home'" ref="tl" key="home" src="home"/>
- <x-tl v-if="src == 'local'" ref="tl" key="local" src="local"/>
- <x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
- <x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
- <x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
- <x-tl v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
- <x-tl v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
- <mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
- </div>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import Progress from '../../../common/scripts/loading';
-import XTl from './home.timeline.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/home.vue'),
-
- components: {
- XTl
- },
-
- data() {
- return {
- src: 'home',
- list: null,
- lists: null,
- tagTl: null,
- showNav: false,
- enableLocalTimeline: false,
- enableGlobalTimeline: false,
- };
- },
-
- watch: {
- src() {
- this.showNav = false;
- this.saveSrc();
- },
-
- list(x) {
- this.showNav = false;
- this.saveSrc();
- if (x != null) this.tagTl = null;
- },
-
- tagTl(x) {
- this.showNav = false;
- this.saveSrc();
- if (x != null) this.list = null;
- },
-
- showNav(v) {
- if (v && this.lists === null) {
- this.$root.api('users/lists/list').then(lists => {
- this.lists = lists;
- });
- }
- }
- },
-
- created() {
- this.$root.getMeta().then((meta: Record<string, any>) => {
- if (!(
- this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
- ) && this.src === 'global') this.src = 'local';
- if (!(
- this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
- ) && ['local', 'hybrid'].includes(this.src)) this.src = 'home';
- });
-
- if (this.$store.state.device.tl) {
- this.src = this.$store.state.device.tl.src;
- if (this.src == 'list') {
- this.list = this.$store.state.device.tl.arg;
- } else if (this.src == 'tag') {
- this.tagTl = this.$store.state.device.tl.arg;
- }
- }
- },
-
- mounted() {
- document.title = this.$root.instanceName;
-
- Progress.start();
-
- (this.$refs.tl as any).$once('loaded', () => {
- Progress.done();
- });
- },
-
- methods: {
- fn() {
- this.$post();
- },
-
- saveSrc() {
- this.$store.commit('device/setTl', {
- src: this.src,
- arg: this.src == 'list' ? this.list : this.tagTl
- });
- },
-
- warp() {
-
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-main
- > .nav
- > .pointer
- position fixed
- z-index 10002
- top 56px
- left 0
- right 0
-
- $size = 16px
-
- &:after
- content ""
- display block
- position absolute
- top -($size * 2)
- left s('calc(50% - %s)', $size)
- border-top solid $size transparent
- border-left solid $size transparent
- border-right solid $size transparent
- border-bottom solid $size var(--popupBg)
-
- > .bg
- position fixed
- z-index 10000
- top 0
- left 0
- width 100%
- height 100%
- background rgba(#000, 0.5)
-
- > .body
- position fixed
- z-index 10001
- top 56px
- left 0
- right 0
- width 300px
- max-height calc(100% - 70px)
- margin 0 auto
- overflow auto
- -webkit-overflow-scrolling touch
- background var(--popupBg)
- border-radius 8px
- box-shadow 0 0 16px rgba(#000, 0.1)
-
- > div
- padding 8px 0
-
- > .hr
- margin 8px 0
- border-top solid 1px var(--faceDivider)
-
- > *:not(.hr)
- display block
- padding 8px 16px
- color var(--text)
-
- &[data-active]
- color var(--primaryForeground)
- background var(--primary)
-
- &:not([data-active]):hover
- background var(--mobileHomeTlItemHover)
-
- > .badge
- margin-left 6px
- font-size 10px
- color var(--notificationIndicator)
-
-</style>
-
-<style lang="stylus" module>
-.title
- [data-icon]
- margin-right 4px
-
-.badge
- margin-left 6px
- font-size 10px
- color var(--notificationIndicator)
- vertical-align middle
-
-</style>
diff --git a/src/client/app/mobile/views/pages/index.vue b/src/client/app/mobile/views/pages/index.vue
deleted file mode 100644
index 5d11fc5423..0000000000
--- a/src/client/app/mobile/views/pages/index.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-<template>
-<component :is="$store.getters.isSignedIn ? 'home' : 'welcome'"></component>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import Home from './home.vue';
-import Welcome from './welcome.vue';
-
-export default Vue.extend({
- components: {
- Home,
- Welcome
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/messaging-room.vue b/src/client/app/mobile/views/pages/messaging-room.vue
deleted file mode 100644
index 7872847127..0000000000
--- a/src/client/app/mobile/views/pages/messaging-room.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<template>
-<mk-ui>
- <template #header>
- <template v-if="user"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span><mk-user-name :user="user"/></template>
- <template v-else-if="group"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ group.name }}</template>
- <template v-else><mk-ellipsis/></template>
- </template>
- <x-messaging-room v-if="!fetching" :user="user" :group="group" :is-naked="true"/>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import parseAcct from '../../../../../misc/acct/parse';
-
-export default Vue.extend({
- i18n: i18n(),
- components: {
- XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
- },
- data() {
- return {
- fetching: true,
- user: null,
- group: null,
- unwatchDarkmode: null
- };
- },
- watch: {
- $route: 'fetch'
- },
- created() {
- const applyBg = v =>
- document.documentElement.style.setProperty('background', v ? '#191b22' : '#fff', 'important');
-
- applyBg(this.$store.state.device.darkmode);
-
- this.unwatchDarkmode = this.$store.watch(s => {
- return s.device.darkmode;
- }, applyBg);
-
- this.fetch();
- },
- beforeDestroy() {
- document.documentElement.style.removeProperty('background');
- document.documentElement.style.removeProperty('background-color'); // for safari's bug
- this.unwatchDarkmode();
- },
- methods: {
- fetch() {
- this.fetching = true;
- if (this.$route.params.user) {
- this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
- this.user = user;
- this.fetching = false;
-
- document.title = `${this.$t('@.messaging')}: ${Vue.filter('userName')(this.user)} | ${this.$root.instanceName}`;
- });
- } else {
- this.$root.api('users/groups/show', { groupId: this.$route.params.group }).then(group => {
- this.group = group;
- this.fetching = false;
-
- document.title = this.$t('@.messaging') + ': ' + this.group.name;
- });
- }
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/messaging.vue b/src/client/app/mobile/views/pages/messaging.vue
deleted file mode 100644
index ff66ae06e6..0000000000
--- a/src/client/app/mobile/views/pages/messaging.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ $t('@.messaging') }}</template>
- <x-messaging @navigate="navigate" @navigateGroup="navigateGroup" :header-top="48"/>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import getAcct from '../../../../../misc/acct/render';
-
-export default Vue.extend({
- i18n: i18n(),
- components: {
- XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
- },
- mounted() {
- document.title = `${this.$root.instanceName} ${this.$t('@.messaging')}`;
- },
- methods: {
- navigate(user) {
- (this as any).$router.push(`/i/messaging/${getAcct(user)}`);
- },
- navigateGroup(group) {
- (this as any).$router.push(`/i/messaging/group/${group.id}`);
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/note.vue b/src/client/app/mobile/views/pages/note.vue
deleted file mode 100644
index 090851fc4e..0000000000
--- a/src/client/app/mobile/views/pages/note.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa :icon="['far', 'sticky-note']"/></span>{{ $t('title') }}</template>
- <main v-if="!fetching">
- <div>
- <mk-note-detail :note="note" :key="note.id"/>
- </div>
- <footer>
- <router-link v-if="note.prev" :to="note.prev"><fa icon="angle-left"/> {{ $t('prev') }}</router-link>
- <router-link v-if="note.next" :to="note.next">{{ $t('next') }} <fa icon="angle-right"/></router-link>
- </footer>
- </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/note.vue'),
- data() {
- return {
- fetching: true,
- note: null
- };
- },
- watch: {
- $route: 'fetch'
- },
- created() {
- this.fetch();
- },
- mounted() {
- document.title = this.$root.instanceName;
- },
- methods: {
- fetch() {
- Progress.start();
- this.fetching = true;
-
- this.$root.api('notes/show', {
- noteId: this.$route.params.note
- }).then(note => {
- this.note = note;
- this.fetching = false;
-
- Progress.done();
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-main
- text-align center
-
- > footer
- margin-top 16px
-
- > a
- display inline-block
- margin 0 16px
-
-</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 24f8f79ccc..0000000000
--- a/src/client/app/mobile/views/pages/notifications.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<template>
-<mk-ui>
- <template #header><fa :icon="faBell"/> {{ $t('notifications') }}</template>
- <template #func>
- <button @click="filter()"><fa icon="cog"/></button>
- </template>
-
- <main>
- <mk-notifications @before-init="beforeInit()" @inited="inited()" :type="type === 'all' ? null : type" :wide="true" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"/>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { faBell } from '@fortawesome/free-regular-svg-icons';
-import i18n from '../../../i18n';
-import Progress from '../../../common/scripts/loading';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/notifications.vue'),
- data() {
- return {
- type: 'all',
- faBell,
- };
- },
- mounted() {
- document.title = this.$root.instanceName;
- },
- methods: {
- beforeInit() {
- Progress.start();
- },
- inited() {
- Progress.done();
- },
- filter() {
- this.$root.dialog({
- title: this.$t('@.notification-type'),
- type: null,
- select: {
- items: ['all', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'].map(x => ({
- value: x, text: this.$t('@.notification-types.' + x)
- }))
- default: this.type,
- },
- showCancelButton: true
- }).then(({ canceled, result: type }) => {
- if (canceled) return;
- this.type = type;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-main > *
- overflow hidden
- background var(--face)
-
- &.round
- border-radius 8px
-
- &.shadow
- box-shadow 0 4px 16px rgba(#000, 0.1)
-
- @media (min-width 500px)
- box-shadow 0 8px 32px rgba(#000, 0.1)
-</style>
diff --git a/src/client/app/mobile/views/pages/search.vue b/src/client/app/mobile/views/pages/search.vue
deleted file mode 100644
index dca1ffd40a..0000000000
--- a/src/client/app/mobile/views/pages/search.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<template>
-<mk-ui>
- <template #header><fa icon="search"/> {{ q }}</template>
-
- <main>
- <mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import Progress from '../../../common/scripts/loading';
-import { genSearchQuery } from '../../../common/scripts/gen-search-query';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/search.vue'),
- data() {
- return {
- pagination: {
- endpoint: 'notes/search',
- limit: 20,
- params: () => genSearchQuery(this, this.q)
- }
- };
- },
- computed: {
- q(): string {
- return this.$route.query.q;
- }
- },
- watch: {
- $route() {
- this.$refs.timeline.reload();
- }
- },
- mounted() {
- document.title = `${this.$t('search')}: ${this.q} | ${this.$root.instanceName}`;
- },
- methods: {
- inited() {
- Progress.done();
- },
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/selectdrive.vue b/src/client/app/mobile/views/pages/selectdrive.vue
deleted file mode 100644
index 095c19cf2c..0000000000
--- a/src/client/app/mobile/views/pages/selectdrive.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<template>
-<div class="mk-selectdrive">
- <header>
- <h1>{{ $t('select-file') }}<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
- <button class="upload" @click="upload"><fa icon="upload"/></button>
- <button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
- </header>
- <x-drive ref="browser" select-file :multiple="multiple" is-naked :top="$store.state.uiHeaderHeight"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/selectdrive.vue'),
- components: {
- XDrive: () => import('../components/drive.vue').then(m => m.default),
- },
- data() {
- return {
- files: []
- };
- },
- computed: {
- multiple(): boolean {
- const q = (new URL(location.toString())).searchParams;
- return q.get('multiple') == 'true';
- }
- },
- mounted() {
- document.title = this.$t('title');
- },
- methods: {
- onSelected(file) {
- this.files = [file];
- this.ok();
- },
- onChangeSelection(files) {
- this.files = files;
- },
- upload() {
- (this.$refs.browser as any).selectLocalFile();
- },
- close() {
- window.close();
- },
- ok() {
- window.opener.cb(this.multiple ? this.files : this.files[0]);
- this.close();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-selectdrive
- width 100%
- height 100%
- background #fff
-
- > header
- position fixed
- top 0
- left 0
- width 100%
- z-index 1000
- background #fff
- box-shadow 0 1px rgba(#000, 0.1)
-
- > h1
- margin 0
- padding 0
- text-align center
- line-height 42px
- font-size 1em
- font-weight normal
-
- > .count
- margin-left 4px
- opacity 0.5
-
- > .upload
- position absolute
- top 0
- left 0
- line-height 42px
- width 42px
-
- > .ok
- position absolute
- top 0
- right 0
- line-height 42px
- width 42px
-
- > .mk-drive
- top 42px
-
-</style>
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
deleted file mode 100644
index c24a56be7b..0000000000
--- a/src/client/app/mobile/views/pages/settings.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa icon="cog"/></span>{{ $t('@.settings') }}</template>
- <main>
- <div class="signed-in-as" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
- <mfm :text="$t('signed-in-as').replace('{}', name)" :plain="true" :custom-emojis="$store.state.i.emojis"/>
- </div>
-
- <x-settings/>
-
- <div class="signout" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }" @click="signout">{{ $t('@.signout') }}</div>
-
- <footer>
- <small>ver {{ version }} ({{ codename }})</small>
- </footer>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import XSettings from '../../../common/views/components/settings/settings.vue';
-import { version, codename } from '../../../config';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/settings.vue'),
- components: {
- XSettings,
- },
- data() {
- return {
- version,
- codename,
- };
- },
- computed: {
- name(): string {
- return Vue.filter('userName')(this.$store.state.i);
- },
- },
- methods: {
- signout() {
- this.$root.signout();
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-main
-
- > .signed-in-as
- margin 16px
- padding 16px
- text-align center
- color var(--mobileSignedInAsFg)
- background var(--mobileSignedInAsBg)
- font-weight bold
-
- &.round
- border-radius 6px
-
- &.shadow
- box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
-
- > .signout
- margin 16px
- padding 16px
- text-align center
- color var(--mobileSignedInAsFg)
- background var(--mobileSignedInAsBg)
-
- &.round
- border-radius 6px
-
- &.shadow
- box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
-
- > footer
- margin 16px
- text-align center
- color var(--text)
- opacity 0.7
-
-</style>
diff --git a/src/client/app/mobile/views/pages/signup.vue b/src/client/app/mobile/views/pages/signup.vue
deleted file mode 100644
index 81d2741ae5..0000000000
--- a/src/client/app/mobile/views/pages/signup.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-<template>
-<div class="signup">
- <h1>{{ $t('lets-start') }}</h1>
- <mk-signup/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/signup.vue')
-});
-</script>
-
-<style lang="stylus" scoped>
-.signup
- padding 32px
- margin 0 auto
- max-width 500px
-
- h1
- margin 0
- padding 8px 0 0 0
- font-size 1.5em
- font-weight bold
- color var(--text)
-
-</style>
diff --git a/src/client/app/mobile/views/pages/tag.vue b/src/client/app/mobile/views/pages/tag.vue
deleted file mode 100644
index 19482ec382..0000000000
--- a/src/client/app/mobile/views/pages/tag.vue
+++ /dev/null
@@ -1,40 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</template>
-
- <main>
- <mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
- </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/tag.vue'),
- data() {
- return {
- pagination: {
- endpoint: 'notes/search-by-tag',
- limit: 20,
- params: {
- tag: this.$route.params.tag
- }
- }
- };
- },
- watch: {
- $route() {
- this.$refs.timeline.reload();
- }
- },
- methods: {
- inited() {
- Progress.done();
- },
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/ui.vue b/src/client/app/mobile/views/pages/ui.vue
deleted file mode 100644
index 397ba5df07..0000000000
--- a/src/client/app/mobile/views/pages/ui.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;" v-if="icon"><fa :icon="icon"/></span>{{ title }}</template>
-
- <main>
- <component :is="component" @init="init" v-bind="$attrs"/>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- component: {
- required: true
- }
- },
-
- data() {
- return {
- title: null,
- icon: null,
- };
- },
-
- mounted() {
- },
-
- methods: {
- init(v) {
- this.title = v.title;
- this.icon = v.icon;
- }
- }
-});
-</script>
diff --git a/src/client/app/mobile/views/pages/user/home.notes.vue b/src/client/app/mobile/views/pages/user/home.notes.vue
deleted file mode 100644
index 9abe5b893c..0000000000
--- a/src/client/app/mobile/views/pages/user/home.notes.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<template>
-<div class="root notes">
- <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
- <div v-if="!fetching && notes.length > 0">
- <mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
- </div>
- <p class="empty" v-if="!fetching && notes.length == 0">{{ $t('@.no-notes') }}</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../../i18n';
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/user/home.notes.vue'),
- props: ['user'],
- data() {
- return {
- fetching: true,
- notes: []
- };
- },
- mounted() {
- this.$root.api('users/notes', {
- userId: this.user.id,
- }).then(notes => {
- this.notes = notes;
- this.fetching = false;
- });
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.root.notes
-
- > div
- overflow-x scroll
- -webkit-overflow-scrolling touch
- white-space nowrap
- padding 8px
-
- > *
- vertical-align top
-
- &:not(:last-child)
- margin-right 8px
-
- > .fetching
- > .empty
- margin 0
- padding 16px
- text-align center
- color var(--text)
-
- > i
- margin-right 4px
-
-</style>
diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue
deleted file mode 100644
index b5547c916f..0000000000
--- a/src/client/app/mobile/views/pages/user/home.photos.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-<div class="root photos">
- <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
- <div class="stream" v-if="!fetching && images.length > 0">
- <a v-for="(image, i) in images" :key="i"
- class="img"
- :style="`background-image: url(${thumbnail(image.file)})`"
- :href="image.note | notePage"
- ></a>
- </div>
- <p class="empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../../i18n';
-import { getStaticImageUrl } from '../../../../common/scripts/get-static-image-url';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/user/home.photos.vue'),
- props: ['user'],
- data() {
- return {
- fetching: true,
- images: []
- };
- },
- mounted() {
- const image = [
- 'image/jpeg',
- 'image/png',
- 'image/gif',
- 'image/apng',
- 'image/vnd.mozilla.apng',
- ];
- this.$root.api('users/notes', {
- userId: this.user.id,
- fileType: image,
- excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
- limit: 9,
- }).then(notes => {
- for (const note of notes) {
- for (const file of note.files) {
- if (this.images.length < 9) {
- this.images.push({
- note,
- file
- });
- }
- }
- }
- this.fetching = false;
- });
- },
- methods: {
- thumbnail(image: any): string {
- return this.$store.state.device.disableShowingAnimatedImages
- ? getStaticImageUrl(image.thumbnailUrl)
- : image.thumbnailUrl;
- },
- },
-});
-</script>
-
-<style lang="stylus" scoped>
-.root.photos
-
- > .stream
- display -webkit-flex
- display -moz-flex
- display -ms-flex
- display flex
- justify-content center
- flex-wrap wrap
- padding 8px
-
- > .img
- flex 1 1 33%
- width 33%
- height 90px
- background-position center center
- background-size cover
- background-clip content-box
- border solid 2px transparent
- border-radius 4px
-
- > .initializing
- > .empty
- margin 0
- padding 16px
- text-align center
- color var(--text)
-
- > i
- margin-right 4px
-
-</style>
-
diff --git a/src/client/app/mobile/views/pages/user/home.vue b/src/client/app/mobile/views/pages/user/home.vue
deleted file mode 100644
index 316b2a12fe..0000000000
--- a/src/client/app/mobile/views/pages/user/home.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-<template>
-<div class="wojmldye">
- <x-page class="page" v-if="user.pinnedPage" :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/>
- <mk-note-detail class="note" v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
- <ui-container :body-togglable="true">
- <template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template>
- <div>
- <x-notes :user="user"/>
- </div>
- </ui-container>
- <ui-container :body-togglable="true">
- <template #header><fa icon="image"/>{{ $t('images') }}</template>
- <div>
- <x-photos :user="user"/>
- </div>
- </ui-container>
- <ui-container :body-togglable="true">
- <template #header><fa icon="chart-bar"/>{{ $t('activity') }}</template>
- <div style="padding:8px;">
- <x-activity :user="user"/>
- </div>
- </ui-container>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../../i18n';
-import XNotes from './home.notes.vue';
-import XPhotos from './home.photos.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/user/home.vue'),
- components: {
- XNotes,
- XPhotos,
- XPage: () => import('../../../../common/views/components/page/page.vue').then(m => m.default),
- XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
- },
- props: ['user'],
- data() {
- return {
- makeFrequentlyRepliedUsersPromise: () => this.$root.api('users/get_frequently_replied_users', {
- userId: this.user.id
- }).then(res => res.map(x => x.user)),
- makeFollowersYouKnowPromise: () => this.$root.api('users/followers', {
- userId: this.user.id,
- iknow: true,
- limit: 30
- }).then(res => res.users),
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.wojmldye
- > .page
- margin 0 0 8px 0
-
- @media (min-width 500px)
- margin 0 0 16px 0
-
- > .note
- margin 0 0 8px 0
-
- @media (min-width 500px)
- margin 0 0 16px 0
-
-</style>
diff --git a/src/client/app/mobile/views/pages/user/index.vue b/src/client/app/mobile/views/pages/user/index.vue
deleted file mode 100644
index b8a79a6b34..0000000000
--- a/src/client/app/mobile/views/pages/user/index.vue
+++ /dev/null
@@ -1,349 +0,0 @@
-<template>
-<mk-ui>
- <template #header v-if="!fetching">
- <img :src="avator" alt=""><mk-user-name :user="user" :key="user.id"/>
- </template>
- <div class="wwtwuxyh" v-if="!fetching">
- <div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
- <div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
- <header>
- <div class="banner" :style="style"></div>
- <div class="body">
- <div class="top">
- <a class="avatar">
- <img :src="avator" alt="avatar"/>
- </a>
- <button class="menu" ref="menu" @click="menu"><fa icon="ellipsis-h"/></button>
- <mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
- </div>
- <div class="title">
- <h1><mk-user-name :user="user" :key="user.id" :nowrap="false"/></h1>
- <span class="username"><mk-acct :user="user" :detail="true" :key="user.id"/></span>
- <span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
- </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" :key="user.id"/>
- <x-integrations :user="user" style="margin:20px 0;"/>
- </div>
- <div class="fields" v-if="user.fields" :key="user.id">
- <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"/>
- </dt>
- <dd class="value">
- <mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
- </dd>
- </dl>
- </div>
- <div class="info">
- <p class="location" v-if="user.host === null && user.location">
- <fa icon="map-marker"/>{{ user.location }}
- </p>
- <p class="birthday" v-if="user.host === null && user.birthday">
- <fa icon="birthday-cake"/>{{ user.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ $t('years-old', { age }) }})
- </p>
- </div>
- <div class="status">
- <router-link :to="user | userPage()">
- <b>{{ user.notesCount | number }}</b>
- <i>{{ $t('notes') }}</i>
- </router-link>
- <router-link :to="user | userPage('following')">
- <b>{{ user.followingCount | number }}</b>
- <i>{{ $t('following') }}</i>
- </router-link>
- <router-link :to="user | userPage('followers')">
- <b>{{ user.followersCount | number }}</b>
- <i>{{ $t('followers') }}</i>
- </router-link>
- </div>
- </div>
- </header>
- <nav v-if="$route.name == 'user'" :class="{ shadow: $store.state.device.useShadow }">
- <div class="nav-container">
- <a :data-active="page == 'home'" @click="page = 'home'"><fa icon="home"/> {{ $t('overview') }}</a>
- <a :data-active="page == 'notes'" @click="page = 'notes'"><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</a>
- <a :data-active="page == 'media'" @click="page = 'media'"><fa icon="image"/> {{ $t('media') }}</a>
- </div>
- </nav>
- <main>
- <template v-if="$route.name == 'user'">
- <x-home v-if="page == 'home'" :user="user" :key="user.id"/>
- <mk-user-timeline v-if="page == 'notes'" :user="user" :key="`tl:${user.id}`"/>
- <mk-user-timeline v-if="page == 'media'" :user="user" :with-media="true" :key="`media:${user.id}`"/>
- </template>
- <router-view :user="user"></router-view>
- </main>
- </div>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../../i18n';
-import * as age from 's-age';
-import parseAcct from '../../../../../../misc/acct/parse';
-import Progress from '../../../../common/scripts/loading';
-import XUserMenu from '../../../../common/views/components/user-menu.vue';
-import XHome from './home.vue';
-import { getStaticImageUrl } from '../../../../common/scripts/get-static-image-url';
-import XIntegrations from '../../../../common/views/components/integrations.vue';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/user.vue'),
- components: {
- XHome,
- XIntegrations
- },
- data() {
- return {
- fetching: true,
- user: null,
- page: this.$route.name == 'user' ? 'home' : null
- };
- },
- computed: {
- age(): number {
- return age(this.user.birthday);
- },
- avator(): string {
- return this.$store.state.device.disableShowingAnimatedImages
- ? getStaticImageUrl(this.user.avatarUrl)
- : this.user.avatarUrl;
- },
- style(): any {
- if (this.user.bannerUrl == null) return {};
- return {
- backgroundColor: this.user.bannerColor,
- backgroundImage: `url(${ this.user.bannerUrl })`
- };
- }
- },
- watch: {
- $route: 'fetch'
- },
- created() {
- this.fetch();
- },
- methods: {
- fetch() {
- Progress.start();
-
- this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
- this.user = user;
- this.fetching = false;
-
- Progress.done();
- document.title = `${Vue.filter('userName')(this.user)} | ${this.$root.instanceName}`;
- });
- },
-
- menu() {
- this.$root.new(XUserMenu, {
- source: this.$refs.menu,
- user: this.user
- });
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.wwtwuxyh
- $bg = var(--face)
-
- > .is-suspended
- > .is-remote
- &.is-suspended
- color #570808
- background #ffdbdb
-
- &.is-remote
- color #573c08
- background #fff0db
-
- > p
- margin 0 auto
- padding 14px
- max-width 600px
- font-size 14px
-
- > a
- font-weight bold
-
- @media (max-width 500px)
- padding 12px
- font-size 12px
-
- > header
- background $bg
-
- > .banner
- padding-bottom 33.3%
- background-color rgba(0, 0, 0, 0.1)
- background-size cover
- background-position center
-
- > .body
- padding 12px
- margin 0 auto
- max-width 600px
-
- > .top
- display flex
-
- > .avatar
- display block
- width 25%
- height 40px
-
- > img
- display block
- position absolute
- left -2px
- bottom -2px
- width 100%
- background $bg
- border 3px solid $bg
- border-radius 6px
-
- @media (min-width 500px)
- left -4px
- bottom -4px
- border 4px solid $bg
- border-radius 12px
-
- > .menu
- margin 0 0 0 auto
- padding 8px
- margin-right 8px
- font-size 18px
- color var(--text)
-
- > .title
- margin 8px 0
-
- > h1
- margin 0
- line-height 22px
- font-size 20px
- color var(--mobileUserPageName)
-
- > .username
- display inline-block
- line-height 20px
- font-size 16px
- font-weight bold
- color var(--mobileUserPageAcct)
-
- > .followed
- margin-left 8px
- padding 2px 4px
- font-size 12px
- color var(--mobileUserPageFollowedFg)
- background var(--mobileUserPageFollowedBg)
- border-radius 4px
-
- > .description
- margin 8px 0
- color var(--mobileUserPageDescription)
-
- @media (max-width 450px)
- font-size 15px
-
- > .fields
- margin 8px 0
-
- > .field
- display flex
- padding 0
- margin 0
- align-items center
-
- > .name
- padding 4px
- margin 4px
- width 30%
- overflow hidden
- white-space nowrap
- text-overflow ellipsis
- font-weight bold
- color var(--mobileUserPageStatusHighlight)
-
- > .value
- padding 4px
- margin 4px
- width 70%
- overflow hidden
- white-space nowrap
- text-overflow ellipsis
- color var(--mobileUserPageStatusHighlight)
-
- > .info
- margin 8px 0
-
- @media (max-width 450px)
- font-size 15px
-
- > p
- display inline
- margin 0 16px 0 0
- color var(--text)
-
- > i
- margin-right 4px
-
- > .status
- > a
- color var(--text)
-
- &:not(:last-child)
- margin-right 16px
-
- > b
- margin-right 4px
- font-size 16px
- color var(--mobileUserPageStatusHighlight)
-
- > i
- font-size 14px
-
- > button
- color var(--text)
-
- > nav
- position -webkit-sticky
- position sticky
- top 47px
- background-color $bg
- z-index 2
-
- &.shadow
- box-shadow 0 4px 4px var(--mobileUserPageHeaderShadow)
-
- > .nav-container
- display flex
- justify-content center
- margin 0 auto
- max-width 616px
-
- > a
- display block
- flex 1 1
- text-align center
- line-height 48px
- font-size 12px
- text-decoration none
- color var(--text)
- border-bottom solid 2px transparent
-
- @media (min-width 400px)
- line-height 52px
- font-size 14px
-
- &[data-active]
- font-weight bold
- color var(--primary)
- border-color var(--primary)
-
-</style>
diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue
deleted file mode 100644
index 6cf4a36f90..0000000000
--- a/src/client/app/mobile/views/pages/welcome.vue
+++ /dev/null
@@ -1,310 +0,0 @@
-<template>
-<div class="wgwfgvvimdjvhjfwxropcwksnzftjqes">
- <div class="banner" :style="{ backgroundImage: banner ? `url(${banner})` : null }"></div>
-
- <div>
- <img svg-inline src="../../../../assets/title.svg" alt="Misskey">
- <p class="host">{{ host }}</p>
- <div class="about">
- <h2>{{ name || 'Misskey' }}</h2>
- <p v-html="description || this.$t('@.about')"></p>
- <router-link class="signup" to="/signup">{{ $t('@.signup') }}</router-link>
- </div>
- <div class="signin">
- <a href="/signin" @click.prevent="signin()">{{ $t('@.signin') }}</a>
- </div>
- <div class="tl">
- <mk-welcome-timeline/>
- </div>
- <div class="hashtags">
- <mk-tag-cloud/>
- </div>
- <div class="photos">
- <div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
- </div>
- <div class="stats" v-if="stats">
- <span><fa icon="user"/> {{ stats.originalUsersCount | number }}</span>
- <span><fa icon="pencil-alt"/> {{ stats.originalNotesCount | number }}</span>
- </div>
- <div class="announcements" v-if="announcements && announcements.length > 0">
- <article v-for="announcement in announcements">
- <span class="title" v-html="announcement.title"></span>
- <mfm :text="announcement.text"/>
- <img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
- </article>
- </div>
- <article class="about-misskey">
- <h1>{{ $t('@.intro.title') }}</h1>
- <p v-html="this.$t('@.intro.about')"></p>
- <section>
- <h2>{{ $t('@.intro.features') }}</h2>
- <section>
- <h3>{{ $t('@.intro.rich-contents') }}</h3>
- <div class="image"><img src="/assets/about/post.png" alt=""></div>
- <p v-html="this.$t('@.intro.rich-contents-desc')"></p>
- </section>
- <section>
- <h3>{{ $t('@.intro.reaction') }}</h3>
- <div class="image"><img src="/assets/about/reaction.png" alt=""></div>
- <p v-html="this.$t('@.intro.reaction-desc')"></p>
- </section>
- <section>
- <h3>{{ $t('@.intro.ui') }}</h3>
- <div class="image"><img src="/assets/about/ui.png" alt=""></div>
- <p v-html="this.$t('@.intro.ui-desc')"></p>
- </section>
- <section>
- <h3>{{ $t('@.intro.drive') }}</h3>
- <div class="image"><img src="/assets/about/drive.png" alt=""></div>
- <p v-html="this.$t('@.intro.drive-desc')"></p>
- </section>
- </section>
- <p v-html="this.$t('@.intro.outro')"></p>
- </article>
- <div class="info" v-if="meta">
- <p>Version: <b>{{ meta.version }}</b></p>
- <p>Maintainer: <b><a :href="'mailto:' + meta.maintainerEmail" target="_blank">{{ meta.maintainerName }}</a></b></p>
- </div>
- <footer>
- <small>{{ copyright }}</small>
- </footer>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import { copyright, host } from '../../../config';
-import { concat } from '../../../../../prelude/array';
-import { toUnicode } from 'punycode';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/welcome.vue'),
- data() {
- return {
- meta: null,
- copyright,
- stats: null,
- banner: null,
- host: toUnicode(host),
- name: null,
- description: '',
- photos: [],
- announcements: []
- };
- },
- created() {
- this.$root.getMeta().then(meta => {
- this.meta = meta;
- this.name = meta.name;
- this.description = meta.description;
- this.announcements = meta.announcements;
- this.banner = meta.bannerUrl;
- });
-
- this.$root.api('stats').then(stats => {
- this.stats = stats;
- });
-
- const image = [
- 'image/jpeg',
- 'image/png',
- 'image/gif',
- 'image/apng',
- 'image/vnd.mozilla.apng',
- ];
-
- this.$root.api('notes/local-timeline', {
- fileType: image,
- excludeNsfw: true,
- limit: 6
- }).then((notes: any[]) => {
- const files = concat(notes.map((n: any): any[] => n.files));
- this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
- });
- },
- methods: {
- signin() {
- this.$root.dialog({
- type: 'signin'
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.wgwfgvvimdjvhjfwxropcwksnzftjqes
- text-align center
-
- > .banner
- position absolute
- top 0
- left 0
- width 100%
- height 300px
- background-position center
- background-size cover
- opacity 0.7
-
- &:after
- content ""
- display block
- position absolute
- bottom 0
- left 0
- width 100%
- height 100px
- background linear-gradient(transparent, var(--bg))
-
- > div:not(.banner)
- padding 32px
- margin 0 auto
- max-width 500px
-
- > svg
- display block
- width 200px
- height 50px
- margin 0 auto
-
- > .host
- display block
- text-align center
- padding 6px 12px
- line-height 32px
- font-weight bold
- color #333
- background rgba(#000, 0.035)
- border-radius 6px
-
- > .about
- margin-top 16px
- padding 16px
- color var(--text)
- background var(--face)
- border-radius 6px
-
- > h2
- margin 0
-
- > p
- margin 8px
-
- > .signup
- font-weight bold
-
- > .signin
- margin 16px 0
-
- > .tl
- margin 16px 0
-
- > *
- max-height 300px
- border-radius 6px
- overflow auto
- -webkit-overflow-scrolling touch
-
- > .hashtags
- padding 0 8px
- height 200px
-
- > .photos
- display grid
- grid-template-rows 1fr 1fr 1fr
- grid-template-columns 1fr 1fr
- gap 8px
- height 300px
- margin-top 16px
-
- > div
- border-radius 4px
- background-position center center
- background-size cover
-
- > .stats
- margin 16px 0
- padding 8px
- font-size 14px
- color var(--text)
- background rgba(#000, 0.1)
- border-radius 6px
-
- > *
- margin 0 8px
-
- > .announcements
- margin 16px 0
-
- > article
- background var(--mobileAnnouncement)
- border-radius 6px
- color var(--mobileAnnouncementFg)
- padding 16px
- margin 8px 0
- font-size 12px
-
- > .title
- font-weight bold
-
- > .about-misskey
- margin 16px 0
- padding 32px
- font-size 14px
- background var(--face)
- border-radius 6px
- overflow hidden
- color var(--text)
-
- > h1
- margin 0
-
- & + p
- margin-top 8px
-
- > p:last-child
- margin-bottom 0
-
- > section
- > h2
- border-bottom 1px solid var(--faceDivider)
-
- > section
- margin-bottom 16px
- padding-bottom 16px
- border-bottom 1px solid var(--faceDivider)
-
- > h3
- margin-bottom 8px
-
- > p
- margin-bottom 0
-
- > .image
- > img
- display block
- width 100%
- height 120px
- object-fit cover
-
- > .info
- padding 16px 0
- border solid 2px rgba(0, 0, 0, 0.1)
- border-radius 8px
- color var(--text)
-
- > *
- margin 0 16px
-
- > footer
- text-align center
- color var(--text)
-
- > small
- display block
- margin 16px 0 0 0
- opacity 0.7
-
-</style>
diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue
deleted file mode 100644
index 19df613b3a..0000000000
--- a/src/client/app/mobile/views/pages/widgets.vue
+++ /dev/null
@@ -1,192 +0,0 @@
-<template>
-<mk-ui>
- <template #header><span style="margin-right:4px;"><fa icon="home"/></span>{{ $t('dashboard') }}</template>
- <template #func>
- <button @click="customizing = !customizing"><fa icon="cog"/></button>
- </template>
- <main>
- <template v-if="customizing">
- <header>
- <select v-model="widgetAdderSelected">
- <option value="profile">{{ $t('@.widgets.profile') }}</option>
- <option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
- <option value="calendar">{{ $t('@.widgets.calendar') }}</option>
- <option value="activity">{{ $t('@.widgets.activity') }}</option>
- <option value="rss">{{ $t('@.widgets.rss') }}</option>
- <option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
- <option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
- <option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
- <option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
- <option value="version">{{ $t('@.widgets.version') }}</option>
- <option value="server">{{ $t('@.widgets.server') }}</option>
- <option value="queue">{{ $t('@.widgets.queue') }}</option>
- <option value="memo">{{ $t('@.widgets.memo') }}</option>
- <option value="nav">{{ $t('@.widgets.nav') }}</option>
- <option value="tips">{{ $t('@.widgets.tips') }}</option>
- </select>
- <button @click="addWidget">{{ $t('add-widget') }}</button>
- <p><a @click="hint">{{ $t('customization-tips') }}</a></p>
- </header>
- <x-draggable
- :list="widgets"
- handle=".handle"
- animation="150"
- @sort="onWidgetSort"
- >
- <div v-for="widget in widgets" class="customize-container" :key="widget.id">
- <header>
- <span class="handle"><fa icon="bars"/></span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)"><fa icon="times"/></button>
- </header>
- <div @click="widgetFunc(widget.id)">
- <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="mobile"/>
- </div>
- </div>
- </x-draggable>
- </template>
- <template v-else>
- <component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="mobile"/>
- </template>
- </main>
-</mk-ui>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import i18n from '../../../i18n';
-import * as XDraggable from 'vuedraggable';
-import { v4 as uuid } from 'uuid';
-
-export default Vue.extend({
- i18n: i18n('mobile/views/pages/widgets.vue'),
- components: {
- XDraggable
- },
-
- data() {
- return {
- showNav: false,
- customizing: false,
- widgetAdderSelected: null
- };
- },
-
- computed: {
- widgets(): any[] {
- return this.$store.getters.mobileHome || [];
- }
- },
-
- created() {
- if (this.widgets.length == 0) {
- this.$store.commit('setMobileHome', [{
- name: 'calendar',
- id: 'a', data: {}
- }, {
- name: 'activity',
- id: 'b', data: {}
- }, {
- name: 'rss',
- id: 'c', data: {}
- }, {
- name: 'photo-stream',
- id: 'd', data: {}
- }, {
- name: 'nav',
- id: 'f', data: {}
- }, {
- name: 'version',
- id: 'g', data: {}
- }]);
- }
- },
-
- mounted() {
- document.title = this.$root.instanceName;
- },
-
- methods: {
- hint() {
- this.$root.dialog({
- type: 'info',
- text: this.$t('widgets-hints')
- });
- },
-
- widgetFunc(id) {
- const w = this.$refs[id][0];
- if (w.func) w.func();
- },
-
- onWidgetSort() {
- this.saveHome();
- },
-
- addWidget() {
- if(this.widgetAdderSelected == null) return;
-
- this.$store.commit('addMobileHomeWidget', {
- name: this.widgetAdderSelected,
- id: uuid(),
- data: {}
- });
- },
-
- removeWidget(widget) {
- this.$store.commit('removeMobileHomeWidget', widget);
- },
-
- saveHome() {
- this.$store.commit('setMobileHome', this.widgets);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-main
- margin 0 auto
- padding 8px
- max-width 500px
- width 100%
-
- @media (min-width 500px)
- padding 16px 8px
-
- @media (min-width 600px)
- padding 32px 8px
-
- > header
- padding 8px
- background #fff
-
- .widget
- margin-bottom 8px
-
- @media (min-width 600px)
- margin-bottom 16px
-
- .customize-container
- margin 8px
- background #fff
-
- > header
- line-height 32px
- background #eee
-
- > .handle
- padding 0 8px
-
- > .remove
- position absolute
- top 0
- right 0
- padding 0 8px
- line-height 32px
-
- > div
- padding 8px
-
- > *
- pointer-events none
-
-</style>
diff --git a/src/client/app/mobile/views/widgets/activity.vue b/src/client/app/mobile/views/widgets/activity.vue
deleted file mode 100644
index 047784deac..0000000000
--- a/src/client/app/mobile/views/widgets/activity.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-<div class="mkw-activity">
- <ui-container :show-header="!props.compact">
- <template #header><fa icon="chart-bar"/>{{ $t('activity') }}</template>
- <div :class="$style.body">
- <x-activity :user="$store.state.i"/>
- </div>
- </ui-container>
-</div>
-</template>
-
-<script lang="ts">
-import define from '../../../common/define-widget';
-import i18n from '../../../i18n';
-
-export default define({
- name: 'activity',
- props: () => ({
- compact: false
- })
-}).extend({
- i18n: i18n(),
- components: {
- XActivity: () => import('../../../common/views/components/activity.vue').then(m => m.default)
- },
- methods: {
- func() {
- this.props.compact = !this.props.compact;
- this.save();
- }
- }
-});
-</script>
-
-<style lang="stylus" module>
-.body
- padding 8px
-</style>
diff --git a/src/client/app/mobile/views/widgets/index.ts b/src/client/app/mobile/views/widgets/index.ts
deleted file mode 100644
index 4de912b64c..0000000000
--- a/src/client/app/mobile/views/widgets/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import Vue from 'vue';
-
-import wActivity from './activity.vue';
-import wProfile from './profile.vue';
-
-Vue.component('mkw-activity', wActivity);
-Vue.component('mkw-profile', wProfile);
diff --git a/src/client/app/mobile/views/widgets/profile.vue b/src/client/app/mobile/views/widgets/profile.vue
deleted file mode 100644
index d4ccc87e57..0000000000
--- a/src/client/app/mobile/views/widgets/profile.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div class="mkw-profile">
- <ui-container>
- <div :class="$style.banner"
- :style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''"
- ></div>
- <img :class="$style.avatar"
- :src="$store.state.i.avatarUrl"
- alt="avatar"
- />
- <router-link :class="$style.name" :to="$store.state.i | userPage">
- <mk-user-name :user="$store.state.i"/>
- </router-link>
- </ui-container>
-</div>
-</template>
-
-<script lang="ts">
-import define from '../../../common/define-widget';
-
-export default define({
- name: 'profile'
-});
-</script>
-
-<style lang="stylus" module>
-.banner
- height 100px
- background-color #f5f5f5
- background-size cover
- background-position center
- cursor pointer
-
-.banner:before
- content ""
- display block
- width 100%
- height 100%
- background rgba(#000, 0.5)
-
-.avatar
- display block
- position absolute
- width 58px
- height 58px
- margin 0
- vertical-align bottom
- top ((100px - 58px) / 2)
- left ((100px - 58px) / 2)
- border none
- border-radius 100%
- box-shadow 0 0 16px rgba(#000, 0.5)
-
-.name
- display block
- position absolute
- top 0
- left 92px
- margin 0
- line-height 100px
- color #fff
- font-weight bold
- text-shadow 0 0 8px rgba(#000, 0.5)
-
-</style>