summaryrefslogtreecommitdiff
path: root/src/server/web/app/common/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/common/views/components
parentUpdate .travis.yml (diff)
downloadsharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.gz
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.bz2
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.zip
整理した
Diffstat (limited to 'src/server/web/app/common/views/components')
-rw-r--r--src/server/web/app/common/views/components/autocomplete.vue306
-rw-r--r--src/server/web/app/common/views/components/connect-failed.troubleshooter.vue137
-rw-r--r--src/server/web/app/common/views/components/connect-failed.vue106
-rw-r--r--src/server/web/app/common/views/components/ellipsis.vue26
-rw-r--r--src/server/web/app/common/views/components/file-type-icon.vue17
-rw-r--r--src/server/web/app/common/views/components/forkit.vue42
-rw-r--r--src/server/web/app/common/views/components/index.ts51
-rw-r--r--src/server/web/app/common/views/components/media-list.vue57
-rw-r--r--src/server/web/app/common/views/components/messaging-room.form.vue305
-rw-r--r--src/server/web/app/common/views/components/messaging-room.message.vue263
-rw-r--r--src/server/web/app/common/views/components/messaging-room.vue377
-rw-r--r--src/server/web/app/common/views/components/messaging.vue463
-rw-r--r--src/server/web/app/common/views/components/nav.vue41
-rw-r--r--src/server/web/app/common/views/components/othello.game.vue324
-rw-r--r--src/server/web/app/common/views/components/othello.gameroom.vue42
-rw-r--r--src/server/web/app/common/views/components/othello.room.vue297
-rw-r--r--src/server/web/app/common/views/components/othello.vue311
-rw-r--r--src/server/web/app/common/views/components/poll-editor.vue142
-rw-r--r--src/server/web/app/common/views/components/poll.vue124
-rw-r--r--src/server/web/app/common/views/components/post-html.ts137
-rw-r--r--src/server/web/app/common/views/components/post-menu.vue141
-rw-r--r--src/server/web/app/common/views/components/reaction-icon.vue28
-rw-r--r--src/server/web/app/common/views/components/reaction-picker.vue191
-rw-r--r--src/server/web/app/common/views/components/reactions-viewer.vue49
-rw-r--r--src/server/web/app/common/views/components/signin.vue142
-rw-r--r--src/server/web/app/common/views/components/signup.vue287
-rw-r--r--src/server/web/app/common/views/components/special-message.vue42
-rw-r--r--src/server/web/app/common/views/components/stream-indicator.vue86
-rw-r--r--src/server/web/app/common/views/components/switch.vue190
-rw-r--r--src/server/web/app/common/views/components/time.vue76
-rw-r--r--src/server/web/app/common/views/components/timer.vue49
-rw-r--r--src/server/web/app/common/views/components/twitter-setting.vue66
-rw-r--r--src/server/web/app/common/views/components/uploader.vue212
-rw-r--r--src/server/web/app/common/views/components/url-preview.vue142
-rw-r--r--src/server/web/app/common/views/components/url.vue66
-rw-r--r--src/server/web/app/common/views/components/welcome-timeline.vue118
36 files changed, 0 insertions, 5453 deletions
diff --git a/src/server/web/app/common/views/components/autocomplete.vue b/src/server/web/app/common/views/components/autocomplete.vue
deleted file mode 100644
index 79bd2ba023..0000000000
--- a/src/server/web/app/common/views/components/autocomplete.vue
+++ /dev/null
@@ -1,306 +0,0 @@
-<template>
-<div class="mk-autocomplete" @contextmenu.prevent="() => {}">
- <ol class="users" ref="suggests" v-if="users.length > 0">
- <li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
- <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
- <span class="name">{{ user.name }}</span>
- <span class="username">@{{ getAcct(user) }}</span>
- </li>
- </ol>
- <ol class="emojis" ref="suggests" v-if="emojis.length > 0">
- <li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
- <span class="emoji">{{ emoji.emoji }}</span>
- <span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
- <span class="alias" v-if="emoji.alias">({{ emoji.alias }})</span>
- </li>
- </ol>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as emojilib from 'emojilib';
-import contains from '../../../common/scripts/contains';
-import getAcct from '../../../../../common/user/get-acct';
-
-const lib = Object.entries(emojilib.lib).filter((x: any) => {
- return x[1].category != 'flags';
-});
-const emjdb = lib.map((x: any) => ({
- emoji: x[1].char,
- name: x[0],
- alias: null
-}));
-lib.forEach((x: any) => {
- if (x[1].keywords) {
- x[1].keywords.forEach(k => {
- emjdb.push({
- emoji: x[1].char,
- name: k,
- alias: x[0]
- });
- });
- }
-});
-emjdb.sort((a, b) => a.name.length - b.name.length);
-
-export default Vue.extend({
- props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'],
- data() {
- return {
- fetching: true,
- users: [],
- emojis: [],
- select: -1,
- emojilib
- }
- },
- computed: {
- items(): HTMLCollection {
- return (this.$refs.suggests as Element).children;
- }
- },
- updated() {
- //#region 位置調整
- const margin = 32;
-
- if (this.x + this.$el.offsetWidth > window.innerWidth - margin) {
- this.$el.style.left = (this.x - this.$el.offsetWidth) + 'px';
- this.$el.style.marginLeft = '-16px';
- } else {
- this.$el.style.left = this.x + 'px';
- this.$el.style.marginLeft = '0';
- }
-
- if (this.y + this.$el.offsetHeight > window.innerHeight - margin) {
- this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
- this.$el.style.marginTop = '0';
- } else {
- this.$el.style.top = this.y + 'px';
- this.$el.style.marginTop = 'calc(1em + 8px)';
- }
- //#endregion
- },
- mounted() {
- this.textarea.addEventListener('keydown', this.onKeydown);
-
- Array.from(document.querySelectorAll('body *')).forEach(el => {
- el.addEventListener('mousedown', this.onMousedown);
- });
-
- this.$nextTick(() => {
- this.exec();
-
- this.$watch('q', () => {
- this.$nextTick(() => {
- this.exec();
- });
- });
- });
- },
- beforeDestroy() {
- this.textarea.removeEventListener('keydown', this.onKeydown);
-
- Array.from(document.querySelectorAll('body *')).forEach(el => {
- el.removeEventListener('mousedown', this.onMousedown);
- });
- },
- methods: {
- getAcct,
- exec() {
- this.select = -1;
- if (this.$refs.suggests) {
- Array.from(this.items).forEach(el => {
- el.removeAttribute('data-selected');
- });
- }
-
- if (this.type == 'user') {
- const cache = sessionStorage.getItem(this.q);
- if (cache) {
- const users = JSON.parse(cache);
- this.users = users;
- this.fetching = false;
- } else {
- (this as any).api('users/search_by_username', {
- query: this.q,
- limit: 30
- }).then(users => {
- this.users = users;
- this.fetching = false;
-
- // キャッシュ
- sessionStorage.setItem(this.q, JSON.stringify(users));
- });
- }
- } else if (this.type == 'emoji') {
- const matched = [];
- emjdb.some(x => {
- if (x.name.indexOf(this.q) == 0 && !x.alias && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
- return matched.length == 30;
- });
- if (matched.length < 30) {
- emjdb.some(x => {
- if (x.name.indexOf(this.q) == 0 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
- return matched.length == 30;
- });
- }
- if (matched.length < 30) {
- emjdb.some(x => {
- if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
- return matched.length == 30;
- });
- }
- this.emojis = matched;
- }
- },
-
- onMousedown(e) {
- if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
- },
-
- onKeydown(e) {
- const cancel = () => {
- e.preventDefault();
- e.stopPropagation();
- };
-
- switch (e.which) {
- case 10: // [ENTER]
- case 13: // [ENTER]
- if (this.select !== -1) {
- cancel();
- (this.items[this.select] as any).click();
- } else {
- this.close();
- }
- break;
-
- case 27: // [ESC]
- cancel();
- this.close();
- break;
-
- case 38: // [↑]
- if (this.select !== -1) {
- cancel();
- this.selectPrev();
- } else {
- this.close();
- }
- break;
-
- case 9: // [TAB]
- case 40: // [↓]
- cancel();
- this.selectNext();
- break;
-
- default:
- e.stopPropagation();
- this.textarea.focus();
- }
- },
-
- selectNext() {
- if (++this.select >= this.items.length) this.select = 0;
- this.applySelect();
- },
-
- selectPrev() {
- if (--this.select < 0) this.select = this.items.length - 1;
- this.applySelect();
- },
-
- applySelect() {
- Array.from(this.items).forEach(el => {
- el.removeAttribute('data-selected');
- });
-
- this.items[this.select].setAttribute('data-selected', 'true');
- (this.items[this.select] as any).focus();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-autocomplete
- position fixed
- z-index 65535
- margin-top calc(1em + 8px)
- overflow hidden
- background #fff
- border solid 1px rgba(0, 0, 0, 0.1)
- border-radius 4px
- transition top 0.1s ease, left 0.1s ease
-
- > ol
- display block
- margin 0
- padding 4px 0
- max-height 190px
- max-width 500px
- overflow auto
- list-style none
-
- > li
- display block
- padding 4px 12px
- white-space nowrap
- overflow hidden
- font-size 0.9em
- color rgba(0, 0, 0, 0.8)
- cursor default
-
- &, *
- user-select none
-
- &:hover
- &[data-selected='true']
- background $theme-color
-
- &, *
- color #fff !important
-
- &:active
- background darken($theme-color, 10%)
-
- &, *
- color #fff !important
-
- > .users > li
-
- .avatar
- vertical-align middle
- min-width 28px
- min-height 28px
- max-width 28px
- max-height 28px
- margin 0 8px 0 0
- border-radius 100%
-
- .name
- margin 0 8px 0 0
- color rgba(0, 0, 0, 0.8)
-
- .username
- color rgba(0, 0, 0, 0.3)
-
- > .emojis > li
-
- .emoji
- display inline-block
- margin 0 4px 0 0
- width 24px
-
- .name
- color rgba(0, 0, 0, 0.8)
-
- .alias
- margin 0 0 0 8px
- color rgba(0, 0, 0, 0.3)
-
-</style>
diff --git a/src/server/web/app/common/views/components/connect-failed.troubleshooter.vue b/src/server/web/app/common/views/components/connect-failed.troubleshooter.vue
deleted file mode 100644
index cadbd36ba4..0000000000
--- a/src/server/web/app/common/views/components/connect-failed.troubleshooter.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-<template>
-<div class="troubleshooter">
- <h1>%fa:wrench%%i18n:common.tags.mk-error.troubleshooter.title%</h1>
- <div>
- <p :data-wip="network == null">
- <template v-if="network != null">
- <template v-if="network">%fa:check%</template>
- <template v-if="!network">%fa:times%</template>
- </template>
- {{ network == null ? '%i18n:common.tags.mk-error.troubleshooter.checking-network%' : '%i18n:common.tags.mk-error.troubleshooter.network%' }}<mk-ellipsis v-if="network == null"/>
- </p>
- <p v-if="network == true" :data-wip="internet == null">
- <template v-if="internet != null">
- <template v-if="internet">%fa:check%</template>
- <template v-if="!internet">%fa:times%</template>
- </template>
- {{ internet == null ? '%i18n:common.tags.mk-error.troubleshooter.checking-internet%' : '%i18n:common.tags.mk-error.troubleshooter.internet%' }}<mk-ellipsis v-if="internet == null"/>
- </p>
- <p v-if="internet == true" :data-wip="server == null">
- <template v-if="server != null">
- <template v-if="server">%fa:check%</template>
- <template v-if="!server">%fa:times%</template>
- </template>
- {{ server == null ? '%i18n:common.tags.mk-error.troubleshooter.checking-server%' : '%i18n:common.tags.mk-error.troubleshooter.server%' }}<mk-ellipsis v-if="server == null"/>
- </p>
- </div>
- <p v-if="!end">%i18n:common.tags.mk-error.troubleshooter.finding%<mk-ellipsis/></p>
- <p v-if="network === false"><b>%fa:exclamation-triangle%%i18n:common.tags.mk-error.troubleshooter.no-network%</b><br>%i18n:common.tags.mk-error.troubleshooter.no-network-desc%</p>
- <p v-if="internet === false"><b>%fa:exclamation-triangle%%i18n:common.tags.mk-error.troubleshooter.no-internet%</b><br>%i18n:common.tags.mk-error.troubleshooter.no-internet-desc%</p>
- <p v-if="server === false"><b>%fa:exclamation-triangle%%i18n:common.tags.mk-error.troubleshooter.no-server%</b><br>%i18n:common.tags.mk-error.troubleshooter.no-server-desc%</p>
- <p v-if="server === true" class="success"><b>%fa:info-circle%%i18n:common.tags.mk-error.troubleshooter.success%</b><br>%i18n:common.tags.mk-error.troubleshooter.success-desc%</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { apiUrl } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- network: navigator.onLine,
- end: false,
- internet: null,
- server: null
- };
- },
- mounted() {
- if (!this.network) {
- this.end = true;
- return;
- }
-
- // Check internet connection
- fetch('https://google.com?rand=' + Math.random(), {
- mode: 'no-cors'
- }).then(() => {
- this.internet = true;
-
- // Check misskey server is available
- fetch(`${apiUrl}/meta`).then(() => {
- this.end = true;
- this.server = true;
- })
- .catch(() => {
- this.end = true;
- this.server = false;
- });
- })
- .catch(() => {
- this.end = true;
- this.internet = false;
- });
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.troubleshooter
- width 100%
- max-width 500px
- text-align left
- background #fff
- border-radius 8px
- border solid 1px #ddd
-
- > h1
- margin 0
- padding 0.6em 1.2em
- font-size 1em
- color #444
- border-bottom solid 1px #eee
-
- > [data-fa]
- margin-right 0.25em
-
- > div
- overflow hidden
- padding 0.6em 1.2em
-
- > p
- margin 0.5em 0
- font-size 0.9em
- color #444
-
- &[data-wip]
- color #888
-
- > [data-fa]
- margin-right 0.25em
-
- &.times
- color #e03524
-
- &.check
- color #84c32f
-
- > p
- margin 0
- padding 0.7em 1.2em
- font-size 1em
- color #444
- border-top solid 1px #eee
-
- > b
- > [data-fa]
- margin-right 0.25em
-
- &.success
- > b
- color #39adad
-
- &:not(.success)
- > b
- color #ad4339
-
-</style>
diff --git a/src/server/web/app/common/views/components/connect-failed.vue b/src/server/web/app/common/views/components/connect-failed.vue
deleted file mode 100644
index 185250dbd8..0000000000
--- a/src/server/web/app/common/views/components/connect-failed.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-<div class="mk-connect-failed">
- <img src="data:image/jpeg;base64,%base64:/assets/error.jpg%" alt=""/>
- <h1>%i18n:common.tags.mk-error.title%</h1>
- <p class="text">
- {{ '%i18n:common.tags.mk-error.description%'.substr(0, '%i18n:common.tags.mk-error.description%'.indexOf('{')) }}
- <a @click="reload">{{ '%i18n:common.tags.mk-error.description%'.match(/\{(.+?)\}/)[1] }}</a>
- {{ '%i18n:common.tags.mk-error.description%'.substr('%i18n:common.tags.mk-error.description%'.indexOf('}') + 1) }}
- </p>
- <button v-if="!troubleshooting" @click="troubleshooting = true">%i18n:common.tags.mk-error.troubleshoot%</button>
- <x-troubleshooter v-if="troubleshooting"/>
- <p class="thanks">%i18n:common.tags.mk-error.thanks%</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import XTroubleshooter from './connect-failed.troubleshooter.vue';
-
-export default Vue.extend({
- components: {
- XTroubleshooter
- },
- data() {
- return {
- troubleshooting: false
- };
- },
- mounted() {
- document.title = 'Oops!';
- document.documentElement.style.background = '#f8f8f8';
- },
- methods: {
- reload() {
- location.reload();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-connect-failed
- width 100%
- padding 32px 18px
- text-align center
-
- > img
- display block
- height 200px
- margin 0 auto
- pointer-events none
- user-select none
-
- > h1
- display block
- margin 1.25em auto 0.65em auto
- font-size 1.5em
- color #555
-
- > .text
- display block
- margin 0 auto
- max-width 600px
- font-size 1em
- color #666
-
- > button
- display block
- margin 1em auto 0 auto
- padding 8px 10px
- color $theme-color-foreground
- background $theme-color
-
- &:focus
- outline solid 3px rgba($theme-color, 0.3)
-
- &:hover
- background lighten($theme-color, 10%)
-
- &:active
- background darken($theme-color, 10%)
-
- > .troubleshooter
- margin 1em auto 0 auto
-
- > .thanks
- display block
- margin 2em auto 0 auto
- padding 2em 0 0 0
- max-width 600px
- font-size 0.9em
- font-style oblique
- color #aaa
- border-top solid 1px #eee
-
- @media (max-width 500px)
- padding 24px 18px
- font-size 80%
-
- > img
- height 150px
-
-</style>
-
diff --git a/src/server/web/app/common/views/components/ellipsis.vue b/src/server/web/app/common/views/components/ellipsis.vue
deleted file mode 100644
index 07349902de..0000000000
--- a/src/server/web/app/common/views/components/ellipsis.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<template>
- <span class="mk-ellipsis">
- <span>.</span><span>.</span><span>.</span>
- </span>
-</template>
-
-<style lang="stylus" scoped>
-.mk-ellipsis
- > span
- animation ellipsis 1.4s infinite ease-in-out both
-
- &:nth-child(1)
- animation-delay 0s
-
- &:nth-child(2)
- animation-delay 0.16s
-
- &:nth-child(3)
- animation-delay 0.32s
-
- @keyframes ellipsis
- 0%, 80%, 100%
- opacity 1
- 40%
- opacity 0
-</style>
diff --git a/src/server/web/app/common/views/components/file-type-icon.vue b/src/server/web/app/common/views/components/file-type-icon.vue
deleted file mode 100644
index b7e868d1f7..0000000000
--- a/src/server/web/app/common/views/components/file-type-icon.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<template>
-<span class="mk-file-type-icon">
- <template v-if="kind == 'image'">%fa:file-image%</template>
-</span>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['type'],
- computed: {
- kind(): string {
- return this.type.split('/')[0];
- }
- }
-});
-</script>
diff --git a/src/server/web/app/common/views/components/forkit.vue b/src/server/web/app/common/views/components/forkit.vue
deleted file mode 100644
index 6f334b965a..0000000000
--- a/src/server/web/app/common/views/components/forkit.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<template>
-<a class="a" href="https://github.com/syuilo/misskey" target="_blank" title="%i18n:common.tags.mk-forkit.open-github-link%" aria-label="%i18n:common.tags.mk-forkit.open-github-link%">
- <svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden">
- <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
- <path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path>
- <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor"></path>
- </svg>
-</a>
-</template>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.a
- display block
- position absolute
- top 0
- right 0
-
- > svg
- display block
- //fill #151513
- //color #fff
- fill $theme-color
- color $theme-color-foreground
-
- .octo-arm
- transform-origin 130px 106px
-
- &:hover
- .octo-arm
- animation octocat-wave 560ms ease-in-out
-
- @keyframes octocat-wave
- 0%, 100%
- transform rotate(0)
- 20%, 60%
- transform rotate(-25deg)
- 40%, 80%
- transform rotate(10deg)
-
-</style>
diff --git a/src/server/web/app/common/views/components/index.ts b/src/server/web/app/common/views/components/index.ts
deleted file mode 100644
index b58ba37ecb..0000000000
--- a/src/server/web/app/common/views/components/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import Vue from 'vue';
-
-import signin from './signin.vue';
-import signup from './signup.vue';
-import forkit from './forkit.vue';
-import nav from './nav.vue';
-import postHtml from './post-html';
-import poll from './poll.vue';
-import pollEditor from './poll-editor.vue';
-import reactionIcon from './reaction-icon.vue';
-import reactionsViewer from './reactions-viewer.vue';
-import time from './time.vue';
-import timer from './timer.vue';
-import mediaList from './media-list.vue';
-import uploader from './uploader.vue';
-import specialMessage from './special-message.vue';
-import streamIndicator from './stream-indicator.vue';
-import ellipsis from './ellipsis.vue';
-import messaging from './messaging.vue';
-import messagingRoom from './messaging-room.vue';
-import urlPreview from './url-preview.vue';
-import twitterSetting from './twitter-setting.vue';
-import fileTypeIcon from './file-type-icon.vue';
-import Switch from './switch.vue';
-import Othello from './othello.vue';
-import welcomeTimeline from './welcome-timeline.vue';
-
-Vue.component('mk-signin', signin);
-Vue.component('mk-signup', signup);
-Vue.component('mk-forkit', forkit);
-Vue.component('mk-nav', nav);
-Vue.component('mk-post-html', postHtml);
-Vue.component('mk-poll', poll);
-Vue.component('mk-poll-editor', pollEditor);
-Vue.component('mk-reaction-icon', reactionIcon);
-Vue.component('mk-reactions-viewer', reactionsViewer);
-Vue.component('mk-time', time);
-Vue.component('mk-timer', timer);
-Vue.component('mk-media-list', mediaList);
-Vue.component('mk-uploader', uploader);
-Vue.component('mk-special-message', specialMessage);
-Vue.component('mk-stream-indicator', streamIndicator);
-Vue.component('mk-ellipsis', ellipsis);
-Vue.component('mk-messaging', messaging);
-Vue.component('mk-messaging-room', messagingRoom);
-Vue.component('mk-url-preview', urlPreview);
-Vue.component('mk-twitter-setting', twitterSetting);
-Vue.component('mk-file-type-icon', fileTypeIcon);
-Vue.component('mk-switch', Switch);
-Vue.component('mk-othello', Othello);
-Vue.component('mk-welcome-timeline', welcomeTimeline);
diff --git a/src/server/web/app/common/views/components/media-list.vue b/src/server/web/app/common/views/components/media-list.vue
deleted file mode 100644
index 64172ad0b4..0000000000
--- a/src/server/web/app/common/views/components/media-list.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
-<div class="mk-media-list" :data-count="mediaList.length">
- <template v-for="media in mediaList">
- <mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
- <mk-media-image :image="media" :key="media.id" v-else />
- </template>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: ['mediaList'],
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-media-list
- display grid
- grid-gap 4px
- height 256px
-
- @media (max-width 500px)
- height 192px
-
- &[data-count="1"]
- grid-template-rows 1fr
- &[data-count="2"]
- grid-template-columns 1fr 1fr
- grid-template-rows 1fr
- &[data-count="3"]
- grid-template-columns 1fr 0.5fr
- grid-template-rows 1fr 1fr
- :nth-child(1)
- grid-row 1 / 3
- :nth-child(3)
- grid-column 2 / 3
- grid-row 2/3
- &[data-count="4"]
- grid-template-columns 1fr 1fr
- grid-template-rows 1fr 1fr
-
- :nth-child(1)
- grid-column 1 / 2
- grid-row 1 / 2
- :nth-child(2)
- grid-column 2 / 3
- grid-row 1 / 2
- :nth-child(3)
- grid-column 1 / 2
- grid-row 2 / 3
- :nth-child(4)
- grid-column 2 / 3
- grid-row 2 / 3
-
-</style>
diff --git a/src/server/web/app/common/views/components/messaging-room.form.vue b/src/server/web/app/common/views/components/messaging-room.form.vue
deleted file mode 100644
index 704f2016d8..0000000000
--- a/src/server/web/app/common/views/components/messaging-room.form.vue
+++ /dev/null
@@ -1,305 +0,0 @@
-<template>
-<div class="mk-messaging-form"
- @dragover.stop="onDragover"
- @drop.stop="onDrop"
->
- <textarea
- v-model="text"
- ref="textarea"
- @keypress="onKeypress"
- @paste="onPaste"
- placeholder="%i18n:common.input-message-here%"
- v-autocomplete="'text'"
- ></textarea>
- <div class="file" @click="file = null" v-if="file">{{ file.name }}</div>
- <mk-uploader ref="uploader" @uploaded="onUploaded"/>
- <button class="send" @click="send" :disabled="!canSend || sending" title="%i18n:common.send%">
- <template v-if="!sending">%fa:paper-plane%</template><template v-if="sending">%fa:spinner .spin%</template>
- </button>
- <button class="attach-from-local" @click="chooseFile" title="%i18n:common.tags.mk-messaging-form.attach-from-local%">
- %fa:upload%
- </button>
- <button class="attach-from-drive" @click="chooseFileFromDrive" title="%i18n:common.tags.mk-messaging-form.attach-from-drive%">
- %fa:R folder-open%
- </button>
- <input ref="file" type="file" @change="onChangeFile"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as autosize from 'autosize';
-
-export default Vue.extend({
- props: ['user'],
- data() {
- return {
- text: null,
- file: null,
- sending: false
- };
- },
- computed: {
- draftId(): string {
- return this.user.id;
- },
- canSend(): boolean {
- return (this.text != null && this.text != '') || this.file != null;
- },
- room(): any {
- return this.$parent;
- }
- },
- watch: {
- text() {
- this.saveDraft();
- },
- file() {
- this.saveDraft();
-
- if (this.room.isBottom()) {
- this.room.scrollToBottom();
- }
- }
- },
- mounted() {
- autosize(this.$refs.textarea);
-
- // 書きかけの投稿を復元
- const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftId];
- if (draft) {
- this.text = draft.data.text;
- this.file = draft.data.file;
- }
- },
- methods: {
- onPaste(e) {
- const data = e.clipboardData;
- const items = data.items;
-
- if (items.length == 1) {
- if (items[0].kind == 'file') {
- this.upload(items[0].getAsFile());
- }
- } else {
- if (items[0].kind == 'file') {
- alert('メッセージに添付できるのはひとつのファイルのみです');
- }
- }
- },
-
- onDragover(e) {
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == 'mk_drive_file';
- if (isFile || isDriveFile) {
- e.preventDefault();
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
- }
- },
-
- onDrop(e): void {
- // ファイルだったら
- if (e.dataTransfer.files.length == 1) {
- e.preventDefault();
- this.upload(e.dataTransfer.files[0]);
- return;
- } else if (e.dataTransfer.files.length > 1) {
- e.preventDefault();
- alert('メッセージに添付できるのはひとつのファイルのみです');
- return;
- }
-
- //#region ドライブのファイル
- const driveFile = e.dataTransfer.getData('mk_drive_file');
- if (driveFile != null && driveFile != '') {
- this.file = JSON.parse(driveFile);
- e.preventDefault();
- }
- //#endregion
- },
-
- onKeypress(e) {
- if ((e.which == 10 || e.which == 13) && e.ctrlKey) {
- this.send();
- }
- },
-
- chooseFile() {
- (this.$refs.file as any).click();
- },
-
- chooseFileFromDrive() {
- (this as any).apis.chooseDriveFile({
- multiple: false
- }).then(file => {
- this.file = file;
- });
- },
-
- onChangeFile() {
- this.upload((this.$refs.file as any).files[0]);
- },
-
- upload(file) {
- (this.$refs.uploader as any).upload(file);
- },
-
- onUploaded(file) {
- this.file = file;
- },
-
- send() {
- this.sending = true;
- (this as any).api('messaging/messages/create', {
- userId: this.user.id,
- text: this.text ? this.text : undefined,
- fileId: this.file ? this.file.id : undefined
- }).then(message => {
- this.clear();
- }).catch(err => {
- console.error(err);
- }).then(() => {
- this.sending = false;
- });
- },
-
- clear() {
- this.text = '';
- this.file = null;
- this.deleteDraft();
- },
-
- saveDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
-
- data[this.draftId] = {
- updatedAt: new Date(),
- data: {
- text: this.text,
- file: this.file
- }
- }
-
- localStorage.setItem('message_drafts', JSON.stringify(data));
- },
-
- deleteDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
-
- delete data[this.draftId];
-
- localStorage.setItem('message_drafts', JSON.stringify(data));
- },
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-messaging-form
- > textarea
- cursor auto
- display block
- width 100%
- min-width 100%
- max-width 100%
- height 64px
- margin 0
- padding 8px
- resize none
- font-size 1em
- color #000
- outline none
- border none
- border-top solid 1px #eee
- border-radius 0
- box-shadow none
- background transparent
-
- > .file
- padding 8px
- color #444
- background #eee
- cursor pointer
-
- > .send
- position absolute
- bottom 0
- right 0
- margin 0
- padding 10px 14px
- font-size 1em
- color #aaa
- transition color 0.1s ease
-
- &:hover
- color $theme-color
-
- &:active
- color darken($theme-color, 10%)
- transition color 0s ease
-
- .files
- display block
- margin 0
- padding 0 8px
- list-style none
-
- &:after
- content ''
- display block
- clear both
-
- > li
- display block
- float left
- margin 4px
- padding 0
- width 64px
- height 64px
- background-color #eee
- background-repeat no-repeat
- background-position center center
- background-size cover
- cursor move
-
- &:hover
- > .remove
- display block
-
- > .remove
- display none
- position absolute
- right -6px
- top -6px
- margin 0
- padding 0
- background transparent
- outline none
- border none
- border-radius 0
- box-shadow none
- cursor pointer
-
- .attach-from-local
- .attach-from-drive
- margin 0
- padding 10px 14px
- font-size 1em
- font-weight normal
- text-decoration none
- color #aaa
- transition color 0.1s ease
-
- &:hover
- color $theme-color
-
- &:active
- color darken($theme-color, 10%)
- transition color 0s ease
-
- input[type=file]
- display none
-
-</style>
diff --git a/src/server/web/app/common/views/components/messaging-room.message.vue b/src/server/web/app/common/views/components/messaging-room.message.vue
deleted file mode 100644
index 94f87fd709..0000000000
--- a/src/server/web/app/common/views/components/messaging-room.message.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<template>
-<div class="message" :data-is-me="isMe">
- <router-link class="avatar-anchor" :to="`/@${acct}`" :title="acct" target="_blank">
- <img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/>
- </router-link>
- <div class="content">
- <div class="balloon" :data-no-text="message.text == null">
- <p class="read" v-if="isMe && message.isRead">%i18n:common.tags.mk-messaging-message.is-read%</p>
- <button class="delete-button" v-if="isMe" title="%i18n:common.delete%">
- <img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
- </button>
- <div class="content" v-if="!message.isDeleted">
- <mk-post-html class="text" v-if="message.ast" :ast="message.ast" :i="os.i"/>
- <div class="file" v-if="message.file">
- <a :href="message.file.url" target="_blank" :title="message.file.name">
- <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
- <p v-else>{{ message.file.name }}</p>
- </a>
- </div>
- </div>
- <div class="content" v-if="message.isDeleted">
- <p class="is-deleted">%i18n:common.tags.mk-messaging-message.deleted%</p>
- </div>
- </div>
- <div></div>
- <mk-url-preview v-for="url in urls" :url="url" :key="url"/>
- <footer>
- <mk-time :time="message.createdAt"/>
- <template v-if="message.is_edited">%fa:pencil-alt%</template>
- </footer>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: ['message'],
- computed: {
- acct() {
- return getAcct(this.message.user);
- },
- isMe(): boolean {
- return this.message.userId == (this as any).os.i.id;
- },
- urls(): string[] {
- if (this.message.ast) {
- return this.message.ast
- .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
- .map(t => t.url);
- } else {
- return null;
- }
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.message
- $me-balloon-color = #23A7B6
-
- padding 10px 12px 10px 12px
- background-color transparent
-
- > .avatar-anchor
- display block
- position absolute
- top 10px
-
- > .avatar
- display block
- min-width 54px
- min-height 54px
- max-width 54px
- max-height 54px
- margin 0
- border-radius 8px
- transition all 0.1s ease
-
- > .content
-
- > .balloon
- display block
- padding 0
- max-width calc(100% - 16px)
- min-height 38px
- border-radius 16px
-
- &:before
- content ""
- pointer-events none
- display block
- position absolute
- top 12px
-
- & + *
- clear both
-
- &:hover
- > .delete-button
- display block
-
- > .delete-button
- display none
- position absolute
- z-index 1
- top -4px
- right -4px
- margin 0
- padding 0
- cursor pointer
- outline none
- border none
- border-radius 0
- box-shadow none
- background transparent
-
- > img
- vertical-align bottom
- width 16px
- height 16px
- cursor pointer
-
- > .read
- user-select none
- display block
- position absolute
- z-index 1
- bottom -4px
- left -12px
- margin 0
- color rgba(0, 0, 0, 0.5)
- font-size 11px
-
- > .content
-
- > .is-deleted
- display block
- margin 0
- padding 0
- overflow hidden
- overflow-wrap break-word
- font-size 1em
- color rgba(0, 0, 0, 0.5)
-
- > .text
- display block
- margin 0
- padding 8px 16px
- overflow hidden
- overflow-wrap break-word
- font-size 1em
- color rgba(0, 0, 0, 0.8)
-
- & + .file
- > a
- border-radius 0 0 16px 16px
-
- > .file
- > a
- display block
- max-width 100%
- max-height 512px
- border-radius 16px
- overflow hidden
- text-decoration none
-
- &:hover
- text-decoration none
-
- > p
- background #ccc
-
- > *
- display block
- margin 0
- width 100%
- height 100%
-
- > p
- padding 30px
- text-align center
- color #555
- background #ddd
-
- > .mk-url-preview
- margin 8px 0
-
- > footer
- display block
- margin 2px 0 0 0
- font-size 10px
- color rgba(0, 0, 0, 0.4)
-
- > [data-fa]
- margin-left 4px
-
- &:not([data-is-me])
- > .avatar-anchor
- left 12px
-
- > .content
- padding-left 66px
-
- > .balloon
- float left
- background #eee
-
- &[data-no-text]
- background transparent
-
- &:not([data-no-text]):before
- left -14px
- border-top solid 8px transparent
- border-right solid 8px #eee
- border-bottom solid 8px transparent
- border-left solid 8px transparent
-
- > footer
- text-align left
-
- &[data-is-me]
- > .avatar-anchor
- right 12px
-
- > .content
- padding-right 66px
-
- > .balloon
- float right
- background $me-balloon-color
-
- &[data-no-text]
- background transparent
-
- &:not([data-no-text]):before
- right -14px
- left auto
- border-top solid 8px transparent
- border-right solid 8px transparent
- border-bottom solid 8px transparent
- border-left solid 8px $me-balloon-color
-
- > .content
-
- > p.is-deleted
- color rgba(255, 255, 255, 0.5)
-
- > .text >>>
- &, *
- color #fff !important
-
- > footer
- text-align right
-
- &[data-is-deleted]
- > .baloon
- opacity 0.5
-
-</style>
diff --git a/src/server/web/app/common/views/components/messaging-room.vue b/src/server/web/app/common/views/components/messaging-room.vue
deleted file mode 100644
index d30c64d74a..0000000000
--- a/src/server/web/app/common/views/components/messaging-room.vue
+++ /dev/null
@@ -1,377 +0,0 @@
-<template>
-<div class="mk-messaging-room"
- @dragover.prevent.stop="onDragover"
- @drop.prevent.stop="onDrop"
->
- <div class="stream">
- <p class="init" v-if="init">%fa:spinner .spin%%i18n:common.loading%</p>
- <p class="empty" v-if="!init && messages.length == 0">%fa:info-circle%%i18n:common.tags.mk-messaging-room.empty%</p>
- <p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages">%fa:flag%%i18n:common.tags.mk-messaging-room.no-history%</p>
- <button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
- <template v-if="fetchingMoreMessages">%fa:spinner .pulse .fw%</template>{{ fetchingMoreMessages ? '%i18n:common.loading%' : '%i18n:common.tags.mk-messaging-room.more%' }}
- </button>
- <template v-for="(message, i) in _messages">
- <x-message :message="message" :key="message.id"/>
- <p class="date" v-if="i != messages.length - 1 && message._date != _messages[i + 1]._date">
- <span>{{ _messages[i + 1]._datetext }}</span>
- </p>
- </template>
- </div>
- <footer>
- <div ref="notifications" class="notifications"></div>
- <x-form :user="user" ref="form"/>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { MessagingStream } from '../../scripts/streaming/messaging';
-import XMessage from './messaging-room.message.vue';
-import XForm from './messaging-room.form.vue';
-import { url } from '../../../config';
-
-export default Vue.extend({
- components: {
- XMessage,
- XForm
- },
-
- props: ['user', 'isNaked'],
-
- data() {
- return {
- init: true,
- fetchingMoreMessages: false,
- messages: [],
- existMoreMessages: false,
- connection: null
- };
- },
-
- computed: {
- _messages(): any[] {
- return (this.messages as any).map(message => {
- const date = new Date(message.createdAt).getDate();
- const month = new Date(message.createdAt).getMonth() + 1;
- message._date = date;
- message._datetext = `${month}月 ${date}日`;
- return message;
- });
- },
-
- form(): any {
- return this.$refs.form;
- }
- },
-
- mounted() {
- this.connection = new MessagingStream((this as any).os, (this as any).os.i, this.user.id);
-
- this.connection.on('message', this.onMessage);
- this.connection.on('read', this.onRead);
-
- document.addEventListener('visibilitychange', this.onVisibilitychange);
-
- this.fetchMessages().then(() => {
- this.init = false;
- this.scrollToBottom();
- });
- },
-
- beforeDestroy() {
- this.connection.off('message', this.onMessage);
- this.connection.off('read', this.onRead);
- this.connection.close();
-
- document.removeEventListener('visibilitychange', this.onVisibilitychange);
- },
-
- methods: {
- onDragover(e) {
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == 'mk_drive_file';
-
- if (isFile || isDriveFile) {
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
- } else {
- e.dataTransfer.dropEffect = 'none';
- }
- },
-
- onDrop(e): void {
- // ファイルだったら
- if (e.dataTransfer.files.length == 1) {
- this.form.upload(e.dataTransfer.files[0]);
- return;
- } else if (e.dataTransfer.files.length > 1) {
- alert('メッセージに添付できるのはひとつのファイルのみです');
- return;
- }
-
- //#region ドライブのファイル
- const driveFile = e.dataTransfer.getData('mk_drive_file');
- if (driveFile != null && driveFile != '') {
- const file = JSON.parse(driveFile);
- this.form.file = file;
- }
- //#endregion
- },
-
- fetchMessages() {
- return new Promise((resolve, reject) => {
- const max = this.existMoreMessages ? 20 : 10;
-
- (this as any).api('messaging/messages', {
- userId: this.user.id,
- limit: max + 1,
- untilId: this.existMoreMessages ? this.messages[0].id : undefined
- }).then(messages => {
- if (messages.length == max + 1) {
- this.existMoreMessages = true;
- messages.pop();
- } else {
- this.existMoreMessages = false;
- }
-
- this.messages.unshift.apply(this.messages, messages.reverse());
- resolve();
- });
- });
- },
-
- fetchMoreMessages() {
- this.fetchingMoreMessages = true;
- this.fetchMessages().then(() => {
- this.fetchingMoreMessages = false;
- });
- },
-
- onMessage(message) {
- // サウンドを再生する
- if ((this as any).os.isEnableSounds) {
- const sound = new Audio(`${url}/assets/message.mp3`);
- sound.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1;
- sound.play();
- }
-
- const isBottom = this.isBottom();
-
- this.messages.push(message);
- if (message.userId != (this as any).os.i.id && !document.hidden) {
- this.connection.send({
- type: 'read',
- id: message.id
- });
- }
-
- if (isBottom) {
- // Scroll to bottom
- this.$nextTick(() => {
- this.scrollToBottom();
- });
- } else if (message.userId != (this as any).os.i.id) {
- // Notify
- this.notify('%i18n:common.tags.mk-messaging-room.new-message%');
- }
- },
-
- onRead(ids) {
- if (!Array.isArray(ids)) ids = [ids];
- ids.forEach(id => {
- if (this.messages.some(x => x.id == id)) {
- const exist = this.messages.map(x => x.id).indexOf(id);
- this.messages[exist].isRead = true;
- }
- });
- },
-
- isBottom() {
- const asobi = 64;
- const current = this.isNaked
- ? window.scrollY + window.innerHeight
- : this.$el.scrollTop + this.$el.offsetHeight;
- const max = this.isNaked
- ? document.body.offsetHeight
- : this.$el.scrollHeight;
- return current > (max - asobi);
- },
-
- scrollToBottom() {
- if (this.isNaked) {
- window.scroll(0, document.body.offsetHeight);
- } else {
- this.$el.scrollTop = this.$el.scrollHeight;
- }
- },
-
- notify(message) {
- const n = document.createElement('p') as any;
- n.innerHTML = '%fa:arrow-circle-down%' + message;
- n.onclick = () => {
- this.scrollToBottom();
- n.parentNode.removeChild(n);
- };
- (this.$refs.notifications as any).appendChild(n);
-
- setTimeout(() => {
- n.style.opacity = 0;
- setTimeout(() => n.parentNode.removeChild(n), 1000);
- }, 4000);
- },
-
- onVisibilitychange() {
- if (document.hidden) return;
- this.messages.forEach(message => {
- if (message.userId !== (this as any).os.i.id && !message.isRead) {
- this.connection.send({
- type: 'read',
- id: message.id
- });
- }
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-messaging-room
- display flex
- flex 1
- flex-direction column
- height 100%
-
- > .stream
- width 100%
- max-width 600px
- margin 0 auto
- flex 1
-
- > .init
- width 100%
- margin 0
- padding 16px 8px 8px 8px
- text-align center
- font-size 0.8em
- color rgba(0, 0, 0, 0.4)
-
- [data-fa]
- margin-right 4px
-
- > .empty
- width 100%
- margin 0
- padding 16px 8px 8px 8px
- text-align center
- font-size 0.8em
- color rgba(0, 0, 0, 0.4)
-
- [data-fa]
- margin-right 4px
-
- > .no-history
- display block
- margin 0
- padding 16px
- text-align center
- font-size 0.8em
- color rgba(0, 0, 0, 0.4)
-
- [data-fa]
- margin-right 4px
-
- > .more
- display block
- margin 16px auto
- padding 0 12px
- line-height 24px
- color #fff
- background rgba(0, 0, 0, 0.3)
- border-radius 12px
-
- &:hover
- background rgba(0, 0, 0, 0.4)
-
- &:active
- background rgba(0, 0, 0, 0.5)
-
- &.fetching
- cursor wait
-
- > [data-fa]
- margin-right 4px
-
- > .message
- // something
-
- > .date
- display block
- margin 8px 0
- text-align center
-
- &:before
- content ''
- display block
- position absolute
- height 1px
- width 90%
- top 16px
- left 0
- right 0
- margin 0 auto
- background rgba(0, 0, 0, 0.1)
-
- > span
- display inline-block
- margin 0
- padding 0 16px
- //font-weight bold
- line-height 32px
- color rgba(0, 0, 0, 0.3)
- background #fff
-
- > footer
- position -webkit-sticky
- position sticky
- z-index 2
- bottom 0
- width 100%
- max-width 600px
- margin 0 auto
- padding 0
- background rgba(255, 255, 255, 0.95)
- background-clip content-box
-
- > .notifications
- position absolute
- top -48px
- width 100%
- padding 8px 0
- text-align center
-
- &:empty
- display none
-
- > p
- display inline-block
- margin 0
- padding 0 12px 0 28px
- cursor pointer
- line-height 32px
- font-size 12px
- color $theme-color-foreground
- background $theme-color
- border-radius 16px
- transition opacity 1s ease
-
- > [data-fa]
- position absolute
- top 0
- left 10px
- line-height 32px
- font-size 16px
-
-</style>
diff --git a/src/server/web/app/common/views/components/messaging.vue b/src/server/web/app/common/views/components/messaging.vue
deleted file mode 100644
index 8317c3738a..0000000000
--- a/src/server/web/app/common/views/components/messaging.vue
+++ /dev/null
@@ -1,463 +0,0 @@
-<template>
-<div class="mk-messaging" :data-compact="compact">
- <div class="search" v-if="!compact" :style="{ top: headerTop + 'px' }">
- <div class="form">
- <label for="search-input">%fa:search%</label>
- <input v-model="q" type="search" @input="search" @keydown="onSearchKeydown" placeholder="%i18n:common.tags.mk-messaging.search-user%"/>
- </div>
- <div class="result">
- <ol class="users" v-if="result.length > 0" ref="searchResult">
- <li v-for="(user, i) in result"
- @keydown.enter="navigate(user)"
- @keydown="onSearchResultKeydown(i)"
- @click="navigate(user)"
- tabindex="-1"
- >
- <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
- <span class="name">{{ user.name }}</span>
- <span class="username">@{{ getAcct(user) }}</span>
- </li>
- </ol>
- </div>
- </div>
- <div class="history" v-if="messages.length > 0">
- <template>
- <a v-for="message in messages"
- class="user"
- :href="`/i/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
- :data-is-me="isMe(message)"
- :data-is-read="message.isRead"
- @click.prevent="navigate(isMe(message) ? message.recipient : message.user)"
- :key="message.id"
- >
- <div>
- <img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/>
- <header>
- <span class="name">{{ isMe(message) ? message.recipient.name : message.user.name }}</span>
- <span class="username">@{{ getAcct(isMe(message) ? message.recipient : message.user) }}</span>
- <mk-time :time="message.createdAt"/>
- </header>
- <div class="body">
- <p class="text"><span class="me" v-if="isMe(message)">%i18n:common.tags.mk-messaging.you%:</span>{{ message.text }}</p>
- </div>
- </div>
- </a>
- </template>
- </div>
- <p class="no-history" v-if="!fetching && messages.length == 0">%i18n:common.tags.mk-messaging.no-history%</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';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- props: {
- compact: {
- type: Boolean,
- default: false
- },
- headerTop: {
- type: Number,
- default: 0
- }
- },
- data() {
- return {
- fetching: true,
- moreFetching: false,
- messages: [],
- q: null,
- result: [],
- connection: null,
- connectionId: null
- };
- },
- mounted() {
- this.connection = (this as any).os.streams.messagingIndexStream.getConnection();
- this.connectionId = (this as any).os.streams.messagingIndexStream.use();
-
- this.connection.on('message', this.onMessage);
- this.connection.on('read', this.onRead);
-
- (this as any).api('messaging/history').then(messages => {
- this.messages = messages;
- this.fetching = false;
- });
- },
- beforeDestroy() {
- this.connection.off('message', this.onMessage);
- this.connection.off('read', this.onRead);
- (this as any).os.streams.messagingIndexStream.dispose(this.connectionId);
- },
- methods: {
- getAcct,
- isMe(message) {
- return message.userId == (this as any).os.i.id;
- },
- onMessage(message) {
- this.messages = this.messages.filter(m => !(
- (m.recipientId == message.recipientId && m.userId == message.userId) ||
- (m.recipientId == message.userId && m.userId == message.recipientId)));
-
- this.messages.unshift(message);
- },
- onRead(ids) {
- ids.forEach(id => {
- const found = this.messages.find(m => m.id == id);
- if (found) found.isRead = true;
- });
- },
- search() {
- if (this.q == '') {
- this.result = [];
- return;
- }
- (this as any).api('users/search', {
- query: this.q,
- max: 5
- }).then(users => {
- this.result = users;
- });
- },
- navigate(user) {
- this.$emit('navigate', user);
- },
- onSearchKeydown(e) {
- switch (e.which) {
- case 9: // [TAB]
- case 40: // [↓]
- e.preventDefault();
- e.stopPropagation();
- (this.$refs.searchResult as any).childNodes[0].focus();
- break;
- }
- },
- onSearchResultKeydown(i, e) {
- const list = this.$refs.searchResult as any;
-
- const cancel = () => {
- e.preventDefault();
- e.stopPropagation();
- };
-
- switch (true) {
- case e.which == 27: // [ESC]
- cancel();
- (this.$refs.search as any).focus();
- break;
-
- case e.which == 9 && e.shiftKey: // [TAB] + [Shift]
- case e.which == 38: // [↑]
- cancel();
- (list.childNodes[i].previousElementSibling || list.childNodes[this.result.length - 1]).focus();
- break;
-
- case e.which == 9: // [TAB]
- case e.which == 40: // [↓]
- cancel();
- (list.childNodes[i].nextElementSibling || list.childNodes[0]).focus();
- break;
- }
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-messaging
-
- &[data-compact]
- font-size 0.8em
-
- > .history
- > a
- &:last-child
- border-bottom none
-
- &:not([data-is-me]):not([data-is-read])
- > div
- background-image none
- border-left solid 4px #3aa2dc
-
- > div
- padding 16px
-
- > header
- > .mk-time
- font-size 1em
-
- > .avatar
- width 42px
- height 42px
- margin 0 12px 0 0
-
- > .search
- display block
- position -webkit-sticky
- position sticky
- top 0
- left 0
- z-index 1
- width 100%
- background #fff
- box-shadow 0 0px 2px rgba(0, 0, 0, 0.2)
-
- > .form
- padding 8px
- background #f7f7f7
-
- > label
- display block
- position absolute
- top 0
- left 8px
- z-index 1
- height 100%
- width 38px
- pointer-events none
-
- > [data-fa]
- display block
- position absolute
- top 0
- right 0
- bottom 0
- left 0
- width 1em
- line-height 56px
- margin auto
- color #555
-
- > input
- margin 0
- padding 0 0 0 32px
- width 100%
- font-size 1em
- line-height 38px
- color #000
- outline none
- border solid 1px #eee
- border-radius 5px
- box-shadow none
- transition color 0.5s ease, border 0.5s ease
-
- &:hover
- border solid 1px #ddd
- transition border 0.2s ease
-
- &:focus
- color darken($theme-color, 20%)
- border solid 1px $theme-color
- transition color 0, border 0
-
- > .result
- display block
- top 0
- left 0
- z-index 2
- width 100%
- margin 0
- padding 0
- background #fff
-
- > .users
- margin 0
- padding 0
- list-style none
-
- > li
- display inline-block
- z-index 1
- width 100%
- padding 8px 32px
- vertical-align top
- white-space nowrap
- overflow hidden
- color rgba(0, 0, 0, 0.8)
- text-decoration none
- transition none
- cursor pointer
-
- &:hover
- &:focus
- color #fff
- background $theme-color
-
- .name
- color #fff
-
- .username
- color #fff
-
- &:active
- color #fff
- background darken($theme-color, 10%)
-
- .name
- color #fff
-
- .username
- color #fff
-
- .avatar
- vertical-align middle
- min-width 32px
- min-height 32px
- max-width 32px
- max-height 32px
- margin 0 8px 0 0
- border-radius 6px
-
- .name
- margin 0 8px 0 0
- /*font-weight bold*/
- font-weight normal
- color rgba(0, 0, 0, 0.8)
-
- .username
- font-weight normal
- color rgba(0, 0, 0, 0.3)
-
- > .history
-
- > a
- display block
- text-decoration none
- background #fff
- border-bottom solid 1px #eee
-
- *
- pointer-events none
- user-select none
-
- &:hover
- background #fafafa
-
- > .avatar
- filter saturate(200%)
-
- &:active
- background #eee
-
- &[data-is-read]
- &[data-is-me]
- opacity 0.8
-
- &:not([data-is-me]):not([data-is-read])
- > div
- background-image url("/assets/unread.svg")
- background-repeat no-repeat
- background-position 0 center
-
- &:after
- content ""
- display block
- clear both
-
- > div
- max-width 500px
- margin 0 auto
- padding 20px 30px
-
- &:after
- content ""
- display block
- clear both
-
- > header
- display flex
- align-items center
- margin-bottom 2px
- white-space nowrap
- overflow hidden
-
- > .name
- margin 0
- padding 0
- overflow hidden
- text-overflow ellipsis
- font-size 1em
- color rgba(0, 0, 0, 0.9)
- font-weight bold
- transition all 0.1s ease
-
- > .username
- margin 0 8px
- color rgba(0, 0, 0, 0.5)
-
- > .mk-time
- margin 0 0 0 auto
- color rgba(0, 0, 0, 0.5)
- font-size 80%
-
- > .avatar
- float left
- width 54px
- height 54px
- margin 0 16px 0 0
- border-radius 8px
- transition all 0.1s ease
-
- > .body
-
- > .text
- display block
- margin 0 0 0 0
- padding 0
- overflow hidden
- overflow-wrap break-word
- font-size 1.1em
- color rgba(0, 0, 0, 0.8)
-
- .me
- color rgba(0, 0, 0, 0.4)
-
- > .image
- display block
- max-width 100%
- max-height 512px
-
- > .no-history
- margin 0
- padding 2em 1em
- text-align center
- color #999
- font-weight 500
-
- > .fetching
- margin 0
- padding 16px
- text-align center
- color #aaa
-
- > [data-fa]
- margin-right 4px
-
- // TODO: element base media query
- @media (max-width 400px)
- > .search
- > .result
- > .users
- > li
- padding 8px 16px
-
- > .history
- > a
- &:not([data-is-me]):not([data-is-read])
- > div
- background-image none
- border-left solid 4px #3aa2dc
-
- > div
- padding 16px
- font-size 14px
-
- > .avatar
- margin 0 12px 0 0
-
-</style>
diff --git a/src/server/web/app/common/views/components/nav.vue b/src/server/web/app/common/views/components/nav.vue
deleted file mode 100644
index 8ce75d3529..0000000000
--- a/src/server/web/app/common/views/components/nav.vue
+++ /dev/null
@@ -1,41 +0,0 @@
-<template>
-<span class="mk-nav">
- <a :href="aboutUrl">%i18n:common.tags.mk-nav-links.about%</a>
- <i>・</i>
- <a :href="statsUrl">%i18n:common.tags.mk-nav-links.stats%</a>
- <i>・</i>
- <a :href="statusUrl">%i18n:common.tags.mk-nav-links.status%</a>
- <i>・</i>
- <a href="http://zawazawa.jp/misskey/">%i18n:common.tags.mk-nav-links.wiki%</a>
- <i>・</i>
- <a href="https://github.com/syuilo/misskey/blob/master/DONORS.md">%i18n:common.tags.mk-nav-links.donors%</a>
- <i>・</i>
- <a href="https://github.com/syuilo/misskey">%i18n:common.tags.mk-nav-links.repository%</a>
- <i>・</i>
- <a :href="devUrl">%i18n:common.tags.mk-nav-links.develop%</a>
- <i>・</i>
- <a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a>
-</span>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { docsUrl, statsUrl, statusUrl, devUrl, lang } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- aboutUrl: `${docsUrl}/${lang}/about`,
- statsUrl,
- statusUrl,
- devUrl
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-nav
- a
- color inherit
-</style>
diff --git a/src/server/web/app/common/views/components/othello.game.vue b/src/server/web/app/common/views/components/othello.game.vue
deleted file mode 100644
index f08742ad10..0000000000
--- a/src/server/web/app/common/views/components/othello.game.vue
+++ /dev/null
@@ -1,324 +0,0 @@
-<template>
-<div class="root">
- <header><b>{{ blackUser.name }}</b>(黒) vs <b>{{ whiteUser.name }}</b>(白)</header>
-
- <div style="overflow: hidden">
- <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ turnUser.name }}のターンです<mk-ellipsis/></p>
- <p class="turn" v-if="logPos != logs.length">{{ turnUser.name }}のターン</p>
- <p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">相手のターンです<mk-ellipsis/></p>
- <p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">あなたのターンです</p>
- <p class="result" v-if="game.isEnded && logPos == logs.length">
- <template v-if="game.winner"><b>{{ game.winner.name }}</b>の勝ち{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
- <template v-else>引き分け</template>
- </p>
- </div>
-
- <div class="board" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
- <div v-for="(stone, i) in o.board"
- :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
- @click="set(i)"
- :title="'[' + (o.transformPosToXy(i)[0] + 1) + ', ' + (o.transformPosToXy(i)[1] + 1) + '] (' + i + ')'"
- >
- <img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt="">
- <img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt="">
- </div>
- </div>
-
- <p class="status"><b>{{ logPos }}ターン目</b> 黒:{{ o.blackCount }} 白:{{ o.whiteCount }} 合計:{{ o.blackCount + o.whiteCount }}</p>
-
- <div class="player" v-if="game.isEnded">
- <el-button-group>
- <el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
- <el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</el-button>
- </el-button-group>
- <span>{{ logPos }} / {{ logs.length }}</span>
- <el-button-group>
- <el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</el-button>
- <el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
- </el-button-group>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as CRC32 from 'crc-32';
-import Othello, { Color } from '../../../../../common/othello/core';
-import { url } from '../../../config';
-
-export default Vue.extend({
- props: ['initGame', 'connection'],
-
- data() {
- return {
- game: null,
- o: null as Othello,
- logs: [],
- logPos: 0,
- pollingClock: null
- };
- },
-
- computed: {
- iAmPlayer(): boolean {
- if (!(this as any).os.isSignedIn) return false;
- return this.game.user1Id == (this as any).os.i.id || this.game.user2Id == (this as any).os.i.id;
- },
- myColor(): Color {
- if (!this.iAmPlayer) return null;
- if (this.game.user1Id == (this as any).os.i.id && this.game.black == 1) return true;
- if (this.game.user2Id == (this as any).os.i.id && this.game.black == 2) return true;
- return false;
- },
- opColor(): Color {
- if (!this.iAmPlayer) return null;
- return this.myColor === true ? false : true;
- },
- blackUser(): any {
- return this.game.black == 1 ? this.game.user1 : this.game.user2;
- },
- whiteUser(): any {
- return this.game.black == 1 ? this.game.user2 : this.game.user1;
- },
- turnUser(): any {
- if (this.o.turn === true) {
- return this.game.black == 1 ? this.game.user1 : this.game.user2;
- } else if (this.o.turn === false) {
- return this.game.black == 1 ? this.game.user2 : this.game.user1;
- } else {
- return null;
- }
- },
- isMyTurn(): boolean {
- if (this.turnUser == null) return null;
- return this.turnUser.id == (this as any).os.i.id;
- }
- },
-
- watch: {
- logPos(v) {
- if (!this.game.isEnded) return;
- this.o = new Othello(this.game.settings.map, {
- isLlotheo: this.game.settings.isLlotheo,
- canPutEverywhere: this.game.settings.canPutEverywhere,
- loopedBoard: this.game.settings.loopedBoard
- });
- this.logs.forEach((log, i) => {
- if (i < v) {
- this.o.put(log.color, log.pos);
- }
- });
- this.$forceUpdate();
- }
- },
-
- created() {
- this.game = this.initGame;
-
- this.o = new Othello(this.game.settings.map, {
- isLlotheo: this.game.settings.isLlotheo,
- canPutEverywhere: this.game.settings.canPutEverywhere,
- loopedBoard: this.game.settings.loopedBoard
- });
-
- this.game.logs.forEach(log => {
- this.o.put(log.color, log.pos);
- });
-
- this.logs = this.game.logs;
- this.logPos = this.logs.length;
-
- // 通信を取りこぼしてもいいように定期的にポーリングさせる
- if (this.game.isStarted && !this.game.isEnded) {
- this.pollingClock = setInterval(() => {
- const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
- this.connection.send({
- type: 'check',
- crc32
- });
- }, 3000);
- }
- },
-
- mounted() {
- this.connection.on('set', this.onSet);
- this.connection.on('rescue', this.onRescue);
- },
-
- beforeDestroy() {
- this.connection.off('set', this.onSet);
- this.connection.off('rescue', this.onRescue);
-
- clearInterval(this.pollingClock);
- },
-
- methods: {
- set(pos) {
- if (this.game.isEnded) return;
- if (!this.iAmPlayer) return;
- if (!this.isMyTurn) return;
- if (!this.o.canPut(this.myColor, pos)) return;
-
- this.o.put(this.myColor, pos);
-
- // サウンドを再生する
- if ((this as any).os.isEnableSounds) {
- const sound = new Audio(`${url}/assets/othello-put-me.mp3`);
- sound.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1;
- sound.play();
- }
-
- this.connection.send({
- type: 'set',
- pos
- });
-
- this.checkEnd();
-
- this.$forceUpdate();
- },
-
- onSet(x) {
- this.logs.push(x);
- this.logPos++;
- this.o.put(x.color, x.pos);
- this.checkEnd();
- this.$forceUpdate();
-
- // サウンドを再生する
- if ((this as any).os.isEnableSounds && x.color != this.myColor) {
- const sound = new Audio(`${url}/assets/othello-put-you.mp3`);
- sound.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1;
- sound.play();
- }
- },
-
- checkEnd() {
- this.game.isEnded = this.o.isEnded;
- if (this.game.isEnded) {
- if (this.o.winner === true) {
- this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id;
- this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2;
- } else if (this.o.winner === false) {
- this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id;
- this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1;
- } else {
- this.game.winnerId = null;
- this.game.winner = null;
- }
- }
- },
-
- // 正しいゲーム情報が送られてきたとき
- onRescue(game) {
- this.game = game;
-
- this.o = new Othello(this.game.settings.map, {
- isLlotheo: this.game.settings.isLlotheo,
- canPutEverywhere: this.game.settings.canPutEverywhere,
- loopedBoard: this.game.settings.loopedBoard
- });
-
- this.game.logs.forEach(log => {
- this.o.put(log.color, log.pos, true);
- });
-
- this.logs = this.game.logs;
- this.logPos = this.logs.length;
-
- this.checkEnd();
- this.$forceUpdate();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.root
- text-align center
-
- > header
- padding 8px
- border-bottom dashed 1px #c4cdd4
-
- > .board
- display grid
- grid-gap 4px
- width 350px
- height 350px
- margin 0 auto
-
- > div
- background transparent
- border-radius 6px
- overflow hidden
-
- *
- pointer-events none
- user-select none
-
- &.empty
- border solid 2px #eee
-
- &.empty.can
- background #eee
-
- &.empty.myTurn
- border-color #ddd
-
- &.can
- background #eee
- cursor pointer
-
- &:hover
- border-color darken($theme-color, 10%)
- background $theme-color
-
- &:active
- background darken($theme-color, 10%)
-
- &.prev
- box-shadow 0 0 0 4px rgba($theme-color, 0.7)
-
- &.isEnded
- border-color #ddd
-
- &.none
- border-color transparent !important
-
- > img
- display block
- width 100%
- height 100%
-
- > .graph
- display grid
- grid-template-columns repeat(61, 1fr)
- width 300px
- height 38px
- margin 0 auto 16px auto
-
- > div
- &:not(:empty)
- background #ccc
-
- > div:first-child
- background #333
-
- > div:last-child
- background #ccc
-
- > .status
- margin 0
- padding 16px 0
-
- > .player
- padding-bottom 32px
-
- > span
- display inline-block
- margin 0 8px
- min-width 70px
-</style>
diff --git a/src/server/web/app/common/views/components/othello.gameroom.vue b/src/server/web/app/common/views/components/othello.gameroom.vue
deleted file mode 100644
index dba9ccd16d..0000000000
--- a/src/server/web/app/common/views/components/othello.gameroom.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<template>
-<div>
- <x-room v-if="!g.isStarted" :game="g" :connection="connection"/>
- <x-game v-else :init-game="g" :connection="connection"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import XGame from './othello.game.vue';
-import XRoom from './othello.room.vue';
-import { OthelloGameStream } from '../../scripts/streaming/othello-game';
-
-export default Vue.extend({
- components: {
- XGame,
- XRoom
- },
- props: ['game'],
- data() {
- return {
- connection: null,
- g: null
- };
- },
- created() {
- this.g = this.game;
- this.connection = new OthelloGameStream((this as any).os, (this as any).os.i, this.game);
- this.connection.on('started', this.onStarted);
- },
- beforeDestroy() {
- this.connection.off('started', this.onStarted);
- this.connection.close();
- },
- methods: {
- onStarted(game) {
- Object.assign(this.g, game);
- this.$forceUpdate();
- }
- }
-});
-</script>
diff --git a/src/server/web/app/common/views/components/othello.room.vue b/src/server/web/app/common/views/components/othello.room.vue
deleted file mode 100644
index a32be6b74f..0000000000
--- a/src/server/web/app/common/views/components/othello.room.vue
+++ /dev/null
@@ -1,297 +0,0 @@
-<template>
-<div class="root">
- <header><b>{{ game.user1.name }}</b> vs <b>{{ game.user2.name }}</b></header>
-
- <div>
- <p>ゲームの設定</p>
-
- <el-card class="map">
- <div slot="header">
- <el-select :class="$style.mapSelect" v-model="mapName" placeholder="マップを選択" @change="onMapChange">
- <el-option label="ランダム" :value="null"/>
- <el-option-group v-for="c in mapCategories" :key="c" :label="c">
- <el-option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name">
- <span style="float: left">{{ m.name }}</span>
- <span style="float: right; color: #8492a6; font-size: 13px" v-if="m.author">(by <i>{{ m.author }}</i>)</span>
- </el-option>
- </el-option-group>
- </el-select>
- </div>
- <div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
- <div v-for="(x, i) in game.settings.map.join('')"
- :data-none="x == ' '"
- @click="onPixelClick(i, x)"
- >
- <template v-if="x == 'b'">%fa:circle%</template>
- <template v-if="x == 'w'">%fa:circle R%</template>
- </div>
- </div>
- </el-card>
-
- <el-card class="bw">
- <div slot="header">
- <span>先手/後手</span>
- </div>
- <el-radio v-model="game.settings.bw" label="random" @change="updateSettings">ランダム</el-radio>
- <el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ game.user1.name }}が黒</el-radio>
- <el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ game.user2.name }}が黒</el-radio>
- </el-card>
-
- <el-card class="rules">
- <div slot="header">
- <span>ルール</span>
- </div>
- <mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="石の少ない方が勝ち(ロセオ)"/>
- <mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="ループマップ"/>
- <mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="どこでも置けるモード"/>
- </el-card>
-
- <el-card class="bot-form" v-if="form">
- <div slot="header">
- <span>Botの設定</span>
- </div>
- <el-alert v-for="message in messages"
- :title="message.text"
- :type="message.type"
- :key="message.id"
- />
- <template v-for="item in form">
- <mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch>
-
- <el-card v-if="item.type == 'radio'" :key="item.id">
- <div slot="header">
- <span>{{ item.label }}</span>
- </div>
- <el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio>
- </el-card>
-
- <el-card v-if="item.type == 'textbox'" :key="item.id">
- <div slot="header">
- <span>{{ item.label }}</span>
- </div>
- <el-input v-model="item.value" @change="onChangeForm($event, item)"/>
- </el-card>
- </template>
- </el-card>
- </div>
-
- <footer>
- <p class="status">
- <template v-if="isAccepted && isOpAccepted">ゲームは数秒後に開始されます<mk-ellipsis/></template>
- <template v-if="isAccepted && !isOpAccepted">相手の準備が完了するのを待っています<mk-ellipsis/></template>
- <template v-if="!isAccepted && isOpAccepted">あなたの準備が完了するのを待っています</template>
- <template v-if="!isAccepted && !isOpAccepted">準備中<mk-ellipsis/></template>
- </p>
-
- <div class="actions">
- <el-button @click="exit">キャンセル</el-button>
- <el-button type="primary" @click="accept" v-if="!isAccepted">準備完了</el-button>
- <el-button type="primary" @click="cancel" v-if="isAccepted">準備続行</el-button>
- </div>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as maps from '../../../../../common/othello/maps';
-
-export default Vue.extend({
- props: ['game', 'connection'],
-
- data() {
- return {
- o: null,
- isLlotheo: false,
- mapName: maps.eighteight.name,
- maps: maps,
- form: null,
- messages: []
- };
- },
-
- computed: {
- mapCategories(): string[] {
- const categories = Object.entries(maps).map(x => x[1].category);
- return categories.filter((item, pos) => categories.indexOf(item) == pos);
- },
- isAccepted(): boolean {
- if (this.game.user1Id == (this as any).os.i.id && this.game.user1Accepted) return true;
- if (this.game.user2Id == (this as any).os.i.id && this.game.user2Accepted) return true;
- return false;
- },
- isOpAccepted(): boolean {
- if (this.game.user1Id != (this as any).os.i.id && this.game.user1Accepted) return true;
- if (this.game.user2Id != (this as any).os.i.id && this.game.user2Accepted) return true;
- return false;
- }
- },
-
- created() {
- this.connection.on('change-accepts', this.onChangeAccepts);
- this.connection.on('update-settings', this.onUpdateSettings);
- this.connection.on('init-form', this.onInitForm);
- this.connection.on('message', this.onMessage);
-
- if (this.game.user1Id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
- if (this.game.user2Id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2;
- },
-
- beforeDestroy() {
- this.connection.off('change-accepts', this.onChangeAccepts);
- this.connection.off('update-settings', this.onUpdateSettings);
- this.connection.off('init-form', this.onInitForm);
- this.connection.off('message', this.onMessage);
- },
-
- methods: {
- exit() {
-
- },
-
- accept() {
- this.connection.send({
- type: 'accept'
- });
- },
-
- cancel() {
- this.connection.send({
- type: 'cancel-accept'
- });
- },
-
- onChangeAccepts(accepts) {
- this.game.user1Accepted = accepts.user1;
- this.game.user2Accepted = accepts.user2;
- this.$forceUpdate();
- },
-
- updateSettings() {
- this.connection.send({
- type: 'update-settings',
- settings: this.game.settings
- });
- },
-
- onUpdateSettings(settings) {
- this.game.settings = settings;
- if (this.game.settings.map == null) {
- this.mapName = null;
- } else {
- const foundMap = Object.entries(maps).find(x => x[1].data.join('') == this.game.settings.map.join(''));
- this.mapName = foundMap ? foundMap[1].name : '-Custom-';
- }
- },
-
- onInitForm(x) {
- if (x.userId == (this as any).os.i.id) return;
- this.form = x.form;
- },
-
- onMessage(x) {
- if (x.userId == (this as any).os.i.id) return;
- this.messages.unshift(x.message);
- },
-
- onChangeForm(v, item) {
- this.connection.send({
- type: 'update-form',
- id: item.id,
- value: v
- });
- },
-
- onMapChange(v) {
- if (v == null) {
- this.game.settings.map = null;
- } else {
- this.game.settings.map = Object.entries(maps).find(x => x[1].name == v)[1].data;
- }
- this.$forceUpdate();
- this.updateSettings();
- },
-
- onPixelClick(pos, pixel) {
- const x = pos % this.game.settings.map[0].length;
- const y = Math.floor(pos / this.game.settings.map[0].length);
- const newPixel =
- pixel == ' ' ? '-' :
- pixel == '-' ? 'b' :
- pixel == 'b' ? 'w' :
- ' ';
- const line = this.game.settings.map[y].split('');
- line[x] = newPixel;
- this.$set(this.game.settings.map, y, line.join(''));
- this.$forceUpdate();
- this.updateSettings();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.root
- text-align center
- background #f9f9f9
-
- > header
- padding 8px
- border-bottom dashed 1px #c4cdd4
-
- > div
- padding 0 16px
-
- > .map
- > .bw
- > .rules
- > .bot-form
- max-width 400px
- margin 0 auto 16px auto
-
- > footer
- position sticky
- bottom 0
- padding 16px
- background rgba(255, 255, 255, 0.9)
- border-top solid 1px #c4cdd4
-
- > .status
- margin 0 0 16px 0
-</style>
-
-<style lang="stylus" module>
-.mapSelect
- width 100%
-
-.board
- display grid
- grid-gap 4px
- width 300px
- height 300px
- margin 0 auto
-
- > div
- background transparent
- border solid 2px #ddd
- border-radius 6px
- overflow hidden
- cursor pointer
-
- *
- pointer-events none
- user-select none
- width 100%
- height 100%
-
- &[data-none]
- border-color transparent
-
-</style>
-
-<style lang="stylus">
-.el-alert__content
- position initial !important
-</style>
diff --git a/src/server/web/app/common/views/components/othello.vue b/src/server/web/app/common/views/components/othello.vue
deleted file mode 100644
index 8f7d9dfd6a..0000000000
--- a/src/server/web/app/common/views/components/othello.vue
+++ /dev/null
@@ -1,311 +0,0 @@
-<template>
-<div class="mk-othello">
- <div v-if="game">
- <x-gameroom :game="game"/>
- </div>
- <div class="matching" v-else-if="matching">
- <h1><b>{{ matching.name }}</b>を待っています<mk-ellipsis/></h1>
- <div class="cancel">
- <el-button round @click="cancel">キャンセル</el-button>
- </div>
- </div>
- <div class="index" v-else>
- <h1>Misskey %fa:circle%thell%fa:circle R%</h1>
- <p>他のMisskeyユーザーとオセロで対戦しよう</p>
- <div class="play">
- <el-button round>フリーマッチ(準備中)</el-button>
- <el-button type="primary" round @click="match">指名</el-button>
- <details>
- <summary>遊び方</summary>
- <div>
- <p>オセロは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
- <dl>
- <dt><b>フリーマッチ</b></dt>
- <dd>ランダムなユーザーと対戦するモードです。</dd>
- <dt><b>指名</b></dt>
- <dd>指定したユーザーと対戦するモードです。</dd>
- </dl>
- </div>
- </details>
- </div>
- <section v-if="invitations.length > 0">
- <h2>対局の招待があります!:</h2>
- <div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
- <img :src="`${i.parent.avatarUrl}?thumbnail&size=32`" alt="">
- <span class="name"><b>{{ i.parent.name }}</b></span>
- <span class="username">@{{ i.parent.username }}</span>
- <mk-time :time="i.createdAt"/>
- </div>
- </section>
- <section v-if="myGames.length > 0">
- <h2>自分の対局</h2>
- <a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
- <img :src="`${g.user1.avatarUrl}?thumbnail&size=32`" alt="">
- <img :src="`${g.user2.avatarUrl}?thumbnail&size=32`" alt="">
- <span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
- <span class="state">{{ g.isEnded ? '終了' : '進行中' }}</span>
- </a>
- </section>
- <section v-if="games.length > 0">
- <h2>みんなの対局</h2>
- <a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
- <img :src="`${g.user1.avatarUrl}?thumbnail&size=32`" alt="">
- <img :src="`${g.user2.avatarUrl}?thumbnail&size=32`" alt="">
- <span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
- <span class="state">{{ g.isEnded ? '終了' : '進行中' }}</span>
- </a>
- </section>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import XGameroom from './othello.gameroom.vue';
-
-export default Vue.extend({
- components: {
- XGameroom
- },
- props: ['initGame'],
- data() {
- return {
- game: null,
- games: [],
- gamesFetching: true,
- gamesMoreFetching: false,
- myGames: [],
- matching: null,
- invitations: [],
- connection: null,
- connectionId: null,
- pingClock: null
- };
- },
- watch: {
- game(g) {
- this.$emit('gamed', g);
- }
- },
- created() {
- if (this.initGame) {
- this.game = this.initGame;
- }
- },
- mounted() {
- this.connection = (this as any).os.streams.othelloStream.getConnection();
- this.connectionId = (this as any).os.streams.othelloStream.use();
-
- this.connection.on('matched', this.onMatched);
- this.connection.on('invited', this.onInvited);
-
- (this as any).api('othello/games', {
- my: true
- }).then(games => {
- this.myGames = games;
- });
-
- (this as any).api('othello/games').then(games => {
- this.games = games;
- this.gamesFetching = false;
- });
-
- (this as any).api('othello/invitations').then(invitations => {
- this.invitations = this.invitations.concat(invitations);
- });
-
- this.pingClock = setInterval(() => {
- if (this.matching) {
- this.connection.send({
- type: 'ping',
- id: this.matching.id
- });
- }
- }, 3000);
- },
- beforeDestroy() {
- this.connection.off('matched', this.onMatched);
- this.connection.off('invited', this.onInvited);
- (this as any).os.streams.othelloStream.dispose(this.connectionId);
-
- clearInterval(this.pingClock);
- },
- methods: {
- go(game) {
- (this as any).api('othello/games/show', {
- gameId: game.id
- }).then(game => {
- this.matching = null;
- this.game = game;
- });
- },
- match() {
- (this as any).apis.input({
- title: 'ユーザー名を入力してください'
- }).then(username => {
- (this as any).api('users/show', {
- username
- }).then(user => {
- (this as any).api('othello/match', {
- userId: user.id
- }).then(res => {
- if (res == null) {
- this.matching = user;
- } else {
- this.game = res;
- }
- });
- });
- });
- },
- cancel() {
- this.matching = null;
- (this as any).api('othello/match/cancel');
- },
- accept(invitation) {
- (this as any).api('othello/match', {
- userId: invitation.parent.id
- }).then(game => {
- if (game) {
- this.matching = null;
- this.game = game;
- }
- });
- },
- onMatched(game) {
- this.matching = null;
- this.game = game;
- },
- onInvited(invite) {
- this.invitations.unshift(invite);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-othello
- color #677f84
- background #fff
-
- > .matching
- > h1
- margin 0
- padding 24px
- font-size 20px
- text-align center
- font-weight normal
-
- > .cancel
- margin 0 auto
- padding 24px 0 0 0
- max-width 200px
- text-align center
- border-top dashed 1px #c4cdd4
-
- > .index
- > h1
- margin 0
- padding 24px
- font-size 24px
- text-align center
- font-weight normal
- color #fff
- background linear-gradient(to bottom, #8bca3e, #d6cf31)
-
- & + p
- margin 0
- padding 12px
- margin-bottom 12px
- text-align center
- font-size 14px
- border-bottom solid 1px #d3d9dc
-
- > .play
- margin 0 auto
- padding 0 16px
- max-width 500px
- text-align center
-
- > details
- margin 8px 0
-
- > div
- padding 16px
- font-size 14px
- text-align left
- background #f5f5f5
- border-radius 8px
-
- > section
- margin 0 auto
- padding 0 16px 16px 16px
- max-width 500px
- border-top solid 1px #d3d9dc
-
- > h2
- margin 0
- padding 16px 0 8px 0
- font-size 16px
- font-weight bold
-
- .invitation
- margin 8px 0
- padding 8px
- border solid 1px #e1e5e8
- border-radius 6px
- cursor pointer
-
- *
- pointer-events none
- user-select none
-
- &:focus
- border-color $theme-color
-
- &:hover
- background #f5f5f5
-
- &:active
- background #eee
-
- > img
- vertical-align bottom
- border-radius 100%
-
- > span
- margin 0 8px
- line-height 32px
-
- .game
- display block
- margin 8px 0
- padding 8px
- color #677f84
- border solid 1px #e1e5e8
- border-radius 6px
- cursor pointer
-
- *
- pointer-events none
- user-select none
-
- &:focus
- border-color $theme-color
-
- &:hover
- background #f5f5f5
-
- &:active
- background #eee
-
- > img
- vertical-align bottom
- border-radius 100%
-
- > span
- margin 0 8px
- line-height 32px
-</style>
diff --git a/src/server/web/app/common/views/components/poll-editor.vue b/src/server/web/app/common/views/components/poll-editor.vue
deleted file mode 100644
index 47d901d7b1..0000000000
--- a/src/server/web/app/common/views/components/poll-editor.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-<template>
-<div class="mk-poll-editor">
- <p class="caution" v-if="choices.length < 2">
- %fa:exclamation-triangle%%i18n:common.tags.mk-poll-editor.no-only-one-choice%
- </p>
- <ul ref="choices">
- <li v-for="(choice, i) in choices">
- <input :value="choice" @input="onInput(i, $event)" :placeholder="'%i18n:common.tags.mk-poll-editor.choice-n%'.replace('{}', i + 1)">
- <button @click="remove(i)" title="%i18n:common.tags.mk-poll-editor.remove%">
- %fa:times%
- </button>
- </li>
- </ul>
- <button class="add" v-if="choices.length < 10" @click="add">%i18n:common.tags.mk-poll-editor.add%</button>
- <button class="destroy" @click="destroy" title="%i18n:common.tags.mk-poll-editor.destroy%">
- %fa:times%
- </button>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- data() {
- return {
- choices: ['', '']
- };
- },
- watch: {
- choices() {
- this.$emit('updated');
- }
- },
- methods: {
- onInput(i, e) {
- Vue.set(this.choices, i, e.target.value);
- },
-
- add() {
- this.choices.push('');
- this.$nextTick(() => {
- (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus();
- });
- },
-
- remove(i) {
- this.choices = this.choices.filter((_, _i) => _i != i);
- },
-
- destroy() {
- this.$emit('destroyed');
- },
-
- get() {
- return {
- choices: this.choices.filter(choice => choice != '')
- }
- },
-
- set(data) {
- if (data.choices.length == 0) return;
- this.choices = data.choices;
- if (data.choices.length == 1) this.choices = this.choices.concat('');
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-poll-editor
- padding 8px
-
- > .caution
- margin 0 0 8px 0
- font-size 0.8em
- color #f00
-
- > [data-fa]
- margin-right 4px
-
- > ul
- display block
- margin 0
- padding 0
- list-style none
-
- > li
- display block
- margin 8px 0
- padding 0
- width 100%
-
- &:first-child
- margin-top 0
-
- &:last-child
- margin-bottom 0
-
- > input
- padding 6px 8px
- width 300px
- font-size 14px
- border solid 1px rgba($theme-color, 0.1)
- border-radius 4px
-
- &:hover
- border-color rgba($theme-color, 0.2)
-
- &:focus
- border-color rgba($theme-color, 0.5)
-
- > button
- padding 4px 8px
- color rgba($theme-color, 0.4)
-
- &:hover
- color rgba($theme-color, 0.6)
-
- &:active
- color darken($theme-color, 30%)
-
- > .add
- margin 8px 0 0 0
- vertical-align top
- color $theme-color
-
- > .destroy
- position absolute
- top 0
- right 0
- padding 4px 8px
- color rgba($theme-color, 0.4)
-
- &:hover
- color rgba($theme-color, 0.6)
-
- &:active
- color darken($theme-color, 30%)
-
-</style>
diff --git a/src/server/web/app/common/views/components/poll.vue b/src/server/web/app/common/views/components/poll.vue
deleted file mode 100644
index 711d89720e..0000000000
--- a/src/server/web/app/common/views/components/poll.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<template>
-<div class="mk-poll" :data-is-voted="isVoted">
- <ul>
- <li v-for="choice in poll.choices" :key="choice.id" @click="vote(choice.id)" :class="{ voted: choice.voted }" :title="!isVoted ? '%i18n:common.tags.mk-poll.vote-to%'.replace('{}', choice.text) : ''">
- <div class="backdrop" :style="{ 'width': (showResult ? (choice.votes / total * 100) : 0) + '%' }"></div>
- <span>
- <template v-if="choice.isVoted">%fa:check%</template>
- <span>{{ choice.text }}</span>
- <span class="votes" v-if="showResult">({{ '%i18n:common.tags.mk-poll.vote-count%'.replace('{}', choice.votes) }})</span>
- </span>
- </li>
- </ul>
- <p v-if="total > 0">
- <span>{{ '%i18n:common.tags.mk-poll.total-users%'.replace('{}', total) }}</span>
- <span>・</span>
- <a v-if="!isVoted" @click="toggleShowResult">{{ showResult ? '%i18n:common.tags.mk-poll.vote%' : '%i18n:common.tags.mk-poll.show-result%' }}</a>
- <span v-if="isVoted">%i18n:common.tags.mk-poll.voted%</span>
- </p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['post'],
- data() {
- return {
- showResult: false
- };
- },
- computed: {
- poll(): any {
- return this.post.poll;
- },
- total(): number {
- return this.poll.choices.reduce((a, b) => a + b.votes, 0);
- },
- isVoted(): boolean {
- return this.poll.choices.some(c => c.isVoted);
- }
- },
- created() {
- this.showResult = this.isVoted;
- },
- methods: {
- toggleShowResult() {
- this.showResult = !this.showResult;
- },
- vote(id) {
- if (this.poll.choices.some(c => c.isVoted)) return;
- (this as any).api('posts/polls/vote', {
- postId: this.post.id,
- choice: id
- }).then(() => {
- this.poll.choices.forEach(c => {
- if (c.id == id) {
- c.votes++;
- Vue.set(c, 'isVoted', true);
- }
- });
- this.showResult = true;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-poll
-
- > ul
- display block
- margin 0
- padding 0
- list-style none
-
- > li
- display block
- margin 4px 0
- padding 4px 8px
- width 100%
- border solid 1px #eee
- border-radius 4px
- overflow hidden
- cursor pointer
-
- &:hover
- background rgba(0, 0, 0, 0.05)
-
- &:active
- background rgba(0, 0, 0, 0.1)
-
- > .backdrop
- position absolute
- top 0
- left 0
- height 100%
- background $theme-color
- transition width 1s ease
-
- > span
- > [data-fa]
- margin-right 4px
-
- > .votes
- margin-left 4px
-
- > p
- a
- color inherit
-
- &[data-is-voted]
- > ul > li
- cursor default
-
- &:hover
- background transparent
-
- &:active
- background transparent
-
-</style>
diff --git a/src/server/web/app/common/views/components/post-html.ts b/src/server/web/app/common/views/components/post-html.ts
deleted file mode 100644
index 98da86617d..0000000000
--- a/src/server/web/app/common/views/components/post-html.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import Vue from 'vue';
-import * as emojilib from 'emojilib';
-import getAcct from '../../../../../common/user/get-acct';
-import { url } from '../../../config';
-import MkUrl from './url.vue';
-
-const flatten = list => list.reduce(
- (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
-);
-
-export default Vue.component('mk-post-html', {
- props: {
- ast: {
- type: Array,
- required: true
- },
- shouldBreak: {
- type: Boolean,
- default: true
- },
- i: {
- type: Object,
- default: null
- }
- },
- render(createElement) {
- const els = flatten((this as any).ast.map(token => {
- switch (token.type) {
- case 'text':
- const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
-
- if ((this as any).shouldBreak) {
- const x = text.split('\n')
- .map(t => t == '' ? [createElement('br')] : [createElement('span', t), createElement('br')]);
- x[x.length - 1].pop();
- return x;
- } else {
- return createElement('span', text.replace(/\n/g, ' '));
- }
-
- case 'bold':
- return createElement('strong', token.bold);
-
- case 'url':
- return createElement(MkUrl, {
- props: {
- url: token.content,
- target: '_blank'
- }
- });
-
- case 'link':
- return createElement('a', {
- attrs: {
- class: 'link',
- href: token.url,
- target: '_blank',
- title: token.url
- }
- }, token.title);
-
- case 'mention':
- return (createElement as any)('a', {
- attrs: {
- href: `${url}/@${getAcct(token)}`,
- target: '_blank',
- dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token)
- },
- directives: [{
- name: 'user-preview',
- value: token.content
- }]
- }, token.content);
-
- case 'hashtag':
- return createElement('a', {
- attrs: {
- href: `${url}/search?q=${token.content}`,
- target: '_blank'
- }
- }, token.content);
-
- case 'code':
- return createElement('pre', [
- createElement('code', {
- domProps: {
- innerHTML: token.html
- }
- })
- ]);
-
- case 'inline-code':
- return createElement('code', token.html);
-
- case 'quote':
- const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n');
-
- if ((this as any).shouldBreak) {
- const x = text2.split('\n')
- .map(t => [createElement('span', t), createElement('br')]);
- x[x.length - 1].pop();
- return createElement('div', {
- attrs: {
- class: 'quote'
- }
- }, x);
- } else {
- return createElement('span', {
- attrs: {
- class: 'quote'
- }
- }, text2.replace(/\n/g, ' '));
- }
-
- case 'emoji':
- const emoji = emojilib.lib[token.emoji];
- return createElement('span', emoji ? emoji.char : token.content);
-
- default:
- console.log('unknown ast type:', token.type);
- }
- }));
-
- const _els = [];
- els.forEach((el, i) => {
- if (el.tag == 'br') {
- if (els[i - 1].tag != 'div') {
- _els.push(el);
- }
- } else {
- _els.push(el);
- }
- });
-
- return createElement('span', _els);
- }
-});
diff --git a/src/server/web/app/common/views/components/post-menu.vue b/src/server/web/app/common/views/components/post-menu.vue
deleted file mode 100644
index 35116db7e2..0000000000
--- a/src/server/web/app/common/views/components/post-menu.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<template>
-<div class="mk-post-menu">
- <div class="backdrop" ref="backdrop" @click="close"></div>
- <div class="popover" :class="{ compact }" ref="popover">
- <button v-if="post.userId == os.i.id" @click="pin">%i18n:common.tags.mk-post-menu.pin%</button>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as anime from 'animejs';
-
-export default Vue.extend({
- props: ['post', 'source', 'compact'],
- mounted() {
- this.$nextTick(() => {
- const popover = this.$refs.popover as any;
-
- const rect = this.source.getBoundingClientRect();
- const width = popover.offsetWidth;
- const height = popover.offsetHeight;
-
- if (this.compact) {
- const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
- const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
- popover.style.left = (x - (width / 2)) + 'px';
- popover.style.top = (y - (height / 2)) + 'px';
- } else {
- const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
- const y = rect.top + window.pageYOffset + this.source.offsetHeight;
- popover.style.left = (x - (width / 2)) + 'px';
- popover.style.top = y + 'px';
- }
-
- anime({
- targets: this.$refs.backdrop,
- opacity: 1,
- duration: 100,
- easing: 'linear'
- });
-
- anime({
- targets: this.$refs.popover,
- opacity: 1,
- scale: [0.5, 1],
- duration: 500
- });
- });
- },
- methods: {
- pin() {
- (this as any).api('i/pin', {
- postId: this.post.id
- }).then(() => {
- this.$destroy();
- });
- },
-
- close() {
- (this.$refs.backdrop as any).style.pointerEvents = 'none';
- anime({
- targets: this.$refs.backdrop,
- opacity: 0,
- duration: 200,
- easing: 'linear'
- });
-
- (this.$refs.popover as any).style.pointerEvents = 'none';
- anime({
- targets: this.$refs.popover,
- opacity: 0,
- scale: 0.5,
- duration: 200,
- easing: 'easeInBack',
- complete: () => this.$destroy()
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-$border-color = rgba(27, 31, 35, 0.15)
-
-.mk-post-menu
- position initial
-
- > .backdrop
- position fixed
- top 0
- left 0
- z-index 10000
- width 100%
- height 100%
- background rgba(0, 0, 0, 0.1)
- opacity 0
-
- > .popover
- position absolute
- z-index 10001
- background #fff
- border 1px solid $border-color
- border-radius 4px
- box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
- transform scale(0.5)
- opacity 0
-
- $balloon-size = 16px
-
- &:not(.compact)
- margin-top $balloon-size
- transform-origin center -($balloon-size)
-
- &:before
- content ""
- display block
- position absolute
- top -($balloon-size * 2)
- left s('calc(50% - %s)', $balloon-size)
- border-top solid $balloon-size transparent
- border-left solid $balloon-size transparent
- border-right solid $balloon-size transparent
- border-bottom solid $balloon-size $border-color
-
- &:after
- content ""
- display block
- position absolute
- top -($balloon-size * 2) + 1.5px
- left s('calc(50% - %s)', $balloon-size)
- border-top solid $balloon-size transparent
- border-left solid $balloon-size transparent
- border-right solid $balloon-size transparent
- border-bottom solid $balloon-size #fff
-
- > button
- display block
- padding 16px
-
-</style>
diff --git a/src/server/web/app/common/views/components/reaction-icon.vue b/src/server/web/app/common/views/components/reaction-icon.vue
deleted file mode 100644
index 7d24f4f9e9..0000000000
--- a/src/server/web/app/common/views/components/reaction-icon.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<template>
-<span class="mk-reaction-icon">
- <img v-if="reaction == 'like'" src="/assets/reactions/like.png" alt="%i18n:common.reactions.like%">
- <img v-if="reaction == 'love'" src="/assets/reactions/love.png" alt="%i18n:common.reactions.love%">
- <img v-if="reaction == 'laugh'" src="/assets/reactions/laugh.png" alt="%i18n:common.reactions.laugh%">
- <img v-if="reaction == 'hmm'" src="/assets/reactions/hmm.png" alt="%i18n:common.reactions.hmm%">
- <img v-if="reaction == 'surprise'" src="/assets/reactions/surprise.png" alt="%i18n:common.reactions.surprise%">
- <img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
- <img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
- <img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
- <img v-if="reaction == 'pudding'" src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
-</span>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['reaction']
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-reaction-icon
- img
- vertical-align middle
- width 1em
- height 1em
-</style>
diff --git a/src/server/web/app/common/views/components/reaction-picker.vue b/src/server/web/app/common/views/components/reaction-picker.vue
deleted file mode 100644
index bcb6b2b965..0000000000
--- a/src/server/web/app/common/views/components/reaction-picker.vue
+++ /dev/null
@@ -1,191 +0,0 @@
-<template>
-<div class="mk-reaction-picker">
- <div class="backdrop" ref="backdrop" @click="close"></div>
- <div class="popover" :class="{ compact }" ref="popover">
- <p v-if="!compact">{{ title }}</p>
- <div>
- <button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
- <button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
- <button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
- <button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.hmm%"><mk-reaction-icon reaction='hmm'/></button>
- <button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.surprise%"><mk-reaction-icon reaction='surprise'/></button>
- <button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.congrats%"><mk-reaction-icon reaction='congrats'/></button>
- <button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.angry%"><mk-reaction-icon reaction='angry'/></button>
- <button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.confused%"><mk-reaction-icon reaction='confused'/></button>
- <button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.pudding%"><mk-reaction-icon reaction='pudding'/></button>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as anime from 'animejs';
-
-const placeholder = '%i18n:common.tags.mk-reaction-picker.choose-reaction%';
-
-export default Vue.extend({
- props: ['post', 'source', 'compact', 'cb'],
- data() {
- return {
- title: placeholder
- };
- },
- mounted() {
- this.$nextTick(() => {
- const popover = this.$refs.popover as any;
-
- const rect = this.source.getBoundingClientRect();
- const width = popover.offsetWidth;
- const height = popover.offsetHeight;
-
- if (this.compact) {
- const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
- const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
- popover.style.left = (x - (width / 2)) + 'px';
- popover.style.top = (y - (height / 2)) + 'px';
- } else {
- const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
- const y = rect.top + window.pageYOffset + this.source.offsetHeight;
- popover.style.left = (x - (width / 2)) + 'px';
- popover.style.top = y + 'px';
- }
-
- anime({
- targets: this.$refs.backdrop,
- opacity: 1,
- duration: 100,
- easing: 'linear'
- });
-
- anime({
- targets: this.$refs.popover,
- opacity: 1,
- scale: [0.5, 1],
- duration: 500
- });
- });
- },
- methods: {
- react(reaction) {
- (this as any).api('posts/reactions/create', {
- postId: this.post.id,
- reaction: reaction
- }).then(() => {
- if (this.cb) this.cb();
- this.$destroy();
- });
- },
- onMouseover(e) {
- this.title = e.target.title;
- },
- onMouseout(e) {
- this.title = placeholder;
- },
- close() {
- (this.$refs.backdrop as any).style.pointerEvents = 'none';
- anime({
- targets: this.$refs.backdrop,
- opacity: 0,
- duration: 200,
- easing: 'linear'
- });
-
- (this.$refs.popover as any).style.pointerEvents = 'none';
- anime({
- targets: this.$refs.popover,
- opacity: 0,
- scale: 0.5,
- duration: 200,
- easing: 'easeInBack',
- complete: () => this.$destroy()
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-$border-color = rgba(27, 31, 35, 0.15)
-
-.mk-reaction-picker
- position initial
-
- > .backdrop
- position fixed
- top 0
- left 0
- z-index 10000
- width 100%
- height 100%
- background rgba(0, 0, 0, 0.1)
- opacity 0
-
- > .popover
- position absolute
- z-index 10001
- background #fff
- border 1px solid $border-color
- border-radius 4px
- box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
- transform scale(0.5)
- opacity 0
-
- $balloon-size = 16px
-
- &:not(.compact)
- margin-top $balloon-size
- transform-origin center -($balloon-size)
-
- &:before
- content ""
- display block
- position absolute
- top -($balloon-size * 2)
- left s('calc(50% - %s)', $balloon-size)
- border-top solid $balloon-size transparent
- border-left solid $balloon-size transparent
- border-right solid $balloon-size transparent
- border-bottom solid $balloon-size $border-color
-
- &:after
- content ""
- display block
- position absolute
- top -($balloon-size * 2) + 1.5px
- left s('calc(50% - %s)', $balloon-size)
- border-top solid $balloon-size transparent
- border-left solid $balloon-size transparent
- border-right solid $balloon-size transparent
- border-bottom solid $balloon-size #fff
-
- > p
- display block
- margin 0
- padding 8px 10px
- font-size 14px
- color #586069
- border-bottom solid 1px #e1e4e8
-
- > div
- padding 4px
- width 240px
- text-align center
-
- > button
- padding 0
- width 40px
- height 40px
- font-size 24px
- border-radius 2px
-
- &:hover
- background #eee
-
- &:active
- background $theme-color
- box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
-
-</style>
diff --git a/src/server/web/app/common/views/components/reactions-viewer.vue b/src/server/web/app/common/views/components/reactions-viewer.vue
deleted file mode 100644
index 246451008f..0000000000
--- a/src/server/web/app/common/views/components/reactions-viewer.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<template>
-<div class="mk-reactions-viewer">
- <template v-if="reactions">
- <span v-if="reactions.like"><mk-reaction-icon reaction='like'/><span>{{ reactions.like }}</span></span>
- <span v-if="reactions.love"><mk-reaction-icon reaction='love'/><span>{{ reactions.love }}</span></span>
- <span v-if="reactions.laugh"><mk-reaction-icon reaction='laugh'/><span>{{ reactions.laugh }}</span></span>
- <span v-if="reactions.hmm"><mk-reaction-icon reaction='hmm'/><span>{{ reactions.hmm }}</span></span>
- <span v-if="reactions.surprise"><mk-reaction-icon reaction='surprise'/><span>{{ reactions.surprise }}</span></span>
- <span v-if="reactions.congrats"><mk-reaction-icon reaction='congrats'/><span>{{ reactions.congrats }}</span></span>
- <span v-if="reactions.angry"><mk-reaction-icon reaction='angry'/><span>{{ reactions.angry }}</span></span>
- <span v-if="reactions.confused"><mk-reaction-icon reaction='confused'/><span>{{ reactions.confused }}</span></span>
- <span v-if="reactions.pudding"><mk-reaction-icon reaction='pudding'/><span>{{ reactions.pudding }}</span></span>
- </template>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: ['post'],
- computed: {
- reactions(): number {
- return this.post.reactionCounts;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-reactions-viewer
- border-top dashed 1px #eee
- border-bottom dashed 1px #eee
- margin 4px 0
-
- &:empty
- display none
-
- > span
- margin-right 8px
-
- > .mk-reaction-icon
- font-size 1.4em
-
- > span
- margin-left 4px
- font-size 1.2em
- color #444
-
-</style>
diff --git a/src/server/web/app/common/views/components/signin.vue b/src/server/web/app/common/views/components/signin.vue
deleted file mode 100644
index 17154e6b31..0000000000
--- a/src/server/web/app/common/views/components/signin.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-<template>
-<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
- <label class="user-name">
- <input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="%i18n:common.tags.mk-signin.username%" autofocus required @change="onUsernameChange"/>%fa:at%
- </label>
- <label class="password">
- <input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required/>%fa:lock%
- </label>
- <label class="token" v-if="user && user.account.twoFactorEnabled">
- <input v-model="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required/>%fa:lock%
- </label>
- <button type="submit" :disabled="signing">{{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }}</button>
- もしくは <a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
-</form>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { apiUrl } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- signing: false,
- user: null,
- username: '',
- password: '',
- token: '',
- apiUrl,
- };
- },
- methods: {
- onUsernameChange() {
- (this as any).api('users/show', {
- username: this.username
- }).then(user => {
- this.user = user;
- });
- },
- onSubmit() {
- this.signing = true;
-
- (this as any).api('signin', {
- username: this.username,
- password: this.password,
- token: this.user && this.user.account.twoFactorEnabled ? this.token : undefined
- }).then(() => {
- location.reload();
- }).catch(() => {
- alert('something happened');
- this.signing = false;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-signin
- &.signing
- &, *
- cursor wait !important
-
- label
- display block
- margin 12px 0
-
- [data-fa]
- display block
- pointer-events none
- position absolute
- bottom 0
- top 0
- left 0
- z-index 1
- margin auto
- padding 0 16px
- height 1em
- color #898786
-
- input[type=text]
- input[type=password]
- input[type=number]
- user-select text
- display inline-block
- cursor auto
- padding 0 0 0 38px
- margin 0
- width 100%
- line-height 44px
- font-size 1em
- color rgba(0, 0, 0, 0.7)
- background #fff
- outline none
- border solid 1px #eee
- border-radius 4px
-
- &:hover
- background rgba(255, 255, 255, 0.7)
- border-color #ddd
-
- & + i
- color #797776
-
- &:focus
- background #fff
- border-color #ccc
-
- & + i
- color #797776
-
- [type=submit]
- cursor pointer
- padding 16px
- margin -6px 0 0 0
- width 100%
- font-size 1.2em
- color rgba(0, 0, 0, 0.5)
- outline none
- border none
- border-radius 0
- background transparent
- transition all .5s ease
-
- &:hover
- color $theme-color
- transition all .2s ease
-
- &:focus
- color $theme-color
- transition all .2s ease
-
- &:active
- color darken($theme-color, 30%)
- transition all .2s ease
-
- &:disabled
- opacity 0.7
-
-</style>
diff --git a/src/server/web/app/common/views/components/signup.vue b/src/server/web/app/common/views/components/signup.vue
deleted file mode 100644
index e77d849ade..0000000000
--- a/src/server/web/app/common/views/components/signup.vue
+++ /dev/null
@@ -1,287 +0,0 @@
-<template>
-<form class="mk-signup" @submit.prevent="onSubmit" autocomplete="off">
- <label class="username">
- <p class="caption">%fa:at%%i18n:common.tags.mk-signup.username%</p>
- <input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required @input="onChangeUsername"/>
- <p class="profile-page-url-preview" v-if="shouldShowProfileUrl">{{ `${url}/@${username}` }}</p>
- <p class="info" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%%i18n:common.tags.mk-signup.checking%</p>
- <p class="info" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.available%</p>
- <p class="info" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.unavailable%</p>
- <p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.error%</p>
- <p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.invalid-format%</p>
- <p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-short%</p>
- <p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-long%</p>
- </label>
- <label class="password">
- <p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%</p>
- <input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signup.password-placeholder%" autocomplete="off" required @input="onChangePassword"/>
- <div class="meter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
- <div class="value" ref="passwordMetar"></div>
- </div>
- <p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.weak-password%</p>
- <p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.normal-password%</p>
- <p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.strong-password%</p>
- </label>
- <label class="retype-password">
- <p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%(%i18n:common.tags.mk-signup.retype%)</p>
- <input v-model="retypedPassword" type="password" placeholder="%i18n:common.tags.mk-signup.retype-placeholder%" autocomplete="off" required @input="onChangePasswordRetype"/>
- <p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.password-matched%</p>
- <p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.password-not-matched%</p>
- </label>
- <label class="recaptcha">
- <p class="caption"><template v-if="recaptchaed">%fa:toggle-on%</template><template v-if="!recaptchaed">%fa:toggle-off%</template>%i18n:common.tags.mk-signup.recaptcha%</p>
- <div class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" :data-sitekey="recaptchaSitekey"></div>
- </label>
- <label class="agree-tou">
- <input name="agree-tou" type="checkbox" autocomplete="off" required/>
- <p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
- </label>
- <button type="submit">%i18n:common.tags.mk-signup.create%</button>
-</form>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-const getPasswordStrength = require('syuilo-password-strength');
-import { url, docsUrl, lang, recaptchaSitekey } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- username: '',
- password: '',
- retypedPassword: '',
- url,
- touUrl: `${docsUrl}/${lang}/tou`,
- recaptchaSitekey,
- recaptchaed: false,
- usernameState: null,
- passwordStrength: '',
- passwordRetypeState: null
- }
- },
- computed: {
- shouldShowProfileUrl(): boolean {
- return (this.username != '' &&
- this.usernameState != 'invalid-format' &&
- this.usernameState != 'min-range' &&
- this.usernameState != 'max-range');
- }
- },
- methods: {
- onChangeUsername() {
- if (this.username == '') {
- this.usernameState = null;
- return;
- }
-
- const err =
- !this.username.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
- this.username.length < 3 ? 'min-range' :
- this.username.length > 20 ? 'max-range' :
- null;
-
- if (err) {
- this.usernameState = err;
- return;
- }
-
- this.usernameState = 'wait';
-
- (this as any).api('username/available', {
- username: this.username
- }).then(result => {
- this.usernameState = result.available ? 'ok' : 'unavailable';
- }).catch(err => {
- this.usernameState = 'error';
- });
- },
- onChangePassword() {
- if (this.password == '') {
- this.passwordStrength = '';
- return;
- }
-
- const strength = getPasswordStrength(this.password);
- this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
- (this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
- },
- onChangePasswordRetype() {
- if (this.retypedPassword == '') {
- this.passwordRetypeState = null;
- return;
- }
-
- this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match';
- },
- onSubmit() {
- (this as any).api('signup', {
- username: this.username,
- password: this.password,
- 'g-recaptcha-response': (window as any).grecaptcha.getResponse()
- }).then(() => {
- (this as any).api('signin', {
- username: this.username,
- password: this.password
- }).then(() => {
- location.href = '/';
- });
- }).catch(() => {
- alert('%i18n:common.tags.mk-signup.some-error%');
-
- (window as any).grecaptcha.reset();
- this.recaptchaed = false;
- });
- }
- },
- created() {
- (window as any).onRecaptchaed = () => {
- this.recaptchaed = true;
- };
-
- (window as any).onRecaptchaExpired = () => {
- this.recaptchaed = false;
- };
- },
- mounted() {
- const head = document.getElementsByTagName('head')[0];
- const script = document.createElement('script');
- script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
- head.appendChild(script);
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-signup
- min-width 302px
-
- label
- display block
- margin 0 0 16px 0
-
- > .caption
- margin 0 0 4px 0
- color #828888
- font-size 0.95em
-
- > [data-fa]
- margin-right 0.25em
- color #96adac
-
- > .info
- display block
- margin 4px 0
- font-size 0.8em
-
- > [data-fa]
- margin-right 0.3em
-
- &.username
- .profile-page-url-preview
- display block
- margin 4px 8px 0 4px
- font-size 0.8em
- color #888
-
- &:empty
- display none
-
- &:not(:empty) + .info
- margin-top 0
-
- &.password
- .meter
- display block
- margin-top 8px
- width 100%
- height 8px
-
- &[data-strength='']
- display none
-
- &[data-strength='low']
- > .value
- background #d73612
-
- &[data-strength='medium']
- > .value
- background #d7ca12
-
- &[data-strength='high']
- > .value
- background #61bb22
-
- > .value
- display block
- width 0%
- height 100%
- background transparent
- border-radius 4px
- transition all 0.1s ease
-
- [type=text], [type=password]
- user-select text
- display inline-block
- cursor auto
- padding 0 12px
- margin 0
- width 100%
- line-height 44px
- font-size 1em
- color #333 !important
- background #fff !important
- outline none
- border solid 1px rgba(0, 0, 0, 0.1)
- border-radius 4px
- box-shadow 0 0 0 114514px #fff inset
- transition all .3s ease
-
- &:hover
- border-color rgba(0, 0, 0, 0.2)
- transition all .1s ease
-
- &:focus
- color $theme-color !important
- border-color $theme-color
- box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
- transition all 0s ease
-
- &:disabled
- opacity 0.5
-
- .agree-tou
- padding 4px
- border-radius 4px
-
- &:hover
- background #f4f4f4
-
- &:active
- background #eee
-
- &, *
- cursor pointer
-
- p
- display inline
- color #555
-
- button
- margin 0
- padding 16px
- width 100%
- font-size 1em
- color #fff
- background $theme-color
- border-radius 3px
-
- &:hover
- background lighten($theme-color, 5%)
-
- &:active
- background darken($theme-color, 5%)
-
-</style>
diff --git a/src/server/web/app/common/views/components/special-message.vue b/src/server/web/app/common/views/components/special-message.vue
deleted file mode 100644
index 2fd4d6515e..0000000000
--- a/src/server/web/app/common/views/components/special-message.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<template>
-<div class="mk-special-message">
- <p v-if="m == 1 && d == 1">%i18n:common.tags.mk-special-message.new-year%</p>
- <p v-if="m == 12 && d == 25">%i18n:common.tags.mk-special-message.christmas%</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- data() {
- return {
- now: new Date()
- };
- },
- computed: {
- d(): number {
- return this.now.getDate();
- },
- m(): number {
- return this.now.getMonth() + 1;
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-special-message
- &:empty
- display none
-
- > p
- margin 0
- padding 4px
- text-align center
- font-size 14px
- font-weight bold
- text-transform uppercase
- color #fff
- background #ff1036
-
-</style>
diff --git a/src/server/web/app/common/views/components/stream-indicator.vue b/src/server/web/app/common/views/components/stream-indicator.vue
deleted file mode 100644
index 1f18fa76ed..0000000000
--- a/src/server/web/app/common/views/components/stream-indicator.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<template>
-<div class="mk-stream-indicator">
- <p v-if=" stream.state == 'initializing' ">
- %fa:spinner .pulse%
- <span>%i18n:common.tags.mk-stream-indicator.connecting%<mk-ellipsis/></span>
- </p>
- <p v-if=" stream.state == 'reconnecting' ">
- %fa:spinner .pulse%
- <span>%i18n:common.tags.mk-stream-indicator.reconnecting%<mk-ellipsis/></span>
- </p>
- <p v-if=" stream.state == 'connected' ">
- %fa:check%
- <span>%i18n:common.tags.mk-stream-indicator.connected%</span>
- </p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import * as anime from 'animejs';
-
-export default Vue.extend({
- computed: {
- stream() {
- return (this as any).os.stream;
- }
- },
- created() {
- (this as any).os.stream.on('_connected_', this.onConnected);
- (this as any).os.stream.on('_disconnected_', this.onDisconnected);
-
- this.$nextTick(() => {
- if (this.stream.state == 'connected') {
- this.$el.style.opacity = '0';
- }
- });
- },
- beforeDestroy() {
- (this as any).os.stream.off('_connected_', this.onConnected);
- (this as any).os.stream.off('_disconnected_', this.onDisconnected);
- },
- methods: {
- onConnected() {
- setTimeout(() => {
- anime({
- targets: this.$el,
- opacity: 0,
- easing: 'linear',
- duration: 200
- });
- }, 1000);
- },
- onDisconnected() {
- anime({
- targets: this.$el,
- opacity: 1,
- easing: 'linear',
- duration: 100
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-stream-indicator
- pointer-events none
- position fixed
- z-index 16384
- bottom 8px
- right 8px
- margin 0
- padding 6px 12px
- font-size 0.9em
- color #fff
- background rgba(0, 0, 0, 0.8)
- border-radius 4px
-
- > p
- display block
- margin 0
-
- > [data-fa]
- margin-right 0.25em
-
-</style>
diff --git a/src/server/web/app/common/views/components/switch.vue b/src/server/web/app/common/views/components/switch.vue
deleted file mode 100644
index 19a4adc3de..0000000000
--- a/src/server/web/app/common/views/components/switch.vue
+++ /dev/null
@@ -1,190 +0,0 @@
-<template>
-<div
- class="mk-switch"
- :class="{ disabled, checked }"
- role="switch"
- :aria-checked="checked"
- :aria-disabled="disabled"
- @click="switchValue"
- @mouseover="mouseenter"
->
- <input
- type="checkbox"
- @change="handleChange"
- ref="input"
- :disabled="disabled"
- @keydown.enter="switchValue"
- >
- <span class="button">
- <span :style="{ transform }"></span>
- </span>
- <span class="label">
- <span :aria-hidden="!checked">{{ text }}</span>
- <p :aria-hidden="!checked">
- <slot></slot>
- </p>
- </span>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
- props: {
- value: {
- type: Boolean,
- default: false
- },
- disabled: {
- type: Boolean,
- default: false
- },
- text: String
- },/*
- created() {
- if (!~[true, false].indexOf(this.value)) {
- this.$emit('input', false);
- }
- },*/
- computed: {
- checked(): boolean {
- return this.value;
- },
- transform(): string {
- return this.checked ? 'translate3d(20px, 0, 0)' : '';
- }
- },
- watch: {
- value() {
- (this.$el).style.transition = 'all 0.3s';
- (this.$refs.input as any).checked = this.checked;
- }
- },
- mounted() {
- (this.$refs.input as any).checked = this.checked;
- },
- methods: {
- mouseenter() {
- (this.$el).style.transition = 'all 0s';
- },
- handleChange() {
- (this.$el).style.transition = 'all 0.3s';
- this.$emit('input', !this.checked);
- this.$emit('change', !this.checked);
- this.$nextTick(() => {
- // set input's checked property
- // in case parent refuses to change component's value
- (this.$refs.input as any).checked = this.checked;
- });
- },
- switchValue() {
- !this.disabled && this.handleChange();
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-switch
- display flex
- margin 12px 0
- cursor pointer
- transition all 0.3s
-
- > *
- user-select none
-
- &.disabled
- opacity 0.6
- cursor not-allowed
-
- &.checked
- > .button
- background-color $theme-color
- border-color $theme-color
-
- > .label
- > span
- color $theme-color
-
- &:hover
- > .label
- > span
- color darken($theme-color, 10%)
-
- > .button
- background darken($theme-color, 10%)
- border-color darken($theme-color, 10%)
-
- &:hover
- > .label
- > span
- color #2e3338
-
- > .button
- background #ced2da
- border-color #ced2da
-
- > input
- position absolute
- width 0
- height 0
- opacity 0
- margin 0
-
- &:focus + .button
- &:after
- content ""
- pointer-events none
- position absolute
- top -5px
- right -5px
- bottom -5px
- left -5px
- border 2px solid rgba($theme-color, 0.3)
- border-radius 14px
-
- > .button
- display inline-block
- margin 0
- width 40px
- min-width 40px
- height 20px
- min-height 20px
- background #dcdfe6
- border 1px solid #dcdfe6
- outline none
- border-radius 10px
- transition inherit
-
- > *
- position absolute
- top 1px
- left 1px
- border-radius 100%
- transition transform 0.3s
- width 16px
- height 16px
- background-color #fff
-
- > .label
- margin-left 8px
- display block
- font-size 15px
- cursor pointer
- transition inherit
-
- > span
- display block
- line-height 20px
- color #4a535a
- transition inherit
-
- > p
- margin 0
- //font-size 90%
- color #9daab3
-
-</style>
diff --git a/src/server/web/app/common/views/components/time.vue b/src/server/web/app/common/views/components/time.vue
deleted file mode 100644
index 6e0d2b0dcb..0000000000
--- a/src/server/web/app/common/views/components/time.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-<time class="mk-time">
- <span v-if=" mode == 'relative' ">{{ relative }}</span>
- <span v-if=" mode == 'absolute' ">{{ absolute }}</span>
- <span v-if=" mode == 'detail' ">{{ absolute }} ({{ relative }})</span>
-</time>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- time: {
- type: [Date, String],
- required: true
- },
- mode: {
- type: String,
- default: 'relative'
- }
- },
- data() {
- return {
- tickId: null,
- now: new Date()
- };
- },
- computed: {
- _time(): Date {
- return typeof this.time == 'string' ? new Date(this.time) : this.time;
- },
- absolute(): string {
- const time = this._time;
- return (
- time.getFullYear() + '年' +
- (time.getMonth() + 1) + '月' +
- time.getDate() + '日' +
- ' ' +
- time.getHours() + '時' +
- time.getMinutes() + '分');
- },
- relative(): string {
- const time = this._time;
- const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/;
- return (
- ago >= 31536000 ? '%i18n:common.time.years_ago%' .replace('{}', (~~(ago / 31536000)).toString()) :
- ago >= 2592000 ? '%i18n:common.time.months_ago%' .replace('{}', (~~(ago / 2592000)).toString()) :
- ago >= 604800 ? '%i18n:common.time.weeks_ago%' .replace('{}', (~~(ago / 604800)).toString()) :
- ago >= 86400 ? '%i18n:common.time.days_ago%' .replace('{}', (~~(ago / 86400)).toString()) :
- ago >= 3600 ? '%i18n:common.time.hours_ago%' .replace('{}', (~~(ago / 3600)).toString()) :
- ago >= 60 ? '%i18n:common.time.minutes_ago%'.replace('{}', (~~(ago / 60)).toString()) :
- ago >= 10 ? '%i18n:common.time.seconds_ago%'.replace('{}', (~~(ago % 60)).toString()) :
- ago >= 0 ? '%i18n:common.time.just_now%' :
- ago < 0 ? '%i18n:common.time.future%' :
- '%i18n:common.time.unknown%');
- }
- },
- created() {
- if (this.mode == 'relative' || this.mode == 'detail') {
- this.tick();
- this.tickId = setInterval(this.tick, 1000);
- }
- },
- destroyed() {
- if (this.mode === 'relative' || this.mode === 'detail') {
- clearInterval(this.tickId);
- }
- },
- methods: {
- tick() {
- this.now = new Date();
- }
- }
-});
-</script>
diff --git a/src/server/web/app/common/views/components/timer.vue b/src/server/web/app/common/views/components/timer.vue
deleted file mode 100644
index a3c4f01b77..0000000000
--- a/src/server/web/app/common/views/components/timer.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<template>
-<time class="mk-time">
- {{ hh }}:{{ mm }}:{{ ss }}
-</time>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: {
- time: {
- type: [Date, String],
- required: true
- }
- },
- data() {
- return {
- tickId: null,
- hh: null,
- mm: null,
- ss: null
- };
- },
- computed: {
- _time(): Date {
- return typeof this.time == 'string' ? new Date(this.time) : this.time;
- }
- },
- created() {
- this.tick();
- this.tickId = setInterval(this.tick, 1000);
- },
- destroyed() {
- clearInterval(this.tickId);
- },
- methods: {
- tick() {
- const now = new Date().getTime();
- const start = this._time.getTime();
- const ago = Math.floor((now - start) / 1000);
-
- this.hh = Math.floor(ago / (60 * 60)).toString().padStart(2, '0');
- this.mm = Math.floor(ago / 60).toString().padStart(2, '0');
- this.ss = (ago % 60).toString().padStart(2, '0');
- }
- }
-});
-</script>
diff --git a/src/server/web/app/common/views/components/twitter-setting.vue b/src/server/web/app/common/views/components/twitter-setting.vue
deleted file mode 100644
index 082d2b435d..0000000000
--- a/src/server/web/app/common/views/components/twitter-setting.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<template>
-<div class="mk-twitter-setting">
- <p>%i18n:common.tags.mk-twitter-setting.description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p>
- <p class="account" v-if="os.i.account.twitter" :title="`Twitter ID: ${os.i.account.twitter.userId}`">%i18n:common.tags.mk-twitter-setting.connected-to%: <a :href="`https://twitter.com/${os.i.account.twitter.screenName}`" target="_blank">@{{ os.i.account.twitter.screenName }}</a></p>
- <p>
- <a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.account.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }}</a>
- <span v-if="os.i.account.twitter"> or </span>
- <a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.account.twitter" @click.prevent="disconnect">%i18n:common.tags.mk-twitter-setting.disconnect%</a>
- </p>
- <p class="id" v-if="os.i.account.twitter">Twitter ID: {{ os.i.account.twitter.userId }}</p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { apiUrl, docsUrl } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- form: null,
- apiUrl,
- docsUrl
- };
- },
- mounted() {
- this.$watch('os.i', () => {
- if ((this as any).os.i.account.twitter) {
- if (this.form) this.form.close();
- }
- }, {
- deep: true
- });
- },
- methods: {
- connect() {
- this.form = window.open(apiUrl + '/connect/twitter',
- 'twitter_connect_window',
- 'height=570, width=520');
- },
-
- disconnect() {
- window.open(apiUrl + '/disconnect/twitter',
- 'twitter_disconnect_window',
- 'height=570, width=520');
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-twitter-setting
- color #4a535a
-
- .account
- border solid 1px #e1e8ed
- border-radius 4px
- padding 16px
-
- a
- font-weight bold
- color inherit
-
- .id
- color #8899a6
-</style>
diff --git a/src/server/web/app/common/views/components/uploader.vue b/src/server/web/app/common/views/components/uploader.vue
deleted file mode 100644
index c74a1edb41..0000000000
--- a/src/server/web/app/common/views/components/uploader.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-<template>
-<div class="mk-uploader">
- <ol v-if="uploads.length > 0">
- <li v-for="ctx in uploads" :key="ctx.id">
- <div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
- <p class="name">%fa:spinner .pulse%{{ ctx.name }}</p>
- <p class="status">
- <span class="initing" v-if="ctx.progress == undefined">%i18n:common.tags.mk-uploader.waiting%<mk-ellipsis/></span>
- <span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
- <span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
- </p>
- <progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress>
- <div class="progress initing" v-if="ctx.progress == undefined"></div>
- <div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div>
- </li>
- </ol>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { apiUrl } from '../../../config';
-
-export default Vue.extend({
- data() {
- return {
- uploads: []
- };
- },
- methods: {
- upload(file, folder) {
- if (folder && typeof folder == 'object') folder = folder.id;
-
- const id = Math.random();
-
- const ctx = {
- id: id,
- name: file.name || 'untitled',
- progress: undefined,
- img: undefined
- };
-
- this.uploads.push(ctx);
- this.$emit('change', this.uploads);
-
- const reader = new FileReader();
- reader.onload = (e: any) => {
- ctx.img = e.target.result;
- };
- reader.readAsDataURL(file);
-
- const data = new FormData();
- data.append('i', (this as any).os.i.account.token);
- data.append('file', file);
-
- if (folder) data.append('folderId', folder);
-
- const xhr = new XMLHttpRequest();
- xhr.open('POST', apiUrl + '/drive/files/create', true);
- xhr.onload = (e: any) => {
- const driveFile = JSON.parse(e.target.response);
-
- this.$emit('uploaded', driveFile);
-
- this.uploads = this.uploads.filter(x => x.id != id);
- this.$emit('change', this.uploads);
- };
-
- xhr.upload.onprogress = e => {
- if (e.lengthComputable) {
- if (ctx.progress == undefined) ctx.progress = {};
- ctx.progress.max = e.total;
- ctx.progress.value = e.loaded;
- }
- };
-
- xhr.send(data);
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-uploader
- overflow auto
-
- &:empty
- display none
-
- > ol
- display block
- margin 0
- padding 0
- list-style none
-
- > li
- display block
- margin 8px 0 0 0
- padding 0
- height 36px
- box-shadow 0 -1px 0 rgba($theme-color, 0.1)
- border-top solid 8px transparent
-
- &:first-child
- margin 0
- box-shadow none
- border-top none
-
- > .img
- display block
- position absolute
- top 0
- left 0
- width 36px
- height 36px
- background-size cover
- background-position center center
-
- > .name
- display block
- position absolute
- top 0
- left 44px
- margin 0
- padding 0
- max-width 256px
- font-size 0.8em
- color rgba($theme-color, 0.7)
- white-space nowrap
- text-overflow ellipsis
- overflow hidden
-
- > [data-fa]
- margin-right 4px
-
- > .status
- display block
- position absolute
- top 0
- right 0
- margin 0
- padding 0
- font-size 0.8em
-
- > .initing
- color rgba($theme-color, 0.5)
-
- > .kb
- color rgba($theme-color, 0.5)
-
- > .percentage
- display inline-block
- width 48px
- text-align right
-
- color rgba($theme-color, 0.7)
-
- &:after
- content '%'
-
- > progress
- display block
- position absolute
- bottom 0
- right 0
- margin 0
- width calc(100% - 44px)
- height 8px
- background transparent
- border none
- border-radius 4px
- overflow hidden
-
- &::-webkit-progress-value
- background $theme-color
-
- &::-webkit-progress-bar
- background rgba($theme-color, 0.1)
-
- > .progress
- display block
- position absolute
- bottom 0
- right 0
- margin 0
- width calc(100% - 44px)
- height 8px
- border none
- border-radius 4px
- background linear-gradient(
- 45deg,
- lighten($theme-color, 30%) 25%,
- $theme-color 25%,
- $theme-color 50%,
- lighten($theme-color, 30%) 50%,
- lighten($theme-color, 30%) 75%,
- $theme-color 75%,
- $theme-color
- )
- background-size 32px 32px
- animation bg 1.5s linear infinite
-
- &.initing
- opacity 0.3
-
- @keyframes bg
- from {background-position: 0 0;}
- to {background-position: -64px 32px;}
-
-</style>
diff --git a/src/server/web/app/common/views/components/url-preview.vue b/src/server/web/app/common/views/components/url-preview.vue
deleted file mode 100644
index e91e510550..0000000000
--- a/src/server/web/app/common/views/components/url-preview.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-<template>
-<iframe v-if="youtubeId" type="text/html" height="250"
- :src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
- frameborder="0"/>
-<div v-else>
- <a class="mk-url-preview" :href="url" target="_blank" :title="url" v-if="!fetching">
- <div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
- <article>
- <header>
- <h1>{{ title }}</h1>
- </header>
- <p>{{ description }}</p>
- <footer>
- <img class="icon" v-if="icon" :src="icon"/>
- <p>{{ sitename }}</p>
- </footer>
- </article>
- </a>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import { url as misskeyUrl } from '../../../config';
-
-export default Vue.extend({
- props: ['url'],
- data() {
- return {
- fetching: true,
- title: null,
- description: null,
- thumbnail: null,
- icon: null,
- sitename: null,
- youtubeId: null,
- misskeyUrl
- };
- },
- created() {
- const url = new URL(this.url);
-
- if (url.hostname == 'www.youtube.com') {
- this.youtubeId = url.searchParams.get('v');
- } else if (url.hostname == 'youtu.be') {
- this.youtubeId = url.pathname;
- } else {
- fetch('/api:url?url=' + this.url).then(res => {
- res.json().then(info => {
- this.title = info.title;
- this.description = info.description;
- this.thumbnail = info.thumbnail;
- this.icon = info.icon;
- this.sitename = info.sitename;
-
- this.fetching = false;
- });
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-iframe
- width 100%
-
-.mk-url-preview
- display block
- font-size 16px
- border solid 1px #eee
- border-radius 4px
- overflow hidden
-
- &:hover
- text-decoration none
- border-color #ddd
-
- > article > header > h1
- text-decoration underline
-
- > .thumbnail
- position absolute
- width 100px
- height 100%
- background-position center
- background-size cover
-
- & + article
- left 100px
- width calc(100% - 100px)
-
- > article
- padding 16px
-
- > header
- margin-bottom 8px
-
- > h1
- margin 0
- font-size 1em
- color #555
-
- > p
- margin 0
- color #777
- font-size 0.8em
-
- > footer
- margin-top 8px
- height 16px
-
- > img
- display inline-block
- width 16px
- height 16px
- margin-right 4px
- vertical-align top
-
- > p
- display inline-block
- margin 0
- color #666
- font-size 0.8em
- line-height 16px
- vertical-align top
-
- @media (max-width 500px)
- font-size 8px
- border none
-
- > .thumbnail
- width 70px
-
- & + article
- left 70px
- width calc(100% - 70px)
-
- > article
- padding 8px
-
-</style>
diff --git a/src/server/web/app/common/views/components/url.vue b/src/server/web/app/common/views/components/url.vue
deleted file mode 100644
index 14d4fc82f3..0000000000
--- a/src/server/web/app/common/views/components/url.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<template>
-<a class="mk-url" :href="url" :target="target">
- <span class="schema">{{ schema }}//</span>
- <span class="hostname">{{ hostname }}</span>
- <span class="port" v-if="port != ''">:{{ port }}</span>
- <span class="pathname" v-if="pathname != ''">{{ pathname }}</span>
- <span class="query">{{ query }}</span>
- <span class="hash">{{ hash }}</span>
- %fa:external-link-square-alt%
-</a>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
- props: ['url', 'target'],
- data() {
- return {
- schema: null,
- hostname: null,
- port: null,
- pathname: null,
- query: null,
- hash: null
- };
- },
- created() {
- const url = new URL(this.url);
-
- this.schema = url.protocol;
- this.hostname = url.hostname;
- this.port = url.port;
- this.pathname = url.pathname;
- this.query = url.search;
- this.hash = url.hash;
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-url
- word-break break-all
-
- > [data-fa]
- padding-left 2px
- font-size .9em
- font-weight 400
- font-style normal
-
- > .schema
- opacity 0.5
-
- > .hostname
- font-weight bold
-
- > .pathname
- opacity 0.8
-
- > .query
- opacity 0.5
-
- > .hash
- font-style italic
-
-</style>
diff --git a/src/server/web/app/common/views/components/welcome-timeline.vue b/src/server/web/app/common/views/components/welcome-timeline.vue
deleted file mode 100644
index 8f6199732a..0000000000
--- a/src/server/web/app/common/views/components/welcome-timeline.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<template>
-<div class="mk-welcome-timeline">
- <div v-for="post in posts">
- <router-link class="avatar-anchor" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">
- <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
- </router-link>
- <div class="body">
- <header>
- <router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ post.user.name }}</router-link>
- <span class="username">@{{ getAcct(post.user) }}</span>
- <div class="info">
- <router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`">
- <mk-time :time="post.createdAt"/>
- </router-link>
- </div>
- </header>
- <div class="text">
- <mk-post-html :ast="post.ast"/>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import getAcct from '../../../../../common/user/get-acct';
-
-export default Vue.extend({
- data() {
- return {
- fetching: true,
- posts: []
- };
- },
- mounted() {
- this.fetch();
- },
- methods: {
- getAcct,
- fetch(cb?) {
- this.fetching = true;
- (this as any).api('posts', {
- reply: false,
- repost: false,
- media: false,
- poll: false,
- bot: false
- }).then(posts => {
- this.posts = posts;
- this.fetching = false;
- });
- }
- }
-});
-</script>
-
-<style lang="stylus" scoped>
-.mk-welcome-timeline
- background #fff
-
- > div
- padding 16px
- overflow-wrap break-word
- font-size .9em
- color #4C4C4C
- border-bottom 1px solid rgba(0, 0, 0, 0.05)
-
- &:after
- content ""
- display block
- clear both
-
- > .avatar-anchor
- display block
- float left
- position -webkit-sticky
- position sticky
- top 16px
-
- > img
- display block
- width 42px
- height 42px
- border-radius 6px
-
- > .body
- float right
- width calc(100% - 42px)
- padding-left 12px
-
- > header
- display flex
- align-items center
- margin-bottom 4px
- white-space nowrap
-
- > .name
- display block
- margin 0 .5em 0 0
- padding 0
- overflow hidden
- font-weight bold
- text-overflow ellipsis
- color #627079
-
- > .username
- margin 0 .5em 0 0
- color #ccc
-
- > .info
- margin-left auto
- font-size 0.9em
-
- > .created-at
- color #c0c0c0
-
-</style>