summaryrefslogtreecommitdiff
path: root/src/server/web/app/mobile/views/components
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-03-29 20:32:18 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-03-29 20:32:18 +0900
commitcf33e483f7e6f40e8cbbbc0118a7df70bdaf651f (patch)
tree318279530d3392ee40d91968477fc0e78d5cf0f7 /src/server/web/app/mobile/views/components
parentUpdate .travis.yml (diff)
downloadsharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.gz
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.bz2
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.zip
整理した
Diffstat (limited to 'src/server/web/app/mobile/views/components')
-rw-r--r--src/server/web/app/mobile/views/components/activity.vue62
-rw-r--r--src/server/web/app/mobile/views/components/drive-file-chooser.vue98
-rw-r--r--src/server/web/app/mobile/views/components/drive-folder-chooser.vue78
-rw-r--r--src/server/web/app/mobile/views/components/drive.file-detail.vue295
-rw-r--r--src/server/web/app/mobile/views/components/drive.file.vue171
-rw-r--r--src/server/web/app/mobile/views/components/drive.folder.vue58
-rw-r--r--src/server/web/app/mobile/views/components/drive.vue581
-rw-r--r--src/server/web/app/mobile/views/components/follow-button.vue123
-rw-r--r--src/server/web/app/mobile/views/components/friends-maker.vue127
-rw-r--r--src/server/web/app/mobile/views/components/index.ts47
-rw-r--r--src/server/web/app/mobile/views/components/media-image.vue31
-rw-r--r--src/server/web/app/mobile/views/components/media-video.vue36
-rw-r--r--src/server/web/app/mobile/views/components/notification-preview.vue128
-rw-r--r--src/server/web/app/mobile/views/components/notification.vue164
-rw-r--r--src/server/web/app/mobile/views/components/notifications.vue168
-rw-r--r--src/server/web/app/mobile/views/components/notify.vue49
-rw-r--r--src/server/web/app/mobile/views/components/post-card.vue89
-rw-r--r--src/server/web/app/mobile/views/components/post-detail.sub.vue109
-rw-r--r--src/server/web/app/mobile/views/components/post-detail.vue447
-rw-r--r--src/server/web/app/mobile/views/components/post-form.vue275
-rw-r--r--src/server/web/app/mobile/views/components/post-preview.vue106
-rw-r--r--src/server/web/app/mobile/views/components/post.sub.vue115
-rw-r--r--src/server/web/app/mobile/views/components/post.vue523
-rw-r--r--src/server/web/app/mobile/views/components/posts.vue111
-rw-r--r--src/server/web/app/mobile/views/components/sub-post-content.vue43
-rw-r--r--src/server/web/app/mobile/views/components/timeline.vue109
-rw-r--r--src/server/web/app/mobile/views/components/ui.header.vue242
-rw-r--r--src/server/web/app/mobile/views/components/ui.nav.vue244
-rw-r--r--src/server/web/app/mobile/views/components/ui.vue75
-rw-r--r--src/server/web/app/mobile/views/components/user-card.vue69
-rw-r--r--src/server/web/app/mobile/views/components/user-preview.vue110
-rw-r--r--src/server/web/app/mobile/views/components/user-timeline.vue76
-rw-r--r--src/server/web/app/mobile/views/components/users-list.vue133
-rw-r--r--src/server/web/app/mobile/views/components/widget-container.vue68
34 files changed, 0 insertions, 5160 deletions
diff --git a/src/server/web/app/mobile/views/components/activity.vue b/src/server/web/app/mobile/views/components/activity.vue
deleted file mode 100644
index 2e44017e77..0000000000
--- a/src/server/web/app/mobile/views/components/activity.vue
+++ /dev/null
@@ -1,62 +0,0 @@
-<template>
-<div class="mk-activity">
- <svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none">
- <g v-for="(d, i) in data">
- <rect width="0.8" :height="d.postsH"
- :x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH"
- fill="#41ddde"/>
- <rect width="0.8" :height="d.repliesH"
- :x="i + 0.1" :y="1 - d.repliesH - d.repostsH"
- fill="#f7796c"/>
- <rect width="0.8" :height="d.repostsH"
- :x="i + 0.1" :y="1 - d.repostsH"
- fill="#a1de41"/>
- </g>
- </svg>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['user'],
- data() {
- return {
- fetching: true,
- data: [],
- peak: null
- };
- },
- mounted() {
- (this as any).api('aggregation/users/activity', {
- userId: this.user.id,
- limit: 30
- }).then(data => {
- data.forEach(d => d.total = d.posts + d.replies + d.reposts);
- this.peak = Math.max.apply(null, data.map(d => d.total));
- data.forEach(d => {
- d.postsH = d.posts / this.peak;
- d.repliesH = d.replies / this.peak;
- d.repostsH = d.reposts / this.peak;
- });
- data.reverse();
- this.data = data;
- });
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-activity
- max-width 600px
- margin 0 auto
-
- > svg
- display block
- width 100%
- height 80px
-
- > rect
- transform-origin center
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/drive-file-chooser.vue b/src/server/web/app/mobile/views/components/drive-file-chooser.vue
deleted file mode 100644
index 6806af0f1e..0000000000
--- a/src/server/web/app/mobile/views/components/drive-file-chooser.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<template>
-<div class="mk-drive-file-chooser">
- <div class="body">
- <header>
- <h1>%i18n:mobile.tags.mk-drive-selector.select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
- <button class="close" @click="cancel">%fa:times%</button>
- <button v-if="multiple" class="ok" @click="ok">%fa:check%</button>
- </header>
- <mk-drive ref="browser"
- :select-file="true"
- :multiple="multiple"
- @change-selection="onChangeSelection"
- @selected="onSelected"
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['multiple'],
- data() {
- return {
- files: []
- };
- },
- methods: {
- onChangeSelection(files) {
- this.files = files;
- },
- onSelected(file) {
- this.$emit('selected', file);
- this.$destroy();
- },
- cancel() {
- this.$emit('canceled');
- this.$destroy();
- },
- ok() {
- this.$emit('selected', this.files);
- this.$destroy();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-drive-file-chooser
- position fixed
- z-index 2048
- top 0
- left 0
- width 100%
- height 100%
- padding 8px
- background rgba(0, 0, 0, 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
-
- > .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
-
- > .mk-drive
- height calc(100% - 42px)
- overflow scroll
- -webkit-overflow-scrolling touch
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/drive-folder-chooser.vue b/src/server/web/app/mobile/views/components/drive-folder-chooser.vue
deleted file mode 100644
index 853078664f..0000000000
--- a/src/server/web/app/mobile/views/components/drive-folder-chooser.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-<div class="mk-drive-folder-chooser">
- <div class="body">
- <header>
- <h1>%i18n:mobile.tags.mk-drive-folder-selector.select-folder%</h1>
- <button class="close" @click="cancel">%fa:times%</button>
- <button class="ok" @click="ok">%fa:check%</button>
- </header>
- <mk-drive ref="browser"
- select-folder
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- methods: {
- cancel() {
- this.$emit('canceled');
- this.$destroy();
- },
- ok() {
- this.$emit('selected', (this.$refs.browser as any).folder);
- this.$destroy();
- }
- }
-});
-</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(0, 0, 0, 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/server/web/app/mobile/views/components/drive.file-detail.vue b/src/server/web/app/mobile/views/components/drive.file-detail.vue
deleted file mode 100644
index f3274f677f..0000000000
--- a/src/server/web/app/mobile/views/components/drive.file-detail.vue
+++ /dev/null
@@ -1,295 +0,0 @@
-<template>
-<div class="file-detail">
- <div class="preview">
- <img v-if="kind == 'image'" ref="img"
- :src="file.url"
- :alt="file.name"
- :title="file.name"
- @load="onImageLoaded"
- :style="style">
- <template v-if="kind != 'image'">%fa: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.datasize | bytes }}</span>
- <span class="separator"></span>
- <span class="created-at" @click="showCreatedAt">%fa:R clock%<mk-time :time="file.createdAt"/></span>
- </div>
- </div>
- <div class="menu">
- <div>
- <a :href="`${file.url}?download`" :download="file.name">
- %fa:download%%i18n:mobile.tags.mk-drive-file-viewer.download%
- </a>
- <button @click="rename">
- %fa:pencil-alt%%i18n:mobile.tags.mk-drive-file-viewer.rename%
- </button>
- <button @click="move">
- %fa:R folder-open%%i18n:mobile.tags.mk-drive-file-viewer.move%
- </button>
- </div>
- </div>
- <div class="exif" v-show="exif">
- <div>
- <p>
- %fa:camera%%i18n:mobile.tags.mk-drive-file-viewer.exif%
- </p>
- <pre ref="exif" class="json">{{ exif ? JSON.stringify(exif, null, 2) : '' }}</pre>
- </div>
- </div>
- <div class="hash">
- <div>
- <p>
- %fa:hashtag%%i18n:mobile.tags.mk-drive-file-viewer.hash%
- </p>
- <code>{{ file.md5 }}</code>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as EXIF from 'exif-js';
-import * as hljs from 'highlight.js';
-import gcd from '../../../common/scripts/gcd';
-
-export default Vue.extend({
- props: ['file'],
- 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': `rgb(${ this.file.properties.avgColor.join(',') })`
- } : {};
- }
- },
- methods: {
- rename() {
- const name = window.prompt('名前を変更', this.file.name);
- if (name == null || name == '' || name == this.file.name) return;
- (this as any).api('drive/files/update', {
- fileId: this.file.id,
- name: name
- }).then(() => {
- this.browser.cf(this.file, true);
- });
- },
- move() {
- (this as any).apis.chooseDriveFolder().then(folder => {
- (this as any).api('drive/files/update', {
- fileId: this.file.id,
- folderId: folder == null ? null : folder.id
- }).then(() => {
- this.browser.cf(this.file, true);
- });
- });
- },
- showCreatedAt() {
- alert(new Date(this.file.createdAt).toLocaleString());
- },
- onImageLoaded() {
- const self = this;
- EXIF.getData(this.$refs.img, function(this: any) {
- const allMetaData = EXIF.getAllTags(this);
- self.exif = allMetaData;
- hljs.highlightBlock(self.$refs.exif);
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.file-detail
-
- > .preview
- padding 8px
- background #f0f0f0
-
- > img
- display block
- max-width 100%
- max-height 300px
- margin 0 auto
- box-shadow 1px 1px 4px rgba(0, 0, 0, 0.2)
-
- > footer
- padding 8px 8px 0 8px
- font-size 0.8em
- color #888
- text-align center
-
- > .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 #dfdfdf
-
- > div
- max-width 500px
- margin 0 auto
-
- > .separator
- padding 0 4px
- color #cdcdcd
-
- > .type
- > .data-size
- color #9d9d9d
-
- > mk-file-type-icon
- margin-right 4px
-
- > .created-at
- color #bdbdbd
-
- > [data-fa]
- margin-right 2px
-
- > .menu
- padding 14px
- border-top solid 1px #dfdfdf
-
- > div
- max-width 500px
- margin 0 auto
-
- > *
- display block
- width 100%
- padding 10px 16px
- margin 0 0 12px 0
- color #333
- font-size 0.9em
- text-align center
- text-decoration none
- text-shadow 0 1px 0 rgba(255, 255, 255, 0.9)
- background-image linear-gradient(#fafafa, #eaeaea)
- border 1px solid #ddd
- border-bottom-color #cecece
- border-radius 3px
-
- &:last-child
- margin-bottom 0
-
- &:active
- background-color #767676
- background-image none
- border-color #444
- box-shadow 0 1px 3px rgba(0, 0, 0, 0.075), inset 0 0 5px rgba(0, 0, 0, 0.2)
-
- > [data-fa]
- margin-right 4px
-
- > .hash
- padding 14px
- border-top solid 1px #dfdfdf
-
- > div
- max-width 500px
- margin 0 auto
-
- > p
- display block
- margin 0
- padding 0
- color #555
- font-size 0.9em
-
- > [data-fa]
- 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
-
- > .exif
- padding 14px
- border-top solid 1px #dfdfdf
-
- > div
- max-width 500px
- margin 0 auto
-
- > p
- display block
- margin 0
- padding 0
- color #555
- font-size 0.9em
-
- > [data-fa]
- margin-right 4px
-
- > pre
- display block
- width 100%
- margin 6px 0 0 0
- padding 8px
- height 128px
- overflow auto
- font-size 0.9em
- border solid 1px #dfdfdf
- border-radius 2px
- background #f5f5f5
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/drive.file.vue b/src/server/web/app/mobile/views/components/drive.file.vue
deleted file mode 100644
index 7d1957042b..0000000000
--- a/src/server/web/app/mobile/views/components/drive.file.vue
+++ /dev/null
@@ -1,171 +0,0 @@
-<template>
-<a class="file" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
- <div class="container">
- <div class="thumbnail" :style="thumbnail"></div>
- <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>
- <!--
- if file.tags.length > 0
- ul.tags
- each tag in file.tags
- li.tag(style={background: tag.color, color: contrast(tag.color)})= tag.name
- -->
- <footer>
- <p class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</p>
- <p class="separator"></p>
- <p class="data-size">{{ file.datasize | bytes }}</p>
- <p class="separator"></p>
- <p class="created-at">
- %fa:R clock%<mk-time :time="file.createdAt"/>
- </p>
- </footer>
- </div>
- </div>
-</a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['file'],
- data() {
- return {
- isSelected: false
- };
- },
- computed: {
- browser(): any {
- return this.$parent;
- },
- thumbnail(): any {
- return {
- 'background-color': this.file.properties.avgColor ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
- 'background-image': `url(${this.file.url}?thumbnail&size=128)`
- };
- }
- },
- 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>
-@import '~const.styl'
-
-.file
- display block
- text-decoration none !important
-
- *
- user-select none
- pointer-events none
-
- > .container
- max-width 500px
- margin 0 auto
- padding 16px
-
- &:after
- content ""
- display block
- clear both
-
- > .thumbnail
- display block
- float left
- width 64px
- height 64px
- background-size cover
- background-position center center
-
- > .body
- display block
- float left
- width calc(100% - 74px)
- margin-left 10px
-
- > .name
- display block
- margin 0
- padding 0
- font-size 0.9em
- font-weight bold
- color #555
- text-overflow ellipsis
- overflow-wrap 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
-
- > .separator
- display inline
- margin 0
- padding 0 4px
- color #CDCDCD
-
- > .type
- display inline
- margin 0
- padding 0
- color #9D9D9D
-
- > .mk-file-type-icon
- margin-right 4px
-
- > .data-size
- display inline
- margin 0
- padding 0
- color #9D9D9D
-
- > .created-at
- display inline
- margin 0
- padding 0
- color #BDBDBD
-
- > [data-fa]
- margin-right 2px
-
- &[data-is-selected]
- background $theme-color
-
- &, *
- color #fff !important
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/drive.folder.vue b/src/server/web/app/mobile/views/components/drive.folder.vue
deleted file mode 100644
index 22ff38fecb..0000000000
--- a/src/server/web/app/mobile/views/components/drive.folder.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<template>
-<a class="root folder" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`">
- <div class="container">
- <p class="name">%fa:folder%{{ folder.name }}</p>%fa: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>
-.root.folder
- display block
- color #777
- 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-fa]
- margin-right 6px
-
- > [data-fa]
- position absolute
- top 0
- bottom 0
- right 20px
-
- > *
- height 100%
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/drive.vue b/src/server/web/app/mobile/views/components/drive.vue
deleted file mode 100644
index ff5366a0ad..0000000000
--- a/src/server/web/app/mobile/views/components/drive.vue
+++ /dev/null
@@ -1,581 +0,0 @@
-<template>
-<div class="mk-drive">
- <nav ref="nav">
- <a @click.prevent="goRoot()" href="/i/drive">%fa:cloud%%i18n:mobile.tags.mk-drive.drive%</a>
- <template v-for="folder in hierarchyFolders">
- <span :key="folder.id + '>'">%fa: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:angle-right%</span>
- <p>{{ folder.name }}</p>
- </template>
- <template v-if="file != null">
- <span>%fa: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) }}% %i18n:mobile.tags.mk-drive.used%</p>
- <p v-if="folder != null && (folder.foldersCount > 0 || folder.filesCount > 0)">
- <template v-if="folder.foldersCount > 0">{{ folder.foldersCount }} %i18n:mobile.tags.mk-drive.folder-count%</template>
- <template v-if="folder.foldersCount > 0 && folder.filesCount > 0">%i18n:mobile.tags.mk-drive.count-separator%</template>
- <template v-if="folder.filesCount > 0">{{ folder.filesCount }} %i18n:mobile.tags.mk-drive.file-count%</template>
- </p>
- </div>
- <div class="folders" v-if="folders.length > 0">
- <x-folder v-for="folder in folders" :key="folder.id" :folder="folder"/>
- <p v-if="moreFolders">%i18n:mobile.tags.mk-drive.load-more%</p>
- </div>
- <div class="files" v-if="files.length > 0">
- <x-file v-for="file in files" :key="file.id" :file="file"/>
- <button class="more" v-if="moreFiles" @click="fetchMoreFiles">
- {{ fetchingMoreFiles ? '%i18n:common.loading%' : '%i18n:mobile.tags.mk-drive.load-more%' }}
- </button>
- </div>
- <div class="empty" v-if="files.length == 0 && folders.length == 0 && !fetching">
- <p v-if="folder == null">%i18n:mobile.tags.mk-drive.nothing-in-drive%</p>
- <p v-if="folder != null">%i18n:mobile.tags.mk-drive.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 XFolder from './drive.folder.vue';
-import XFile from './drive.file.vue';
-import XFileDetail from './drive.file-detail.vue';
-
-export default Vue.extend({
- 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,
- connectionId: null,
-
- fetching: true,
- fetchingMoreFiles: false,
- fetchingMoreFolders: false
- };
- },
- computed: {
- isFileSelectMode(): boolean {
- return this.selectFile;
- }
- },
- mounted() {
- this.connection = (this as any).os.streams.driveStream.getConnection();
- this.connectionId = (this as any).os.streams.driveStream.use();
-
- this.connection.on('file_created', this.onStreamDriveFileCreated);
- this.connection.on('file_updated', this.onStreamDriveFileUpdated);
- this.connection.on('folder_created', this.onStreamDriveFolderCreated);
- this.connection.on('folder_updated', 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.off('file_created', this.onStreamDriveFileCreated);
- this.connection.off('file_updated', this.onStreamDriveFileUpdated);
- this.connection.off('folder_created', this.onStreamDriveFolderCreated);
- this.connection.off('folder_updated', this.onStreamDriveFolderUpdated);
- (this as any).os.streams.driveStream.dispose(this.connectionId);
- },
- 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);
- }
- },
-
- 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) {
- this.file = null;
-
- if (target == null) {
- this.goRoot(silent);
- return;
- } else if (typeof target == 'object') {
- target = target.id;
- }
-
- this.fetching = true;
-
- (this as any).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) {
- if (this.folder || this.file) {
- 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 as any).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 as any).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) {
- fetchedFolders.forEach(this.appendFolder);
- fetchedFiles.forEach(this.appendFile);
- this.fetching = false;
-
- // 一連の読み込みが完了したイベントを発行
- this.$emit('fetched');
- } else {
- flag = true;
- // 一連の読み込みが半分完了したイベントを発行
- this.$emit('fetch-mid');
- }
- };
-
- if (this.folder == null) {
- // Fetch addtional drive info
- (this as any).api('drive').then(info => {
- this.info = info;
- });
- }
- },
-
- fetchMoreFiles() {
- this.fetching = true;
- this.fetchingMoreFiles = true;
-
- const max = 30;
-
- // ファイル一覧取得
- (this as any).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;
- }
- files.forEach(this.appendFile);
- 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 as any).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);
- });
- },
-
- openContextMenu() {
- const fn = window.prompt('何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>');
- if (fn == null || fn == '') return;
- switch (fn) {
- case '1':
- this.selectLocalFile();
- break;
- case '2':
- this.urlUpload();
- break;
- case '3':
- this.createFolder();
- break;
- case '4':
- this.renameFolder();
- break;
- case '5':
- this.moveFolder();
- break;
- case '6':
- alert('ごめんなさい!フォルダの削除は未実装です...。');
- break;
- }
- },
-
- selectLocalFile() {
- (this.$refs.file as any).click();
- },
-
- createFolder() {
- const name = window.prompt('フォルダー名');
- if (name == null || name == '') return;
- (this as any).api('drive/folders/create', {
- name: name,
- parentId: this.folder ? this.folder.id : undefined
- }).then(folder => {
- this.addFolder(folder, true);
- });
- },
-
- renameFolder() {
- if (this.folder == null) {
- alert('現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。');
- return;
- }
- const name = window.prompt('フォルダー名', this.folder.name);
- if (name == null || name == '') return;
- (this as any).api('drive/folders/update', {
- name: name,
- folderId: this.folder.id
- }).then(folder => {
- this.cd(folder);
- });
- },
-
- moveFolder() {
- if (this.folder == null) {
- alert('現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。');
- return;
- }
- (this as any).apis.chooseDriveFolder().then(folder => {
- (this as any).api('drive/folders/update', {
- parentId: folder ? folder.id : null,
- folderId: this.folder.id
- }).then(folder => {
- this.cd(folder);
- });
- });
- },
-
- urlUpload() {
- const url = window.prompt('アップロードしたいファイルのURL');
- if (url == null || url == '') return;
- (this as any).api('drive/files/upload_from_url', {
- url: url,
- folderId: this.folder ? this.folder.id : undefined
- });
- alert('アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。');
- },
-
- onChangeLocalFile() {
- Array.from((this.$refs.file as any).files)
- .forEach(f => (this.$refs.uploader as any).upload(f, this.folder));
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-drive
- background #fff
-
- > 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 rgba(0, 0, 0, 0.67)
- -webkit-backdrop-filter blur(12px)
- backdrop-filter blur(12px)
- background-color rgba(#fff, 0.75)
- border-bottom solid 1px rgba(0, 0, 0, 0.13)
-
- > p
- > a
- display inline
- margin 0
- padding 0
- text-decoration none !important
- color inherit
-
- &:last-child
- font-weight bold
-
- > [data-fa]
- margin-right 4px
-
- > span
- margin 0 8px
- opacity 0.5
-
- > .browser
- &.fetching
- opacity 0.5
-
- > .info
- border-bottom solid 1px #eee
-
- &:empty
- display none
-
- > p
- display block
- max-width 500px
- margin 0 auto
- padding 4px 16px
- font-size 10px
- color #777
-
- > .folders
- > .folder
- border-bottom solid 1px #eee
-
- > .files
- > .file
- border-bottom solid 1px #eee
-
- > .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(0, 0, 0, 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/server/web/app/mobile/views/components/follow-button.vue b/src/server/web/app/mobile/views/components/follow-button.vue
deleted file mode 100644
index 43c69d4e02..0000000000
--- a/src/server/web/app/mobile/views/components/follow-button.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
-<button class="mk-follow-button"
- :class="{ wait: wait, follow: !user.isFollowing, unfollow: user.isFollowing }"
- @click="onClick"
- :disabled="wait"
->
- <template v-if="!wait && user.isFollowing">%fa:minus%</template>
- <template v-if="!wait && !user.isFollowing">%fa:plus%</template>
- <template v-if="wait">%fa:spinner .pulse .fw%</template>
- {{ user.isFollowing ? '%i18n:mobile.tags.mk-follow-button.unfollow%' : '%i18n:mobile.tags.mk-follow-button.follow%' }}
-</button>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: {
- user: {
- type: Object,
- required: true
- }
- },
- data() {
- return {
- wait: false,
- connection: null,
- connectionId: null
- };
- },
- mounted() {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('follow', this.onFollow);
- this.connection.on('unfollow', this.onUnfollow);
- },
- beforeDestroy() {
- this.connection.off('follow', this.onFollow);
- this.connection.off('unfollow', this.onUnfollow);
- (this as any).os.stream.dispose(this.connectionId);
- },
- methods: {
-
- onFollow(user) {
- if (user.id == this.user.id) {
- this.user.isFollowing = user.isFollowing;
- }
- },
-
- onUnfollow(user) {
- if (user.id == this.user.id) {
- this.user.isFollowing = user.isFollowing;
- }
- },
-
- onClick() {
- this.wait = true;
- if (this.user.isFollowing) {
- (this as any).api('following/delete', {
- userId: this.user.id
- }).then(() => {
- this.user.isFollowing = false;
- }).catch(err => {
- console.error(err);
- }).then(() => {
- this.wait = false;
- });
- } else {
- (this as any).api('following/create', {
- userId: this.user.id
- }).then(() => {
- this.user.isFollowing = true;
- }).catch(err => {
- console.error(err);
- }).then(() => {
- this.wait = false;
- });
- }
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-follow-button
- display block
- user-select none
- cursor pointer
- padding 0 16px
- margin 0
- height inherit
- font-size 16px
- outline none
- border solid 1px $theme-color
- border-radius 4px
-
- *
- pointer-events none
-
- &.follow
- color $theme-color
- background transparent
-
- &:hover
- background rgba($theme-color, 0.1)
-
- &:active
- background rgba($theme-color, 0.2)
-
- &.unfollow
- color $theme-color-foreground
- background $theme-color
-
- &.wait
- cursor wait !important
- opacity 0.7
-
- > [data-fa]
- margin-right 4px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/friends-maker.vue b/src/server/web/app/mobile/views/components/friends-maker.vue
deleted file mode 100644
index 961a5f568a..0000000000
--- a/src/server/web/app/mobile/views/components/friends-maker.vue
+++ /dev/null
@@ -1,127 +0,0 @@
-<template>
-<div class="mk-friends-maker">
- <p class="title">気になるユーザーをフォロー:</p>
- <div class="users" v-if="!fetching && users.length > 0">
- <mk-user-card v-for="user in users" :key="user.id" :user="user"/>
- </div>
- <p class="empty" v-if="!fetching && users.length == 0">おすすめのユーザーは見つかりませんでした。</p>
- <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p>
- <a class="refresh" @click="refresh">もっと見る</a>
- <button class="close" @click="close" title="閉じる">%fa:times%</button>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- data() {
- return {
- users: [],
- fetching: true,
- limit: 6,
- page: 0
- };
- },
- mounted() {
- this.fetch();
- },
- methods: {
- fetch() {
- this.fetching = true;
- this.users = [];
-
- (this as any).api('users/recommendation', {
- limit: this.limit,
- offset: this.limit * this.page
- }).then(users => {
- this.users = users;
- this.fetching = false;
- });
- },
- refresh() {
- if (this.users.length < this.limit) {
- this.page = 0;
- } else {
- this.page++;
- }
- this.fetch();
- },
- close() {
- this.$destroy();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-friends-maker
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- > .title
- margin 0
- padding 8px 16px
- font-size 1em
- font-weight bold
- color #888
-
- > .users
- overflow-x scroll
- -webkit-overflow-scrolling touch
- white-space nowrap
- padding 16px
- background #eee
-
- > .mk-user-card
- &:not(:last-child)
- margin-right 16px
-
- > .empty
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > .fetching
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > [data-fa]
- margin-right 4px
-
- > .refresh
- display block
- margin 0
- padding 8px 16px
- text-align right
- font-size 0.9em
- color #999
-
- > .close
- cursor pointer
- display block
- position absolute
- top 0
- right 0
- z-index 1
- margin 0
- padding 0
- font-size 1.2em
- color #999
- border none
- outline none
- background transparent
-
- &:hover
- color #555
-
- &:active
- color #222
-
- > [data-fa]
- padding 10px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/index.ts b/src/server/web/app/mobile/views/components/index.ts
deleted file mode 100644
index fb8f65f47d..0000000000
--- a/src/server/web/app/mobile/views/components/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-
-import ui from './ui.vue';
-import timeline from './timeline.vue';
-import post from './post.vue';
-import posts from './posts.vue';
-import mediaImage from './media-image.vue';
-import mediaVideo from './media-video.vue';
-import drive from './drive.vue';
-import postPreview from './post-preview.vue';
-import subPostContent from './sub-post-content.vue';
-import postCard from './post-card.vue';
-import userCard from './user-card.vue';
-import postDetail from './post-detail.vue';
-import followButton from './follow-button.vue';
-import friendsMaker from './friends-maker.vue';
-import notification from './notification.vue';
-import notifications from './notifications.vue';
-import notificationPreview from './notification-preview.vue';
-import usersList from './users-list.vue';
-import userPreview from './user-preview.vue';
-import userTimeline from './user-timeline.vue';
-import activity from './activity.vue';
-import widgetContainer from './widget-container.vue';
-
-Vue.component('mk-ui', ui);
-Vue.component('mk-timeline', timeline);
-Vue.component('mk-post', post);
-Vue.component('mk-posts', posts);
-Vue.component('mk-media-image', mediaImage);
-Vue.component('mk-media-video', mediaVideo);
-Vue.component('mk-drive', drive);
-Vue.component('mk-post-preview', postPreview);
-Vue.component('mk-sub-post-content', subPostContent);
-Vue.component('mk-post-card', postCard);
-Vue.component('mk-user-card', userCard);
-Vue.component('mk-post-detail', postDetail);
-Vue.component('mk-follow-button', followButton);
-Vue.component('mk-friends-maker', friendsMaker);
-Vue.component('mk-notification', notification);
-Vue.component('mk-notifications', notifications);
-Vue.component('mk-notification-preview', notificationPreview);
-Vue.component('mk-users-list', usersList);
-Vue.component('mk-user-preview', userPreview);
-Vue.component('mk-user-timeline', userTimeline);
-Vue.component('mk-activity', activity);
-Vue.component('mk-widget-container', widgetContainer);
diff --git a/src/server/web/app/mobile/views/components/media-image.vue b/src/server/web/app/mobile/views/components/media-image.vue
deleted file mode 100644
index cfc2134988..0000000000
--- a/src/server/web/app/mobile/views/components/media-image.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<template>
-<a class="mk-media-image" :href="image.url" target="_blank" :style="style" :title="image.name"></a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: ['image'],
- computed: {
- style(): any {
- return {
- 'background-color': this.image.properties.avgColor ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
- 'background-image': `url(${this.image.url}?thumbnail&size=512)`
- };
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-media-image
- display block
- overflow hidden
- width 100%
- height 100%
- background-position center
- background-size cover
- border-radius 4px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/media-video.vue b/src/server/web/app/mobile/views/components/media-video.vue
deleted file mode 100644
index 68cd48587a..0000000000
--- a/src/server/web/app/mobile/views/components/media-video.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<template>
- <a class="mk-media-video"
- :href="video.url"
- target="_blank"
- :style="imageStyle"
- :title="video.name">
- %fa:R play-circle%
- </a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue'
-export default Vue.extend({
- props: ['video'],
- computed: {
- imageStyle(): any {
- return {
- 'background-image': `url(${this.video.url}?thumbnail&size=512)`
- };
- }
- },})
-</script>
-
-<style lang="stylus" scoped>
-.mk-media-video
- display flex
- justify-content center
- align-items center
-
- font-size 3.5em
- overflow hidden
- background-position center
- background-size cover
- width 100%
- height 100%
-</style>
diff --git a/src/server/web/app/mobile/views/components/notification-preview.vue b/src/server/web/app/mobile/views/components/notification-preview.vue
deleted file mode 100644
index fce9ed82f9..0000000000
--- a/src/server/web/app/mobile/views/components/notification-preview.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<template>
-<div class="mk-notification-preview" :class="notification.type">
- <template v-if="notification.type == 'reaction'">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p><mk-reaction-icon :reaction="notification.reaction"/>{{ notification.user.name }}</p>
- <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'repost'">
- <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:retweet%{{ notification.post.user.name }}</p>
- <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'quote'">
- <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:quote-left%{{ notification.post.user.name }}</p>
- <p class="post-preview">{{ getPostSummary(notification.post) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'follow'">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:user-plus%{{ notification.user.name }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'reply'">
- <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:reply%{{ notification.post.user.name }}</p>
- <p class="post-preview">{{ getPostSummary(notification.post) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'mention'">
- <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:at%{{ notification.post.user.name }}</p>
- <p class="post-preview">{{ getPostSummary(notification.post) }}</p>
- </div>
- </template>
-
- <template v-if="notification.type == 'poll_vote'">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- <div class="text">
- <p>%fa:chart-pie%{{ notification.user.name }}</p>
- <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
- </div>
- </template>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getPostSummary from '../../../../../common/get-post-summary';
-
-export default Vue.extend({
- props: ['notification'],
- data() {
- return {
- getPostSummary
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notification-preview
- margin 0
- padding 8px
- color #fff
- overflow-wrap break-word
-
- &:after
- content ""
- display block
- clear both
-
- img
- display block
- float left
- min-width 36px
- min-height 36px
- max-width 36px
- max-height 36px
- border-radius 6px
-
- .text
- float right
- width calc(100% - 36px)
- padding-left 8px
-
- p
- margin 0
-
- i, mk-reaction-icon
- margin-right 4px
-
- .post-ref
-
- [data-fa]
- font-size 1em
- font-weight normal
- font-style normal
- display inline-block
- margin-right 3px
-
- &.repost, &.quote
- .text p i
- color #77B255
-
- &.follow
- .text p i
- color #53c7ce
-
- &.reply, &.mention
- .text p i
- color #fff
-
-</style>
-
diff --git a/src/server/web/app/mobile/views/components/notification.vue b/src/server/web/app/mobile/views/components/notification.vue
deleted file mode 100644
index e221fb3ac4..0000000000
--- a/src/server/web/app/mobile/views/components/notification.vue
+++ /dev/null
@@ -1,164 +0,0 @@
-<template>
-<div class="mk-notification">
- <div class="notification reaction" v-if="notification.type == 'reaction'">
- <mk-time :time="notification.createdAt"/>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="text">
- <p>
- <mk-reaction-icon :reaction="notification.reaction"/>
- <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
- </p>
- <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
- %fa:quote-left%{{ getPostSummary(notification.post) }}
- %fa:quote-right%
- </router-link>
- </div>
- </div>
-
- <div class="notification repost" v-if="notification.type == 'repost'">
- <mk-time :time="notification.createdAt"/>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="text">
- <p>
- %fa:retweet%
- <router-link :to="`/@${acct}`">{{ notification.post.user.name }}</router-link>
- </p>
- <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
- %fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%
- </router-link>
- </div>
- </div>
-
- <template v-if="notification.type == 'quote'">
- <mk-post :post="notification.post"/>
- </template>
-
- <div class="notification follow" v-if="notification.type == 'follow'">
- <mk-time :time="notification.createdAt"/>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="text">
- <p>
- %fa:user-plus%
- <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
- </p>
- </div>
- </div>
-
- <template v-if="notification.type == 'reply'">
- <mk-post :post="notification.post"/>
- </template>
-
- <template v-if="notification.type == 'mention'">
- <mk-post :post="notification.post"/>
- </template>
-
- <div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
- <mk-time :time="notification.createdAt"/>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="text">
- <p>
- %fa:chart-pie%
- <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
- </p>
- <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
- %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%
- </router-link>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getPostSummary from '../../../../../common/get-post-summary';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['notification'],
- computed: {
- acct() {
- return getAcct(this.notification.user);
- }
- },
- data() {
- return {
- getPostSummary
- };
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notification
-
- > .notification
- padding 16px
- overflow-wrap break-word
-
- &:after
- content ""
- display block
- clear both
-
- > .mk-time
- display inline
- position absolute
- top 16px
- right 12px
- vertical-align top
- color rgba(0, 0, 0, 0.6)
- font-size 0.9em
-
- > .avatar-anchor
- display block
- float left
-
- img
- min-width 36px
- min-height 36px
- max-width 36px
- max-height 36px
- border-radius 6px
-
- > .text
- float right
- width calc(100% - 36px)
- padding-left 8px
-
- p
- margin 0
-
- i, .mk-reaction-icon
- margin-right 4px
-
- > .post-preview
- color rgba(0, 0, 0, 0.7)
-
- > .post-ref
- color rgba(0, 0, 0, 0.7)
-
- [data-fa]
- font-size 1em
- font-weight normal
- font-style normal
- display inline-block
- margin-right 3px
-
- &.repost
- .text p i
- color #77B255
-
- &.follow
- .text p i
- color #53c7ce
-
-</style>
-
diff --git a/src/server/web/app/mobile/views/components/notifications.vue b/src/server/web/app/mobile/views/components/notifications.vue
deleted file mode 100644
index d68b990dfa..0000000000
--- a/src/server/web/app/mobile/views/components/notifications.vue
+++ /dev/null
@@ -1,168 +0,0 @@
-<template>
-<div class="mk-notifications">
- <div class="notifications" v-if="notifications.length != 0">
- <template v-for="(notification, i) in _notifications">
- <mk-notification :notification="notification" :key="notification.id"/>
- <p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date">
- <span>%fa:angle-up%{{ notification._datetext }}</span>
- <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
- </p>
- </template>
- </div>
- <button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
- <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>
- {{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:mobile.tags.mk-notifications.more%' }}
- </button>
- <p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:mobile.tags.mk-notifications.empty%</p>
- <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- data() {
- return {
- fetching: true,
- fetchingMoreNotifications: false,
- notifications: [],
- moreNotifications: false,
- connection: null,
- connectionId: null
- };
- },
- computed: {
- _notifications(): any[] {
- return (this.notifications as any).map(notification => {
- const date = new Date(notification.createdAt).getDate();
- const month = new Date(notification.createdAt).getMonth() + 1;
- notification._date = date;
- notification._datetext = `${month}月 ${date}日`;
- return notification;
- });
- }
- },
- mounted() {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('notification', this.onNotification);
-
- const max = 10;
-
- (this as any).api('i/notifications', {
- limit: max + 1
- }).then(notifications => {
- if (notifications.length == max + 1) {
- this.moreNotifications = true;
- notifications.pop();
- }
-
- this.notifications = notifications;
- this.fetching = false;
- this.$emit('fetched');
- });
- },
- beforeDestroy() {
- this.connection.off('notification', this.onNotification);
- (this as any).os.stream.dispose(this.connectionId);
- },
- methods: {
- fetchMoreNotifications() {
- this.fetchingMoreNotifications = true;
-
- const max = 30;
-
- (this as any).api('i/notifications', {
- limit: max + 1,
- untilId: this.notifications[this.notifications.length - 1].id
- }).then(notifications => {
- if (notifications.length == max + 1) {
- this.moreNotifications = true;
- notifications.pop();
- } else {
- this.moreNotifications = false;
- }
- this.notifications = this.notifications.concat(notifications);
- this.fetchingMoreNotifications = false;
- });
- },
- onNotification(notification) {
- // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
- this.connection.send({
- type: 'read_notification',
- id: notification.id
- });
-
- this.notifications.unshift(notification);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notifications
- margin 8px auto
- padding 0
- max-width 500px
- width calc(100% - 16px)
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- @media (min-width 500px)
- margin 16px auto
- width calc(100% - 32px)
-
- > .notifications
-
- > .mk-notification
- margin 0 auto
- max-width 500px
- border-bottom solid 1px rgba(0, 0, 0, 0.05)
-
- &:last-child
- border-bottom none
-
- > .date
- display block
- margin 0
- line-height 32px
- text-align center
- font-size 0.8em
- color #aaa
- background #fdfdfd
- border-bottom solid 1px rgba(0, 0, 0, 0.05)
-
- span
- margin 0 16px
-
- i
- margin-right 8px
-
- > .more
- display block
- width 100%
- padding 16px
- color #555
- border-top solid 1px rgba(0, 0, 0, 0.05)
-
- > [data-fa]
- margin-right 4px
-
- > .empty
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > .fetching
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > [data-fa]
- margin-right 4px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/notify.vue b/src/server/web/app/mobile/views/components/notify.vue
deleted file mode 100644
index 6d4a481dbe..0000000000
--- a/src/server/web/app/mobile/views/components/notify.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<template>
-<div class="mk-notify">
- <mk-notification-preview :notification="notification"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as anime from 'animejs';
-
-export default Vue.extend({
- props: ['notification'],
- mounted() {
- this.$nextTick(() => {
- anime({
- targets: this.$el,
- bottom: '0px',
- duration: 500,
- easing: 'easeOutQuad'
- });
-
- setTimeout(() => {
- anime({
- targets: this.$el,
- bottom: '-64px',
- duration: 500,
- easing: 'easeOutQuad',
- complete: () => this.$destroy()
- });
- }, 6000);
- });
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-notify
- position fixed
- z-index 1024
- bottom -64px
- left 0
- width 100%
- height 64px
- pointer-events none
- -webkit-backdrop-filter blur(2px)
- backdrop-filter blur(2px)
- background-color rgba(#000, 0.5)
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/post-card.vue b/src/server/web/app/mobile/views/components/post-card.vue
deleted file mode 100644
index 10dfd92415..0000000000
--- a/src/server/web/app/mobile/views/components/post-card.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<template>
-<div class="mk-post-card">
- <a :href="`/@${acct}/${post.id}`">
- <header>
- <img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ post.user.name }}</h3>
- </header>
- <div>
- {{ text }}
- </div>
- <mk-time :time="post.createdAt"/>
- </a>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import summary from '../../../../../common/get-post-summary';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['post'],
- computed: {
- acct() {
- return getAcct(this.post.user);
- },
- text(): string {
- return summary(this.post);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-post-card
- display inline-block
- width 150px
- //height 120px
- font-size 12px
- background #fff
- border-radius 4px
-
- > a
- display block
- color #2c3940
-
- &: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, rgba(255, 255, 255, 0) 0%, #fff 100%)
-
- > .mk-time
- display inline-block
- padding 8px
- color #aaa
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/post-detail.sub.vue b/src/server/web/app/mobile/views/components/post-detail.sub.vue
deleted file mode 100644
index db7567834a..0000000000
--- a/src/server/web/app/mobile/views/components/post-detail.sub.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-<template>
-<div class="root sub">
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="main">
- <header>
- <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
- <span class="username">@{{ acct }}</span>
- <router-link class="time" :to="`/@${acct}/${post.id}`">
- <mk-time :time="post.createdAt"/>
- </router-link>
- </header>
- <div class="body">
- <mk-sub-post-content class="text" :post="post"/>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['post'],
- computed: {
- acct() {
- return getAcct(this.post.user);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.root.sub
- padding 8px
- font-size 0.9em
- background #fdfdfd
-
- @media (min-width 500px)
- padding 12px
-
- &:after
- content ""
- display block
- clear both
-
- &:hover
- > .main > footer > button
- color #888
-
- > .avatar-anchor
- display block
- float left
- margin 0 12px 0 0
-
- > .avatar
- display block
- width 48px
- height 48px
- margin 0
- border-radius 8px
- vertical-align bottom
-
- > .main
- float left
- width calc(100% - 60px)
-
- > header
- display flex
- margin-bottom 4px
- white-space nowrap
-
- > .name
- display block
- margin 0 .5em 0 0
- padding 0
- overflow hidden
- color #607073
- font-size 1em
- font-weight 700
- text-align left
- text-decoration none
- text-overflow ellipsis
-
- &:hover
- text-decoration underline
-
- > .username
- text-align left
- margin 0 .5em 0 0
- color #d1d8da
-
- > .time
- margin-left auto
- color #b2b8bb
-
- > .body
-
- > .text
- cursor default
- margin 0
- padding 0
- font-size 1.1em
- color #717171
-
-</style>
-
diff --git a/src/server/web/app/mobile/views/components/post-detail.vue b/src/server/web/app/mobile/views/components/post-detail.vue
deleted file mode 100644
index f0af1a61aa..0000000000
--- a/src/server/web/app/mobile/views/components/post-detail.vue
+++ /dev/null
@@ -1,447 +0,0 @@
-<template>
-<div class="mk-post-detail">
- <button
- class="more"
- v-if="p.reply && p.reply.replyId && context == null"
- @click="fetchContext"
- :disabled="fetchingContext"
- >
- <template v-if="!contextFetching">%fa:ellipsis-v%</template>
- <template v-if="contextFetching">%fa:spinner .pulse%</template>
- </button>
- <div class="context">
- <x-sub v-for="post in context" :key="post.id" :post="post"/>
- </div>
- <div class="reply-to" v-if="p.reply">
- <x-sub :post="p.reply"/>
- </div>
- <div class="repost" v-if="isRepost">
- <p>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
- </router-link>
- %fa:retweet%
- <router-link class="name" :to="`/@${acct}`">
- {{ post.user.name }}
- </router-link>
- がRepost
- </p>
- </div>
- <article>
- <header>
- <router-link class="avatar-anchor" :to="`/@${pAcct}`">
- <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div>
- <router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link>
- <span class="username">@{{ pAcct }}</span>
- </div>
- </header>
- <div class="body">
- <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i" :class="$style.text"/>
- <div class="tags" v-if="p.tags && p.tags.length > 0">
- <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
- </div>
- <div class="media" v-if="p.media">
- <mk-media-list :media-list="p.media"/>
- </div>
- <mk-poll v-if="p.poll" :post="p"/>
- <mk-url-preview v-for="url in urls" :url="url" :key="url"/>
- <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
- <div class="map" v-if="p.geo" ref="map"></div>
- <div class="repost" v-if="p.repost">
- <mk-post-preview :post="p.repost"/>
- </div>
- </div>
- <router-link class="time" :to="`/@${pAcct}/${p.id}`">
- <mk-time :time="p.createdAt" mode="detail"/>
- </router-link>
- <footer>
- <mk-reactions-viewer :post="p"/>
- <button @click="reply" title="%i18n:mobile.tags.mk-post-detail.reply%">
- %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
- </button>
- <button @click="repost" title="Repost">
- %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p>
- </button>
- <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%">
- %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
- </button>
- <button @click="menu" ref="menuButton">
- %fa:ellipsis-h%
- </button>
- </footer>
- </article>
- <div class="replies" v-if="!compact">
- <x-sub v-for="post in replies" :key="post.id" :post="post"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-import MkPostMenu from '../../../common/views/components/post-menu.vue';
-import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
-import XSub from './post-detail.sub.vue';
-
-export default Vue.extend({
- components: {
- XSub
- },
- props: {
- post: {
- type: Object,
- required: true
- },
- compact: {
- default: false
- }
- },
- data() {
- return {
- context: [],
- contextFetching: false,
- replies: [],
- };
- },
- computed: {
- acct() {
- return getAcct(this.post.user);
- },
- pAcct() {
- return getAcct(this.p.user);
- },
- isRepost(): boolean {
- return (this.post.repost &&
- this.post.text == null &&
- this.post.mediaIds == null &&
- this.post.poll == null);
- },
- p(): any {
- return this.isRepost ? this.post.repost : this.post;
- },
- reactionsCount(): number {
- return this.p.reactionCounts
- ? Object.keys(this.p.reactionCounts)
- .map(key => this.p.reactionCounts[key])
- .reduce((a, b) => a + b)
- : 0;
- },
- urls(): string[] {
- if (this.p.ast) {
- return this.p.ast
- .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
- .map(t => t.url);
- } else {
- return null;
- }
- }
- },
- mounted() {
- // Get replies
- if (!this.compact) {
- (this as any).api('posts/replies', {
- postId: this.p.id,
- limit: 8
- }).then(replies => {
- this.replies = replies;
- });
- }
-
- // Draw map
- if (this.p.geo) {
- const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
- if (shouldShowMap) {
- (this as any).os.getGoogleMaps().then(maps => {
- const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
- const map = new maps.Map(this.$refs.map, {
- center: uluru,
- zoom: 15
- });
- new maps.Marker({
- position: uluru,
- map: map
- });
- });
- }
- }
- },
- methods: {
- fetchContext() {
- this.contextFetching = true;
-
- // Fetch context
- (this as any).api('posts/context', {
- postId: this.p.replyId
- }).then(context => {
- this.contextFetching = false;
- this.context = context.reverse();
- });
- },
- reply() {
- (this as any).apis.post({
- reply: this.p
- });
- },
- repost() {
- (this as any).apis.post({
- repost: this.p
- });
- },
- react() {
- (this as any).os.new(MkReactionPicker, {
- source: this.$refs.reactButton,
- post: this.p,
- compact: true
- });
- },
- menu() {
- (this as any).os.new(MkPostMenu, {
- source: this.$refs.menuButton,
- post: this.p,
- compact: true
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-post-detail
- overflow hidden
- margin 0 auto
- padding 0
- width 100%
- text-align left
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- > .fetching
- padding 64px 0
-
- > .more
- display block
- margin 0
- padding 10px 0
- width 100%
- font-size 1em
- text-align center
- color #999
- cursor pointer
- background #fafafa
- outline none
- border none
- border-bottom solid 1px #eef0f2
- border-radius 6px 6px 0 0
- box-shadow none
-
- &:hover
- background #f6f6f6
-
- &:active
- background #f0f0f0
-
- &:disabled
- color #ccc
-
- > .context
- > *
- border-bottom 1px solid #eef0f2
-
- > .repost
- color #9dbb00
- background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
-
- > p
- margin 0
- padding 16px 32px
-
- .avatar-anchor
- display inline-block
-
- .avatar
- vertical-align bottom
- min-width 28px
- min-height 28px
- max-width 28px
- max-height 28px
- margin 0 8px 0 0
- border-radius 6px
-
- [data-fa]
- margin-right 4px
-
- .name
- font-weight bold
-
- & + article
- padding-top 8px
-
- > .reply-to
- border-bottom 1px solid #eef0f2
-
- > article
- padding 14px 16px 9px 16px
-
- @media (min-width 500px)
- padding 28px 32px 18px 32px
-
- &:after
- content ""
- display block
- clear both
-
- &:hover
- > .main > footer > button
- color #888
-
- > header
- display flex
- line-height 1.1
-
- > .avatar-anchor
- display block
- padding 0 .5em 0 0
-
- > .avatar
- display block
- width 54px
- height 54px
- margin 0
- border-radius 8px
- vertical-align bottom
-
- @media (min-width 500px)
- width 60px
- height 60px
-
- > div
-
- > .name
- display inline-block
- margin .4em 0
- color #777
- 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 #ccc
-
- > .body
- padding 8px 0
-
- > .repost
- margin 8px 0
-
- > .mk-post-preview
- padding 16px
- border dashed 1px #c0dac6
- border-radius 8px
-
- > .location
- margin 4px 0
- font-size 12px
- color #ccc
-
- > .map
- width 100%
- height 200px
-
- &:empty
- display none
-
- > .mk-url-preview
- margin-top 8px
-
- > .media
- > img
- display block
- max-width 100%
-
- > .tags
- margin 4px 0 0 0
-
- > *
- display inline-block
- margin 0 8px 0 0
- padding 2px 8px 2px 16px
- font-size 90%
- color #8d969e
- background #edf0f3
- border-radius 4px
-
- &:before
- content ""
- display block
- position absolute
- top 0
- bottom 0
- left 4px
- width 8px
- height 8px
- margin auto 0
- background #fff
- border-radius 100%
-
- > .time
- font-size 16px
- color #c0c0c0
-
- > footer
- font-size 1.2em
-
- > button
- margin 0
- padding 8px
- background transparent
- border none
- box-shadow none
- font-size 1em
- color #ddd
- cursor pointer
-
- &:not(:last-child)
- margin-right 28px
-
- &:hover
- color #666
-
- > .count
- display inline
- margin 0 0 0 8px
- color #999
-
- &.reacted
- color $theme-color
-
- > .replies
- > *
- border-top 1px solid #eef0f2
-
-</style>
-
-<style lang="stylus" module>
-.text
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- font-size 16px
- color #717171
-
- @media (min-width 500px)
- font-size 24px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/post-form.vue b/src/server/web/app/mobile/views/components/post-form.vue
deleted file mode 100644
index 5b78a25710..0000000000
--- a/src/server/web/app/mobile/views/components/post-form.vue
+++ /dev/null
@@ -1,275 +0,0 @@
-<template>
-<div class="mk-post-form">
- <header>
- <button class="cancel" @click="cancel">%fa:times%</button>
- <div>
- <span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
- <span class="geo" v-if="geo">%fa:map-marker-alt%</span>
- <button class="submit" :disabled="posting" @click="post">{{ reply ? '返信' : '%i18n:mobile.tags.mk-post-form.submit%' }}</button>
- </div>
- </header>
- <div class="form">
- <mk-post-preview v-if="reply" :post="reply"/>
- <textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea>
- <div class="attaches" v-show="files.length != 0">
- <x-draggable class="files" :list="files" :options="{ animation: 150 }">
- <div class="file" v-for="file in files" :key="file.id">
- <div class="img" :style="`background-image: url(${file.url}?thumbnail&size=128)`" @click="detachMedia(file)"></div>
- </div>
- </x-draggable>
- </div>
- <mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false"/>
- <mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
- <button class="upload" @click="chooseFile">%fa:upload%</button>
- <button class="drive" @click="chooseFileFromDrive">%fa:cloud%</button>
- <button class="kao" @click="kao">%fa:R smile%</button>
- <button class="poll" @click="poll = true">%fa:chart-pie%</button>
- <button class="geo" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
- <input ref="file" class="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as XDraggable from 'vuedraggable';
-import getKao from '../../../common/scripts/get-kao';
-
-export default Vue.extend({
- components: {
- XDraggable
- },
- props: ['reply'],
- data() {
- return {
- posting: false,
- text: '',
- uploadings: [],
- files: [],
- poll: false,
- geo: null
- };
- },
- mounted() {
- this.$nextTick(() => {
- this.focus();
- });
- },
- methods: {
- focus() {
- (this.$refs.text as any).focus();
- },
- chooseFile() {
- (this.$refs.file as any).click();
- },
- chooseFileFromDrive() {
- (this as any).apis.chooseDriveFile({
- multiple: true
- }).then(files => {
- files.forEach(this.attachMedia);
- });
- },
- attachMedia(driveFile) {
- this.files.push(driveFile);
- this.$emit('change-attached-media', this.files);
- },
- detachMedia(file) {
- this.files = this.files.filter(x => x.id != file.id);
- this.$emit('change-attached-media', this.files);
- },
- onChangeFile() {
- Array.from((this.$refs.file as any).files).forEach(this.upload);
- },
- upload(file) {
- (this.$refs.uploader as any).upload(file);
- },
- onChangeUploadings(uploads) {
- this.$emit('change-uploadings', uploads);
- },
- setGeo() {
- if (navigator.geolocation == null) {
- alert('お使いの端末は位置情報に対応していません');
- return;
- }
-
- navigator.geolocation.getCurrentPosition(pos => {
- this.geo = pos.coords;
- }, err => {
- alert('エラー: ' + err.message);
- }, {
- enableHighAccuracy: true
- });
- },
- removeGeo() {
- this.geo = null;
- },
- clear() {
- this.text = '';
- this.files = [];
- this.poll = false;
- this.$emit('change-attached-media');
- },
- post() {
- this.posting = true;
- const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== true;
- (this as any).api('posts/create', {
- text: this.text == '' ? undefined : this.text,
- mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
- replyId: this.reply ? this.reply.id : undefined,
- poll: this.poll ? (this.$refs.poll as any).get() : undefined,
- geo: this.geo ? {
- coordinates: [this.geo.longitude, this.geo.latitude],
- altitude: this.geo.altitude,
- accuracy: this.geo.accuracy,
- altitudeAccuracy: this.geo.altitudeAccuracy,
- heading: isNaN(this.geo.heading) ? null : this.geo.heading,
- speed: this.geo.speed,
- } : null,
- viaMobile: viaMobile
- }).then(data => {
- this.$emit('post');
- this.$destroy();
- }).catch(err => {
- this.posting = false;
- });
- },
- cancel() {
- this.$emit('cancel');
- this.$destroy();
- },
- kao() {
- this.text += getKao();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-post-form
- max-width 500px
- width calc(100% - 16px)
- margin 8px auto
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- @media (min-width 500px)
- margin 16px auto
- width calc(100% - 32px)
-
- > header
- z-index 1
- height 50px
- box-shadow 0 1px 0 0 rgba(0, 0, 0, 0.1)
-
- > .cancel
- padding 0
- width 50px
- line-height 50px
- font-size 24px
- color #555
-
- > div
- position absolute
- top 0
- right 0
- color #657786
-
- > .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 $theme-color-foreground
- background $theme-color
- border-radius 4px
-
- &:disabled
- opacity 0.7
-
- > .form
- max-width 500px
- margin 0 auto
-
- > .mk-post-preview
- padding 16px
-
- > .attaches
-
- > .files
- display block
- margin 0
- padding 4px
- list-style none
-
- &:after
- content ""
- display block
- clear both
-
- > .file
- display block
- float left
- margin 0
- padding 0
- border solid 4px transparent
-
- > .img
- width 64px
- height 64px
- background-size cover
- background-position center center
-
- > .mk-uploader
- margin 8px 0 0 0
- padding 8px
-
- > .file
- display none
-
- > textarea
- display block
- padding 12px
- margin 0
- width 100%
- max-width 100%
- min-width 100%
- min-height 80px
- font-size 16px
- color #333
- border none
- border-bottom solid 1px #ddd
- border-radius 0
-
- &:disabled
- opacity 0.5
-
- > .upload
- > .drive
- > .kao
- > .poll
- > .geo
- display inline-block
- padding 0
- margin 0
- width 48px
- height 48px
- font-size 20px
- color #657786
- background transparent
- outline none
- border none
- border-radius 0
- box-shadow none
-
-</style>
-
diff --git a/src/server/web/app/mobile/views/components/post-preview.vue b/src/server/web/app/mobile/views/components/post-preview.vue
deleted file mode 100644
index a6141dc8e3..0000000000
--- a/src/server/web/app/mobile/views/components/post-preview.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-<div class="mk-post-preview">
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="main">
- <header>
- <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
- <span class="username">@{{ acct }}</span>
- <router-link class="time" :to="`/@${acct}/${post.id}`">
- <mk-time :time="post.createdAt"/>
- </router-link>
- </header>
- <div class="body">
- <mk-sub-post-content class="text" :post="post"/>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['post'],
- computed: {
- acct() {
- return getAcct(this.post.user);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-post-preview
- margin 0
- padding 0
- font-size 0.9em
- background #fff
-
- &:after
- content ""
- display block
- clear both
-
- &:hover
- > .main > footer > button
- color #888
-
- > .avatar-anchor
- display block
- float left
- margin 0 12px 0 0
-
- > .avatar
- display block
- width 48px
- height 48px
- margin 0
- border-radius 8px
- vertical-align bottom
-
- > .main
- float left
- width calc(100% - 60px)
-
- > header
- display flex
- margin-bottom 4px
- white-space nowrap
-
- > .name
- display block
- margin 0 .5em 0 0
- padding 0
- overflow hidden
- color #607073
- font-size 1em
- font-weight 700
- text-align left
- text-decoration none
- text-overflow ellipsis
-
- &:hover
- text-decoration underline
-
- > .username
- text-align left
- margin 0 .5em 0 0
- color #d1d8da
-
- > .time
- margin-left auto
- color #b2b8bb
-
- > .body
-
- > .text
- cursor default
- margin 0
- padding 0
- font-size 1.1em
- color #717171
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/post.sub.vue b/src/server/web/app/mobile/views/components/post.sub.vue
deleted file mode 100644
index adf444a2d6..0000000000
--- a/src/server/web/app/mobile/views/components/post.sub.vue
+++ /dev/null
@@ -1,115 +0,0 @@
-<template>
-<div class="sub">
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
- </router-link>
- <div class="main">
- <header>
- <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
- <span class="username">@{{ acct }}</span>
- <router-link class="created-at" :to="`/@${acct}/${post.id}`">
- <mk-time :time="post.createdAt"/>
- </router-link>
- </header>
- <div class="body">
- <mk-sub-post-content class="text" :post="post"/>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['post'],
- computed: {
- acct() {
- return getAcct(this.post.user);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.sub
- font-size 0.9em
- padding 16px
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar-anchor
- display block
- float left
- margin 0 10px 0 0
-
- @media (min-width 500px)
- margin-right 16px
-
- > .avatar
- display block
- width 44px
- height 44px
- margin 0
- border-radius 8px
- vertical-align bottom
-
- @media (min-width 500px)
- width 52px
- height 52px
-
- > .main
- float left
- width calc(100% - 54px)
-
- @media (min-width 500px)
- width calc(100% - 68px)
-
- > header
- display flex
- margin-bottom 2px
- white-space nowrap
-
- > .name
- display block
- margin 0 0.5em 0 0
- padding 0
- overflow hidden
- color #607073
- font-size 1em
- font-weight 700
- text-align left
- text-decoration none
- text-overflow ellipsis
-
- &:hover
- text-decoration underline
-
- > .username
- text-align left
- margin 0
- color #d1d8da
-
- > .created-at
- margin-left auto
- color #b2b8bb
-
- > .body
-
- > .text
- cursor default
- margin 0
- padding 0
- font-size 1.1em
- color #717171
-
- pre
- max-height 120px
- font-size 80%
-
-</style>
-
diff --git a/src/server/web/app/mobile/views/components/post.vue b/src/server/web/app/mobile/views/components/post.vue
deleted file mode 100644
index a01eb7669e..0000000000
--- a/src/server/web/app/mobile/views/components/post.vue
+++ /dev/null
@@ -1,523 +0,0 @@
-<template>
-<div class="post" :class="{ repost: isRepost }">
- <div class="reply-to" v-if="p.reply">
- <x-sub :post="p.reply"/>
- </div>
- <div class="repost" v-if="isRepost">
- <p>
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- %fa:retweet%
- <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span>
- <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
- <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span>
- </p>
- <mk-time :time="post.createdAt"/>
- </div>
- <article>
- <router-link class="avatar-anchor" :to="`/@${pAcct}`">
- <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
- </router-link>
- <div class="main">
- <header>
- <router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link>
- <span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
- <span class="username">@{{ pAcct }}</span>
- <div class="info">
- <span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
- <router-link class="created-at" :to="url">
- <mk-time :time="p.createdAt"/>
- </router-link>
- </div>
- </header>
- <div class="body">
- <p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
- <div class="text">
- <a class="reply" v-if="p.reply">
- %fa:reply%
- </a>
- <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i" :class="$style.text"/>
- <a class="rp" v-if="p.repost != null">RP:</a>
- </div>
- <div class="media" v-if="p.media">
- <mk-media-list :media-list="p.media"/>
- </div>
- <mk-poll v-if="p.poll" :post="p" ref="pollViewer"/>
- <div class="tags" v-if="p.tags && p.tags.length > 0">
- <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
- </div>
- <mk-url-preview v-for="url in urls" :url="url" :key="url"/>
- <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
- <div class="map" v-if="p.geo" ref="map"></div>
- <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
- <div class="repost" v-if="p.repost">
- <mk-post-preview :post="p.repost"/>
- </div>
- </div>
- <footer>
- <mk-reactions-viewer :post="p" ref="reactionsViewer"/>
- <button @click="reply">
- %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
- </button>
- <button @click="repost" title="Repost">
- %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p>
- </button>
- <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
- %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
- </button>
- <button class="menu" @click="menu" ref="menuButton">
- %fa:ellipsis-h%
- </button>
- </footer>
- </div>
- </article>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-import MkPostMenu from '../../../common/views/components/post-menu.vue';
-import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
-import XSub from './post.sub.vue';
-
-export default Vue.extend({
- components: {
- XSub
- },
- props: ['post'],
- data() {
- return {
- connection: null,
- connectionId: null
- };
- },
- computed: {
- acct() {
- return getAcct(this.post.user);
- },
- pAcct() {
- return getAcct(this.p.user);
- },
- isRepost(): boolean {
- return (this.post.repost &&
- this.post.text == null &&
- this.post.mediaIds == null &&
- this.post.poll == null);
- },
- p(): any {
- return this.isRepost ? this.post.repost : this.post;
- },
- reactionsCount(): number {
- return this.p.reactionCounts
- ? Object.keys(this.p.reactionCounts)
- .map(key => this.p.reactionCounts[key])
- .reduce((a, b) => a + b)
- : 0;
- },
- url(): string {
- return `/@${this.pAcct}/${this.p.id}`;
- },
- urls(): string[] {
- if (this.p.ast) {
- return this.p.ast
- .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
- .map(t => t.url);
- } else {
- return null;
- }
- }
- },
- created() {
- if ((this as any).os.isSignedIn) {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
- }
- },
- mounted() {
- this.capture(true);
-
- if ((this as any).os.isSignedIn) {
- this.connection.on('_connected_', this.onStreamConnected);
- }
-
- // Draw map
- if (this.p.geo) {
- const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
- if (shouldShowMap) {
- (this as any).os.getGoogleMaps().then(maps => {
- const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
- const map = new maps.Map(this.$refs.map, {
- center: uluru,
- zoom: 15
- });
- new maps.Marker({
- position: uluru,
- map: map
- });
- });
- }
- }
- },
- beforeDestroy() {
- this.decapture(true);
-
- if ((this as any).os.isSignedIn) {
- this.connection.off('_connected_', this.onStreamConnected);
- (this as any).os.stream.dispose(this.connectionId);
- }
- },
- methods: {
- capture(withHandler = false) {
- if ((this as any).os.isSignedIn) {
- this.connection.send({
- type: 'capture',
- id: this.p.id
- });
- if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated);
- }
- },
- decapture(withHandler = false) {
- if ((this as any).os.isSignedIn) {
- this.connection.send({
- type: 'decapture',
- id: this.p.id
- });
- if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated);
- }
- },
- onStreamConnected() {
- this.capture();
- },
- onStreamPostUpdated(data) {
- const post = data.post;
- if (post.id == this.post.id) {
- this.$emit('update:post', post);
- } else if (post.id == this.post.repostId) {
- this.post.repost = post;
- }
- },
- reply() {
- (this as any).apis.post({
- reply: this.p
- });
- },
- repost() {
- (this as any).apis.post({
- repost: this.p
- });
- },
- react() {
- (this as any).os.new(MkReactionPicker, {
- source: this.$refs.reactButton,
- post: this.p,
- compact: true
- });
- },
- menu() {
- (this as any).os.new(MkPostMenu, {
- source: this.$refs.menuButton,
- post: this.p,
- compact: true
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.post
- font-size 12px
- border-bottom solid 1px #eaeaea
-
- &:first-child
- border-radius 8px 8px 0 0
-
- > .repost
- border-radius 8px 8px 0 0
-
- &:last-of-type
- border-bottom none
-
- @media (min-width 350px)
- font-size 14px
-
- @media (min-width 500px)
- font-size 16px
-
- > .repost
- color #9dbb00
- background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
-
- > p
- margin 0
- padding 8px 16px
- line-height 28px
-
- @media (min-width 500px)
- padding 16px
-
- .avatar-anchor
- display inline-block
-
- .avatar
- vertical-align bottom
- width 28px
- height 28px
- margin 0 8px 0 0
- border-radius 6px
-
- [data-fa]
- margin-right 4px
-
- .name
- font-weight bold
-
- > .mk-time
- position absolute
- top 8px
- right 16px
- font-size 0.9em
- line-height 28px
-
- @media (min-width 500px)
- top 16px
-
- & + article
- padding-top 8px
-
- > .reply-to
- background rgba(0, 0, 0, 0.0125)
-
- > .mk-post-preview
- background transparent
-
- > article
- padding 14px 16px 9px 16px
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar-anchor
- display block
- float left
- margin 0 10px 8px 0
- position -webkit-sticky
- position sticky
- top 62px
-
- @media (min-width 500px)
- margin-right 16px
-
- > .avatar
- display block
- width 48px
- height 48px
- margin 0
- border-radius 6px
- vertical-align bottom
-
- @media (min-width 500px)
- width 58px
- height 58px
- border-radius 8px
-
- > .main
- float left
- width calc(100% - 58px)
-
- @media (min-width 500px)
- width calc(100% - 74px)
-
- > header
- display flex
- align-items center
- white-space nowrap
-
- @media (min-width 500px)
- margin-bottom 2px
-
- > .name
- display block
- margin 0 0.5em 0 0
- padding 0
- overflow hidden
- color #627079
- font-size 1em
- font-weight bold
- text-decoration none
- text-overflow ellipsis
-
- &:hover
- text-decoration underline
-
- > .is-bot
- margin 0 0.5em 0 0
- padding 1px 6px
- font-size 12px
- color #aaa
- border solid 1px #ddd
- border-radius 3px
-
- > .username
- margin 0 0.5em 0 0
- color #ccc
-
- > .info
- margin-left auto
- font-size 0.9em
-
- > .mobile
- margin-right 6px
- color #c0c0c0
-
- > .created-at
- color #c0c0c0
-
- > .body
-
- > .text
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- font-size 1.1em
- color #717171
-
- >>> .quote
- margin 8px
- padding 6px 12px
- color #aaa
- border-left solid 3px #eee
-
- > .reply
- margin-right 8px
- color #717171
-
- > .rp
- margin-left 4px
- font-style oblique
- color #a0bf46
-
- [data-is-me]:after
- content "you"
- padding 0 4px
- margin-left 4px
- font-size 80%
- color $theme-color-foreground
- background $theme-color
- border-radius 4px
-
- .mk-url-preview
- margin-top 8px
-
- > .channel
- margin 0
-
- > .tags
- margin 4px 0 0 0
-
- > *
- display inline-block
- margin 0 8px 0 0
- padding 2px 8px 2px 16px
- font-size 90%
- color #8d969e
- background #edf0f3
- border-radius 4px
-
- &:before
- content ""
- display block
- position absolute
- top 0
- bottom 0
- left 4px
- width 8px
- height 8px
- margin auto 0
- background #fff
- border-radius 100%
-
- > .media
- > img
- display block
- max-width 100%
-
- > .location
- margin 4px 0
- font-size 12px
- color #ccc
-
- > .map
- width 100%
- height 200px
-
- &:empty
- display none
-
- > .app
- font-size 12px
- color #ccc
-
- > .mk-poll
- font-size 80%
-
- > .repost
- margin 8px 0
-
- > .mk-post-preview
- padding 16px
- border dashed 1px #c0dac6
- border-radius 8px
-
- > footer
- > button
- margin 0
- padding 8px
- background transparent
- border none
- box-shadow none
- font-size 1em
- color #ddd
- cursor pointer
-
- &:not(:last-child)
- margin-right 28px
-
- &:hover
- color #666
-
- > .count
- display inline
- margin 0 0 0 8px
- color #999
-
- &.reacted
- color $theme-color
-
- &.menu
- @media (max-width 350px)
- display none
-
-</style>
-
-<style lang="stylus" module>
-.text
- code
- padding 4px 8px
- margin 0 0.5em
- font-size 80%
- color #525252
- background #f8f8f8
- border-radius 2px
-
- pre > code
- padding 16px
- margin 0
-</style>
diff --git a/src/server/web/app/mobile/views/components/posts.vue b/src/server/web/app/mobile/views/components/posts.vue
deleted file mode 100644
index 4695f1beaa..0000000000
--- a/src/server/web/app/mobile/views/components/posts.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-<template>
-<div class="mk-posts">
- <slot name="head"></slot>
- <slot></slot>
- <template v-for="(post, i) in _posts">
- <mk-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/>
- <p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date">
- <span>%fa:angle-up%{{ post._datetext }}</span>
- <span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span>
- </p>
- </template>
- <footer>
- <slot name="tail"></slot>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- posts: {
- type: Array,
- default: () => []
- }
- },
- computed: {
- _posts(): any[] {
- return (this.posts as any).map(post => {
- const date = new Date(post.createdAt).getDate();
- const month = new Date(post.createdAt).getMonth() + 1;
- post._date = date;
- post._datetext = `${month}月 ${date}日`;
- return post;
- });
- }
- },
- methods: {
- onPostUpdated(i, post) {
- Vue.set((this as any).posts, i, post);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-posts
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- > .init
- padding 64px 0
- text-align center
- color #999
-
- > [data-fa]
- margin-right 4px
-
- > .empty
- margin 0 auto
- padding 32px
- max-width 400px
- text-align center
- color #999
-
- > [data-fa]
- display block
- margin-bottom 16px
- font-size 3em
- color #ccc
-
- > .date
- display block
- margin 0
- line-height 32px
- text-align center
- font-size 0.9em
- color #aaa
- background #fdfdfd
- border-bottom solid 1px #eaeaea
-
- span
- margin 0 16px
-
- [data-fa]
- margin-right 8px
-
- > footer
- text-align center
- border-top solid 1px #eaeaea
- border-bottom-left-radius 4px
- border-bottom-right-radius 4px
-
- &:empty
- display none
-
- > button
- margin 0
- padding 16px
- width 100%
- color $theme-color
- border-radius 0 0 8px 8px
-
- &:disabled
- opacity 0.7
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/sub-post-content.vue b/src/server/web/app/mobile/views/components/sub-post-content.vue
deleted file mode 100644
index b95883de77..0000000000
--- a/src/server/web/app/mobile/views/components/sub-post-content.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<template>
-<div class="mk-sub-post-content">
- <div class="body">
- <a class="reply" v-if="post.replyId">%fa:reply%</a>
- <mk-post-html v-if="post.ast" :ast="post.ast" :i="os.i"/>
- <a class="rp" v-if="post.repostId">RP: ...</a>
- </div>
- <details v-if="post.media">
- <summary>({{ post.media.length }}個のメディア)</summary>
- <mk-media-list :media-list="post.media"/>
- </details>
- <details v-if="post.poll">
- <summary>%i18n:mobile.tags.mk-sub-post-content.poll%</summary>
- <mk-poll :post="post"/>
- </details>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['post']
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-sub-post-content
- overflow-wrap break-word
-
- > .body
- > .reply
- margin-right 6px
- color #717171
-
- > .rp
- margin-left 4px
- font-style oblique
- color #a0bf46
-
- mk-poll
- font-size 80%
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/timeline.vue b/src/server/web/app/mobile/views/components/timeline.vue
deleted file mode 100644
index 7b5948faf1..0000000000
--- a/src/server/web/app/mobile/views/components/timeline.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-<template>
-<div class="mk-timeline">
- <mk-friends-maker v-if="alone"/>
- <mk-posts :posts="posts">
- <div class="init" v-if="fetching">
- %fa:spinner .pulse%%i18n:common.loading%
- </div>
- <div class="empty" v-if="!fetching && posts.length == 0">
- %fa:R comments%
- %i18n:mobile.tags.mk-home-timeline.empty-timeline%
- </div>
- <button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail">
- <span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span>
- <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
- </button>
- </mk-posts>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-const limit = 10;
-
-export default Vue.extend({
- props: {
- date: {
- type: Date,
- required: false
- }
- },
- data() {
- return {
- fetching: true,
- moreFetching: false,
- posts: [],
- existMore: false,
- connection: null,
- connectionId: null
- };
- },
- computed: {
- alone(): boolean {
- return (this as any).os.i.followingCount == 0;
- }
- },
- mounted() {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('post', this.onPost);
- this.connection.on('follow', this.onChangeFollowing);
- this.connection.on('unfollow', this.onChangeFollowing);
-
- this.fetch();
- },
- beforeDestroy() {
- this.connection.off('post', this.onPost);
- this.connection.off('follow', this.onChangeFollowing);
- this.connection.off('unfollow', this.onChangeFollowing);
- (this as any).os.stream.dispose(this.connectionId);
- },
- methods: {
- fetch(cb?) {
- this.fetching = true;
- (this as any).api('posts/timeline', {
- limit: limit + 1,
- untilDate: this.date ? (this.date as any).getTime() : undefined
- }).then(posts => {
- if (posts.length == limit + 1) {
- posts.pop();
- this.existMore = true;
- }
- this.posts = posts;
- this.fetching = false;
- this.$emit('loaded');
- if (cb) cb();
- });
- },
- more() {
- this.moreFetching = true;
- (this as any).api('posts/timeline', {
- limit: limit + 1,
- untilId: this.posts[this.posts.length - 1].id
- }).then(posts => {
- if (posts.length == limit + 1) {
- posts.pop();
- this.existMore = true;
- } else {
- this.existMore = false;
- }
- this.posts = this.posts.concat(posts);
- this.moreFetching = false;
- });
- },
- onPost(post) {
- this.posts.unshift(post);
- },
- onChangeFollowing() {
- this.fetch();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-friends-maker
- margin-bottom 8px
-</style>
diff --git a/src/server/web/app/mobile/views/components/ui.header.vue b/src/server/web/app/mobile/views/components/ui.header.vue
deleted file mode 100644
index 2bf47a90a9..0000000000
--- a/src/server/web/app/mobile/views/components/ui.header.vue
+++ /dev/null
@@ -1,242 +0,0 @@
-<template>
-<div class="header">
- <mk-special-message/>
- <div class="main" ref="main">
- <div class="backdrop"></div>
- <p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p>
- <div class="content" ref="mainContainer">
- <button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button>
- <template v-if="hasUnreadNotifications || hasUnreadMessagingMessages || hasGameInvitations">%fa:circle%</template>
- <h1>
- <slot>Misskey</slot>
- </h1>
- <slot name="func"></slot>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as anime from 'animejs';
-
-export default Vue.extend({
- props: ['func'],
- data() {
- return {
- hasUnreadNotifications: false,
- hasUnreadMessagingMessages: false,
- hasGameInvitations: false,
- connection: null,
- connectionId: null
- };
- },
- mounted() {
- if ((this as any).os.isSignedIn) {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('read_all_notifications', this.onReadAllNotifications);
- this.connection.on('unread_notification', this.onUnreadNotification);
- this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
- this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
- this.connection.on('othello_invited', this.onOthelloInvited);
- this.connection.on('othello_no_invites', this.onOthelloNoInvites);
-
- // Fetch count of unread notifications
- (this as any).api('notifications/get_unread_count').then(res => {
- if (res.count > 0) {
- this.hasUnreadNotifications = true;
- }
- });
-
- // Fetch count of unread messaging messages
- (this as any).api('messaging/unread').then(res => {
- if (res.count > 0) {
- this.hasUnreadMessagingMessages = true;
- }
- });
-
- const ago = (new Date().getTime() - new Date((this as any).os.i.account.lastUsedAt).getTime()) / 1000
- const isHisasiburi = ago >= 3600;
- (this as any).os.i.account.lastUsedAt = new Date();
- if (isHisasiburi) {
- (this.$refs.welcomeback as any).style.display = 'block';
- (this.$refs.main as any).style.overflow = 'hidden';
-
- anime({
- targets: this.$refs.welcomeback,
- top: '0',
- opacity: 1,
- delay: 1000,
- duration: 500,
- easing: 'easeOutQuad'
- });
-
- anime({
- targets: this.$refs.mainContainer,
- opacity: 0,
- delay: 1000,
- duration: 500,
- easing: 'easeOutQuad'
- });
-
- setTimeout(() => {
- anime({
- targets: this.$refs.welcomeback,
- top: '-48px',
- opacity: 0,
- duration: 500,
- complete: () => {
- (this.$refs.welcomeback as any).style.display = 'none';
- (this.$refs.main as any).style.overflow = 'initial';
- },
- easing: 'easeInQuad'
- });
-
- anime({
- targets: this.$refs.mainContainer,
- opacity: 1,
- duration: 500,
- easing: 'easeInQuad'
- });
- }, 2500);
- }
- }
- },
- beforeDestroy() {
- if ((this as any).os.isSignedIn) {
- this.connection.off('read_all_notifications', this.onReadAllNotifications);
- this.connection.off('unread_notification', this.onUnreadNotification);
- this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
- this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
- this.connection.off('othello_invited', this.onOthelloInvited);
- this.connection.off('othello_no_invites', this.onOthelloNoInvites);
- (this as any).os.stream.dispose(this.connectionId);
- }
- },
- methods: {
- onReadAllNotifications() {
- this.hasUnreadNotifications = false;
- },
- onUnreadNotification() {
- this.hasUnreadNotifications = true;
- },
- onReadAllMessagingMessages() {
- this.hasUnreadMessagingMessages = false;
- },
- onUnreadMessagingMessage() {
- this.hasUnreadMessagingMessages = true;
- },
- onOthelloInvited() {
- this.hasGameInvitations = true;
- },
- onOthelloNoInvites() {
- this.hasGameInvitations = false;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.header
- $height = 48px
-
- position fixed
- top 0
- z-index 1024
- width 100%
- box-shadow 0 1px 0 rgba(#000, 0.075)
-
- > .main
- color rgba(#fff, 0.9)
-
- > .backdrop
- position absolute
- top 0
- z-index 1000
- width 100%
- height $height
- -webkit-backdrop-filter blur(12px)
- backdrop-filter blur(12px)
- //background-color rgba(#1b2023, 0.75)
- background-color #1b2023
-
- > p
- display none
- position absolute
- z-index 1002
- top $height
- width 100%
- line-height $height
- margin 0
- text-align center
- color #fff
- opacity 0
-
- > .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
-
- [data-fa], [data-icon]
- margin-right 4px
-
- > 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-fa]
- transition all 0.2s ease
-
- > [data-fa].circle
- position absolute
- top 8px
- left 8px
- pointer-events none
- font-size 10px
- color $theme-color
-
- > 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/server/web/app/mobile/views/components/ui.nav.vue b/src/server/web/app/mobile/views/components/ui.nav.vue
deleted file mode 100644
index a923774a73..0000000000
--- a/src/server/web/app/mobile/views/components/ui.nav.vue
+++ /dev/null
@@ -1,244 +0,0 @@
-<template>
-<div class="nav">
- <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" v-if="isOpen">
- <router-link class="me" v-if="os.isSignedIn" :to="`/@${os.i.username}`">
- <img class="avatar" :src="`${os.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/>
- <p class="name">{{ os.i.name }}</p>
- </router-link>
- <div class="links">
- <ul>
- <li><router-link to="/">%fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%</router-link></li>
- <li><router-link to="/i/notifications">%fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%<template v-if="hasUnreadNotifications">%fa:circle%</template>%fa:angle-right%</router-link></li>
- <li><router-link to="/i/messaging">%fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>%fa:angle-right%</router-link></li>
- <li><router-link to="/othello">%fa:gamepad%ゲーム<template v-if="hasGameInvitations">%fa:circle%</template>%fa:angle-right%</router-link></li>
- </ul>
- <ul>
- <li><a :href="chUrl" target="_blank">%fa:tv%%i18n:mobile.tags.mk-ui-nav.ch%%fa:angle-right%</a></li>
- <li><router-link to="/i/drive">%fa:cloud%%i18n:mobile.tags.mk-ui-nav.drive%%fa:angle-right%</router-link></li>
- </ul>
- <ul>
- <li><a @click="search">%fa:search%%i18n:mobile.tags.mk-ui-nav.search%%fa:angle-right%</a></li>
- </ul>
- <ul>
- <li><router-link to="/i/settings">%fa:cog%%i18n:mobile.tags.mk-ui-nav.settings%%fa:angle-right%</router-link></li>
- </ul>
- </div>
- <a :href="aboutUrl"><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
- </div>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { docsUrl, chUrl, lang } from '../../../config';
-
-export default Vue.extend({
- props: ['isOpen'],
- data() {
- return {
- hasUnreadNotifications: false,
- hasUnreadMessagingMessages: false,
- hasGameInvitations: false,
- connection: null,
- connectionId: null,
- aboutUrl: `${docsUrl}/${lang}/about`,
- chUrl
- };
- },
- mounted() {
- if ((this as any).os.isSignedIn) {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('read_all_notifications', this.onReadAllNotifications);
- this.connection.on('unread_notification', this.onUnreadNotification);
- this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
- this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
- this.connection.on('othello_invited', this.onOthelloInvited);
- this.connection.on('othello_no_invites', this.onOthelloNoInvites);
-
- // Fetch count of unread notifications
- (this as any).api('notifications/get_unread_count').then(res => {
- if (res.count > 0) {
- this.hasUnreadNotifications = true;
- }
- });
-
- // Fetch count of unread messaging messages
- (this as any).api('messaging/unread').then(res => {
- if (res.count > 0) {
- this.hasUnreadMessagingMessages = true;
- }
- });
- }
- },
- beforeDestroy() {
- if ((this as any).os.isSignedIn) {
- this.connection.off('read_all_notifications', this.onReadAllNotifications);
- this.connection.off('unread_notification', this.onUnreadNotification);
- this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
- this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
- this.connection.off('othello_invited', this.onOthelloInvited);
- this.connection.off('othello_no_invites', this.onOthelloNoInvites);
- (this as any).os.stream.dispose(this.connectionId);
- }
- },
- methods: {
- search() {
- const query = window.prompt('%i18n:mobile.tags.mk-ui-nav.search%');
- if (query == null || query == '') return;
- this.$router.push('/search?q=' + encodeURIComponent(query));
- },
- onReadAllNotifications() {
- this.hasUnreadNotifications = false;
- },
- onUnreadNotification() {
- this.hasUnreadNotifications = true;
- },
- onReadAllMessagingMessages() {
- this.hasUnreadMessagingMessages = false;
- },
- onUnreadMessagingMessage() {
- this.hasUnreadMessagingMessages = true;
- },
- onOthelloInvited() {
- this.hasGameInvitations = true;
- },
- onOthelloNoInvites() {
- this.hasGameInvitations = false;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.nav
- .backdrop
- position fixed
- top 0
- left 0
- z-index 1025
- width 100%
- height 100%
- background rgba(0, 0, 0, 0.2)
-
- .body
- position fixed
- top 0
- left 0
- z-index 1026
- width 240px
- height 100%
- overflow auto
- -webkit-overflow-scrolling touch
- color #777
- background #fff
-
- .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 #777
- 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
-
- li
- display block
- font-size 1em
- line-height 1em
-
- a
- display block
- padding 0 20px
- line-height 3rem
- line-height calc(1rem + 30px)
- color #777
- text-decoration none
-
- > [data-fa]:first-child
- margin-right 0.5em
-
- > [data-fa].circle
- margin-left 6px
- font-size 10px
- color $theme-color
-
- > [data-fa]:last-child
- position absolute
- top 0
- right 0
- padding 0 20px
- font-size 1.2em
- line-height calc(1rem + 30px)
- color #ccc
-
- .about
- margin 0
- padding 1em 0
- text-align center
- font-size 0.8em
- opacity 0.5
-
- a
- color #777
-
-.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/server/web/app/mobile/views/components/ui.vue b/src/server/web/app/mobile/views/components/ui.vue
deleted file mode 100644
index 325ce9d40e..0000000000
--- a/src/server/web/app/mobile/views/components/ui.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<template>
-<div class="mk-ui">
- <x-header>
- <template slot="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="os.isSignedIn"/>
-</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 {
- isDrawerOpening: false,
- connection: null,
- connectionId: null
- };
- },
- mounted() {
- if ((this as any).os.isSignedIn) {
- this.connection = (this as any).os.stream.getConnection();
- this.connectionId = (this as any).os.stream.use();
-
- this.connection.on('notification', this.onNotification);
- }
- },
- beforeDestroy() {
- if ((this as any).os.isSignedIn) {
- this.connection.off('notification', this.onNotification);
- (this as any).os.stream.dispose(this.connectionId);
- }
- },
- methods: {
- onNotification(notification) {
- // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
- this.connection.send({
- type: 'read_notification',
- id: notification.id
- });
-
- (this as any).os.new(MkNotify, {
- notification
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-ui
- display flex
- flex 1
- flex-direction column
- padding-top 48px
-
- > .content
- display flex
- flex 1
- flex-direction column
-</style>
diff --git a/src/server/web/app/mobile/views/components/user-card.vue b/src/server/web/app/mobile/views/components/user-card.vue
deleted file mode 100644
index ffa1100519..0000000000
--- a/src/server/web/app/mobile/views/components/user-card.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-<template>
-<div class="mk-user-card">
- <header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''">
- <a :href="`/@${acct}`">
- <img :src="`${user.avatarUrl}?thumbnail&size=200`" alt="avatar"/>
- </a>
- </header>
- <a class="name" :href="`/@${acct}`" target="_blank">{{ user.name }}</a>
- <p class="username">@{{ acct }}</p>
- <mk-follow-button :user="user"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['user'],
- computed: {
- acct() {
- return getAcct(this.user);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-user-card
- display inline-block
- width 200px
- text-align center
- border-radius 8px
- background #fff
-
- > header
- display block
- height 80px
- background-color #ddd
- background-size cover
- background-position center
- border-radius 8px 8px 0 0
-
- > a
- > img
- position absolute
- top 20px
- left calc(50% - 40px)
- width 80px
- height 80px
- border solid 2px #fff
- border-radius 8px
-
- > .name
- display block
- margin 24px 0 0 0
- font-size 16px
- color #555
-
- > .username
- margin 0
- font-size 15px
- color #ccc
-
- > .mk-follow-button
- display inline-block
- margin 8px 0 16px 0
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/user-preview.vue b/src/server/web/app/mobile/views/components/user-preview.vue
deleted file mode 100644
index e51e4353d3..0000000000
--- a/src/server/web/app/mobile/views/components/user-preview.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<template>
-<div class="mk-user-preview">
- <router-link class="avatar-anchor" :to="`/@${acct}`">
- <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
- </router-link>
- <div class="main">
- <header>
- <router-link class="name" :to="`/@${acct}`">{{ user.name }}</router-link>
- <span class="username">@{{ acct }}</span>
- </header>
- <div class="body">
- <div class="description">{{ user.description }}</div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['user'],
- computed: {
- acct() {
- return getAcct(this.user);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-user-preview
- margin 0
- padding 16px
- font-size 12px
-
- @media (min-width 350px)
- font-size 14px
-
- @media (min-width 500px)
- font-size 16px
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar-anchor
- display block
- float left
- margin 0 10px 0 0
-
- @media (min-width 500px)
- margin-right 16px
-
- > .avatar
- display block
- width 48px
- height 48px
- margin 0
- border-radius 6px
- vertical-align bottom
-
- @media (min-width 500px)
- width 58px
- height 58px
- border-radius 8px
-
- > .main
- float left
- width calc(100% - 58px)
-
- @media (min-width 500px)
- width calc(100% - 74px)
-
- > header
- @media (min-width 500px)
- margin-bottom 2px
-
- > .name
- display inline
- margin 0
- padding 0
- color #777
- font-size 1em
- font-weight 700
- text-align left
- text-decoration none
-
- &:hover
- text-decoration underline
-
- > .username
- text-align left
- margin 0 0 0 8px
- color #ccc
-
- > .body
-
- > .description
- cursor default
- display block
- margin 0
- padding 0
- overflow-wrap break-word
- font-size 1.1em
- color #717171
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/user-timeline.vue b/src/server/web/app/mobile/views/components/user-timeline.vue
deleted file mode 100644
index bd3e3d0c87..0000000000
--- a/src/server/web/app/mobile/views/components/user-timeline.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-<div class="mk-user-timeline">
- <mk-posts :posts="posts">
- <div class="init" v-if="fetching">
- %fa:spinner .pulse%%i18n:common.loading%
- </div>
- <div class="empty" v-if="!fetching && posts.length == 0">
- %fa:R comments%
- {{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-posts-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-posts%' }}
- </div>
- <button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail">
- <span v-if="!moreFetching">%i18n:mobile.tags.mk-user-timeline.load-more%</span>
- <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
- </button>
- </mk-posts>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-const limit = 10;
-
-export default Vue.extend({
- props: ['user', 'withMedia'],
- data() {
- return {
- fetching: true,
- posts: [],
- existMore: false,
- moreFetching: false
- };
- },
- mounted() {
- (this as any).api('users/posts', {
- userId: this.user.id,
- withMedia: this.withMedia,
- limit: limit + 1
- }).then(posts => {
- if (posts.length == limit + 1) {
- posts.pop();
- this.existMore = true;
- }
- this.posts = posts;
- this.fetching = false;
- this.$emit('loaded');
- });
- },
- methods: {
- more() {
- this.moreFetching = true;
- (this as any).api('users/posts', {
- userId: this.user.id,
- withMedia: this.withMedia,
- limit: limit + 1,
- untilId: this.posts[this.posts.length - 1].id
- }).then(posts => {
- if (posts.length == limit + 1) {
- posts.pop();
- this.existMore = true;
- } else {
- this.existMore = false;
- }
- this.posts = this.posts.concat(posts);
- this.moreFetching = false;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-user-timeline
- max-width 600px
- margin 0 auto
-</style>
diff --git a/src/server/web/app/mobile/views/components/users-list.vue b/src/server/web/app/mobile/views/components/users-list.vue
deleted file mode 100644
index b11e4549d6..0000000000
--- a/src/server/web/app/mobile/views/components/users-list.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-<template>
-<div class="mk-users-list">
- <nav>
- <span :data-is-active="mode == 'all'" @click="mode = 'all'">%i18n:mobile.tags.mk-users-list.all%<span>{{ count }}</span></span>
- <span v-if="os.isSignedIn && youKnowCount" :data-is-active="mode == 'iknow'" @click="mode = 'iknow'">%i18n:mobile.tags.mk-users-list.known%<span>{{ youKnowCount }}</span></span>
- </nav>
- <div class="users" v-if="!fetching && users.length != 0">
- <mk-user-preview v-for="u in users" :user="u" :key="u.id"/>
- </div>
- <button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching">
- <span v-if="!moreFetching">%i18n:mobile.tags.mk-users-list.load-more%</span>
- <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
- </button>
- <p class="no" v-if="!fetching && users.length == 0">
- <slot></slot>
- </p>
- <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['fetch', 'count', 'youKnowCount'],
- data() {
- return {
- limit: 30,
- mode: 'all',
- fetching: true,
- moreFetching: false,
- users: [],
- next: null
- };
- },
- watch: {
- mode() {
- this._fetch();
- }
- },
- mounted() {
- this._fetch(() => {
- this.$emit('loaded');
- });
- },
- methods: {
- _fetch(cb?) {
- this.fetching = true;
- this.fetch(this.mode == 'iknow', this.limit, null, obj => {
- this.users = obj.users;
- this.next = obj.next;
- this.fetching = false;
- if (cb) cb();
- });
- },
- more() {
- this.moreFetching = true;
- this.fetch(this.mode == 'iknow', this.limit, this.next, obj => {
- this.moreFetching = false;
- this.users = this.users.concat(obj.users);
- this.next = obj.next;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-users-list
-
- > nav
- display flex
- justify-content center
- margin 0 auto
- max-width 600px
- border-bottom solid 1px rgba(0, 0, 0, 0.2)
-
- > span
- display block
- flex 1 1
- text-align center
- line-height 52px
- font-size 14px
- color #657786
- border-bottom solid 2px transparent
-
- &[data-is-active]
- font-weight bold
- color $theme-color
- border-color $theme-color
-
- > span
- display inline-block
- margin-left 4px
- padding 2px 5px
- font-size 12px
- line-height 1
- color #fff
- background rgba(0, 0, 0, 0.3)
- border-radius 20px
-
- > .users
- margin 8px auto
- max-width 500px
- width calc(100% - 16px)
- background #fff
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
-
- @media (min-width 500px)
- margin 16px auto
- width calc(100% - 32px)
-
- > *
- border-bottom solid 1px rgba(0, 0, 0, 0.05)
-
- > .no
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > .fetching
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > [data-fa]
- margin-right 4px
-
-</style>
diff --git a/src/server/web/app/mobile/views/components/widget-container.vue b/src/server/web/app/mobile/views/components/widget-container.vue
deleted file mode 100644
index 7319c90849..0000000000
--- a/src/server/web/app/mobile/views/components/widget-container.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<template>
-<div class="mk-widget-container" :class="{ naked, hideHeader: !showHeader }">
- <header v-if="showHeader">
- <div class="title"><slot name="header"></slot></div>
- <slot name="func"></slot>
- </header>
- <slot></slot>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: {
- showHeader: {
- type: Boolean,
- default: true
- },
- naked: {
- type: Boolean,
- default: false
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-widget-container
- background #eee
- border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
- overflow hidden
-
- &.hideHeader
- background #fff
-
- &.naked
- background transparent !important
- box-shadow none !important
-
- > header
- > .title
- margin 0
- padding 8px 10px
- font-size 15px
- font-weight normal
- color #465258
- background #fff
- border-radius 8px 8px 0 0
-
- > [data-fa]
- 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 #465258
-
-</style>