整理した

This commit is contained in:
syuilo
2018-03-29 20:32:18 +09:00
parent 8a279a4656
commit cf33e483f7
552 changed files with 360 additions and 1311 deletions

View File

@ -0,0 +1,31 @@
<template>
<mk-activity
:design="props.design"
:init-view="props.view"
:user="os.i"
@view-changed="viewChanged"/>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'activity',
props: () => ({
design: 0,
view: 0
})
}).extend({
methods: {
func() {
if (this.props.design == 2) {
this.props.design = 0;
} else {
this.props.design++;
}
},
viewChanged(view) {
this.props.view = view;
}
}
});
</script>

View File

@ -0,0 +1,67 @@
<template>
<div class="form">
<input v-model="text" :disabled="wait" @keydown="onKeydown" placeholder="書いて">
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
data() {
return {
text: '',
wait: false
};
},
methods: {
onKeydown(e) {
if (e.which == 10 || e.which == 13) this.post();
},
post() {
this.wait = true;
let reply = null;
if (/^>>([0-9]+) /.test(this.text)) {
const index = this.text.match(/^>>([0-9]+) /)[1];
reply = (this.$parent as any).posts.find(p => p.index.toString() == index);
this.text = this.text.replace(/^>>([0-9]+) /, '');
}
(this as any).api('posts/create', {
text: this.text,
replyId: reply ? reply.id : undefined,
channelId: (this.$parent as any).channel.id
}).then(data => {
this.text = '';
}).catch(err => {
alert('失敗した');
}).then(() => {
this.wait = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.form
width 100%
height 38px
padding 4px
border-top solid 1px #ddd
> input
padding 0 8px
width 100%
height 100%
font-size 14px
color #55595c
border solid 1px #dadada
border-radius 4px
&:hover
&:focus
border-color #aeaeae
</style>

View File

@ -0,0 +1,71 @@
<template>
<div class="post">
<header>
<a class="index" @click="reply">{{ post.index }}:</a>
<router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ post.user.name }}</b></router-link>
<span>ID:<i>{{ acct }}</i></span>
</header>
<div>
<a v-if="post.reply">&gt;&gt;{{ post.reply.index }}</a>
{{ post.text }}
<div class="media" v-if="post.media">
<a v-for="file in post.media" :href="file.url" target="_blank">
<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
</a>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import getAcct from '../../../../../common/user/get-acct';
export default Vue.extend({
props: ['post'],
computed: {
acct() {
return getAcct(this.post.user);
}
},
methods: {
reply() {
this.$emit('reply', this.post);
}
}
});
</script>
<style lang="stylus" scoped>
.post
margin 0
padding 0
color #444
> header
position -webkit-sticky
position sticky
z-index 1
top 0
padding 8px 4px 4px 16px
background rgba(255, 255, 255, 0.9)
> .index
margin-right 0.25em
> .name
margin-right 0.5em
color #008000
> div
padding 0 16px 16px 16px
> .media
> a
display inline-block
> img
max-width 100%
vertical-align bottom
</style>

View File

@ -0,0 +1,106 @@
<template>
<div class="channel">
<p v-if="fetching">読み込み中<mk-ellipsis/></p>
<div v-if="!fetching" ref="posts" class="posts">
<p v-if="posts.length == 0">まだ投稿がありません</p>
<x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/>
</div>
<x-form class="form" ref="form"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import ChannelStream from '../../../common/scripts/streaming/channel';
import XForm from './channel.channel.form.vue';
import XPost from './channel.channel.post.vue';
export default Vue.extend({
components: {
XForm,
XPost
},
props: ['channel'],
data() {
return {
fetching: true,
posts: [],
connection: null
};
},
watch: {
channel() {
this.zap();
}
},
mounted() {
this.zap();
},
beforeDestroy() {
this.disconnect();
},
methods: {
zap() {
this.fetching = true;
(this as any).api('channels/posts', {
channelId: this.channel.id
}).then(posts => {
this.posts = posts;
this.fetching = false;
this.$nextTick(() => {
this.scrollToBottom();
});
this.disconnect();
this.connection = new ChannelStream((this as any).os, this.channel.id);
this.connection.on('post', this.onPost);
});
},
disconnect() {
if (this.connection) {
this.connection.off('post', this.onPost);
this.connection.close();
}
},
onPost(post) {
this.posts.unshift(post);
this.scrollToBottom();
},
scrollToBottom() {
(this.$refs.posts as any).scrollTop = (this.$refs.posts as any).scrollHeight;
},
reply(post) {
(this.$refs.form as any).text = `>>${ post.index } `;
}
}
});
</script>
<style lang="stylus" scoped>
.channel
> p
margin 0
padding 16px
text-align center
color #aaa
> .posts
height calc(100% - 38px)
overflow auto
font-size 0.9em
> .post
border-bottom solid 1px #eee
&:last-child
border-bottom none
> .form
position absolute
left 0
bottom 0
</style>

View File

@ -0,0 +1,107 @@
<template>
<div class="mkw-channel">
<template v-if="!props.compact">
<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' }}</p>
<button @click="settings" title="%i18n:desktop.tags.mk-channel-home-widget.settings%">%fa:cog%</button>
</template>
<p class="get-started" v-if="props.channel == null">%i18n:desktop.tags.mk-channel-home-widget.get-started%</p>
<x-channel class="channel" :channel="channel" v-if="channel != null"/>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import XChannel from './channel.channel.vue';
export default define({
name: 'server',
props: () => ({
channel: null,
compact: false
})
}).extend({
components: {
XChannel
},
data() {
return {
fetching: true,
channel: null
};
},
mounted() {
if (this.props.channel) {
this.zap();
}
},
methods: {
func() {
this.props.compact = !this.props.compact;
},
settings() {
const id = window.prompt('チャンネルID');
if (!id) return;
this.props.channel = id;
this.zap();
},
zap() {
this.fetching = true;
(this as any).api('channels/show', {
channelId: this.props.channel
}).then(channel => {
this.channel = channel;
this.fetching = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-channel
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
overflow hidden
> .title
z-index 2
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .get-started
margin 0
padding 16px
text-align center
color #aaa
> .channel
height 200px
</style>

View File

@ -0,0 +1,23 @@
import Vue from 'vue';
import wNotifications from './notifications.vue';
import wTimemachine from './timemachine.vue';
import wActivity from './activity.vue';
import wTrends from './trends.vue';
import wUsers from './users.vue';
import wPolls from './polls.vue';
import wPostForm from './post-form.vue';
import wMessaging from './messaging.vue';
import wChannel from './channel.vue';
import wProfile from './profile.vue';
Vue.component('mkw-notifications', wNotifications);
Vue.component('mkw-timemachine', wTimemachine);
Vue.component('mkw-activity', wActivity);
Vue.component('mkw-trends', wTrends);
Vue.component('mkw-users', wUsers);
Vue.component('mkw-polls', wPolls);
Vue.component('mkw-post-form', wPostForm);
Vue.component('mkw-messaging', wMessaging);
Vue.component('mkw-channel', wChannel);
Vue.component('mkw-profile', wProfile);

View File

@ -0,0 +1,59 @@
<template>
<div class="mkw-messaging">
<p class="title" v-if="props.design == 0">%fa:comments%%i18n:desktop.tags.mk-messaging-home-widget.title%</p>
<mk-messaging ref="index" compact @navigate="navigate"/>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import MkMessagingRoomWindow from '../components/messaging-room-window.vue';
export default define({
name: 'messaging',
props: () => ({
design: 0
})
}).extend({
methods: {
navigate(user) {
(this as any).os.new(MkMessagingRoomWindow, {
user: user
});
},
func() {
if (this.props.design == 1) {
this.props.design = 0;
} else {
this.props.design++;
}
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-messaging
overflow hidden
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
z-index 2
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> .mk-messaging
max-height 250px
overflow auto
</style>

View File

@ -0,0 +1,70 @@
<template>
<div class="mkw-notifications">
<template v-if="!props.compact">
<p class="title">%fa:R bell%%i18n:desktop.tags.mk-notifications-home-widget.title%</p>
<button @click="settings" title="%i18n:desktop.tags.mk-notifications-home-widget.settings%">%fa:cog%</button>
</template>
<mk-notifications/>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'notifications',
props: () => ({
compact: false
})
}).extend({
methods: {
settings() {
alert('not implemented yet');
},
func() {
this.props.compact = !this.props.compact;
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-notifications
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .mk-notifications
max-height 300px
overflow auto
</style>

View File

@ -0,0 +1,129 @@
<template>
<div class="mkw-polls">
<template v-if="!props.compact">
<p class="title">%fa:chart-pie%%i18n:desktop.tags.mk-recommended-polls-home-widget.title%</p>
<button @click="fetch" title="%i18n:desktop.tags.mk-recommended-polls-home-widget.refresh%">%fa:sync%</button>
</template>
<div class="poll" v-if="!fetching && poll != null">
<p v-if="poll.text"><router-link to="`/@${ acct }/${ poll.id }`">{{ poll.text }}</router-link></p>
<p v-if="!poll.text"><router-link to="`/@${ acct }/${ poll.id }`">%fa:link%</router-link></p>
<mk-poll :post="poll"/>
</div>
<p class="empty" v-if="!fetching && poll == null">%i18n:desktop.tags.mk-recommended-polls-home-widget.nothing%</p>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import getAcct from '../../../../../common/user/get-acct';
export default define({
name: 'polls',
props: () => ({
compact: false
})
}).extend({
computed: {
acct() {
return getAcct(this.poll.user);
},
},
data() {
return {
poll: null,
fetching: true,
offset: 0
};
},
mounted() {
this.fetch();
},
methods: {
func() {
this.props.compact = !this.props.compact;
},
fetch() {
this.fetching = true;
this.poll = null;
(this as any).api('posts/polls/recommendation', {
limit: 1,
offset: this.offset
}).then(posts => {
const poll = posts ? posts[0] : null;
if (poll == null) {
this.offset = 0;
} else {
this.offset++;
}
this.poll = poll;
this.fetching = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-polls
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
border-bottom solid 1px #eee
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .poll
padding 16px
font-size 12px
color #555
> p
margin 0 0 8px 0
> a
color inherit
> .empty
margin 0
padding 16px
text-align center
color #aaa
> .fetching
margin 0
padding 16px
text-align center
color #aaa
> [data-fa]
margin-right 4px
</style>

View File

@ -0,0 +1,111 @@
<template>
<div class="mkw-post-form">
<template v-if="props.design == 0">
<p class="title">%fa:pencil-alt%%i18n:desktop.tags.mk-post-form-home-widget.title%</p>
</template>
<textarea :disabled="posting" v-model="text" @keydown="onKeydown" placeholder="%i18n:desktop.tags.mk-post-form-home-widget.placeholder%"></textarea>
<button @click="post" :disabled="posting">%i18n:desktop.tags.mk-post-form-home-widget.post%</button>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'post-form',
props: () => ({
design: 0
})
}).extend({
data() {
return {
posting: false,
text: ''
};
},
methods: {
func() {
if (this.props.design == 1) {
this.props.design = 0;
} else {
this.props.design++;
}
},
onKeydown(e) {
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
},
post() {
this.posting = true;
(this as any).api('posts/create', {
text: this.text
}).then(data => {
this.clear();
}).catch(err => {
alert('失敗した');
}).then(() => {
this.posting = false;
});
},
clear() {
this.text = '';
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
.mkw-post-form
background #fff
overflow hidden
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> textarea
display block
width 100%
max-width 100%
min-width 100%
padding 16px
margin-bottom 28px + 16px
border none
border-bottom solid 1px #eee
> button
display block
position absolute
bottom 8px
right 8px
margin 0
padding 0 10px
height 28px
color $theme-color-foreground
background $theme-color !important
outline none
border none
border-radius 4px
transition background 0.1s ease
cursor pointer
&:hover
background lighten($theme-color, 10%) !important
&:active
background darken($theme-color, 10%) !important
transition background 0s ease
</style>

View File

@ -0,0 +1,125 @@
<template>
<div class="mkw-profile"
:data-compact="props.design == 1 || props.design == 2"
:data-melt="props.design == 2"
>
<div class="banner"
:style="os.i.bannerUrl ? `background-image: url(${os.i.bannerUrl}?thumbnail&size=256)` : ''"
title="クリックでバナー編集"
@click="os.apis.updateBanner"
></div>
<img class="avatar"
:src="`${os.i.avatarUrl}?thumbnail&size=96`"
@click="os.apis.updateAvatar"
alt="avatar"
title="クリックでアバター編集"
v-user-preview="os.i.id"
/>
<router-link class="name" :to="`/@${os.i.username}`">{{ os.i.name }}</router-link>
<p class="username">@{{ os.i.username }}</p>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'profile',
props: () => ({
design: 0
})
}).extend({
methods: {
func() {
if (this.props.design == 2) {
this.props.design = 0;
} else {
this.props.design++;
}
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-profile
overflow hidden
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
&[data-compact]
> .banner:before
content ""
display block
width 100%
height 100%
background rgba(0, 0, 0, 0.5)
> .avatar
top ((100px - 58px) / 2)
left ((100px - 58px) / 2)
border none
border-radius 100%
box-shadow 0 0 16px rgba(0, 0, 0, 0.5)
> .name
position absolute
top 0
left 92px
margin 0
line-height 100px
color #fff
text-shadow 0 0 8px rgba(0, 0, 0, 0.5)
> .username
display none
&[data-melt]
background transparent !important
border none !important
> .banner
visibility hidden
> .avatar
box-shadow none
> .name
color #666
text-shadow none
> .banner
height 100px
background-color #f5f5f5
background-size cover
background-position center
cursor pointer
> .avatar
display block
position absolute
top 76px
left 16px
width 58px
height 58px
margin 0
border solid 3px #fff
border-radius 8px
vertical-align bottom
cursor pointer
> .name
display block
margin 10px 0 0 84px
line-height 16px
font-weight bold
color #555
> .username
display block
margin 4px 0 8px 84px
line-height 16px
font-size 0.9em
color #999
</style>

View File

@ -0,0 +1,28 @@
<template>
<div class="mkw-timemachine">
<mk-calendar :design="props.design" @chosen="chosen"/>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'timemachine',
props: () => ({
design: 0
})
}).extend({
methods: {
chosen(date) {
this.$emit('chosen', date);
},
func() {
if (this.props.design == 5) {
this.props.design = 0;
} else {
this.props.design++;
}
}
}
});
</script>

View File

@ -0,0 +1,135 @@
<template>
<div class="mkw-trends">
<template v-if="!props.compact">
<p class="title">%fa:fire%%i18n:desktop.tags.mk-trends-home-widget.title%</p>
<button @click="fetch" title="%i18n:desktop.tags.mk-trends-home-widget.refresh%">%fa:sync%</button>
</template>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div class="post" v-else-if="post != null">
<p class="text"><router-link :to="`/@${ acct }/${ post.id }`">{{ post.text }}</router-link></p>
<p class="author"><router-link :to="`/@${ acct }`">@{{ acct }}</router-link></p>
</div>
<p class="empty" v-else>%i18n:desktop.tags.mk-trends-home-widget.nothing%</p>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import getAcct from '../../../../../common/user/get-acct';
export default define({
name: 'trends',
props: () => ({
compact: false
})
}).extend({
computed: {
acct() {
return getAcct(this.post.user);
},
},
data() {
return {
post: null,
fetching: true,
offset: 0
};
},
mounted() {
this.fetch();
},
methods: {
func() {
this.props.compact = !this.props.compact;
},
fetch() {
this.fetching = true;
this.post = null;
(this as any).api('posts/trend', {
limit: 1,
offset: this.offset,
repost: false,
reply: false,
media: false,
poll: false
}).then(posts => {
const post = posts ? posts[0] : null;
if (post == null) {
this.offset = 0;
} else {
this.offset++;
}
this.post = post;
this.fetching = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-trends
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
border-bottom solid 1px #eee
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .post
padding 16px
font-size 12px
font-style oblique
color #555
> p
margin 0
> .text,
> .author
> a
color inherit
> .empty
margin 0
padding 16px
text-align center
color #aaa
> .fetching
margin 0
padding 16px
text-align center
color #aaa
> [data-fa]
margin-right 4px
</style>

View File

@ -0,0 +1,172 @@
<template>
<div class="mkw-users">
<template v-if="!props.compact">
<p class="title">%fa:users%%i18n:desktop.tags.mk-user-recommendation-home-widget.title%</p>
<button @click="refresh" title="%i18n:desktop.tags.mk-user-recommendation-home-widget.refresh%">%fa:sync%</button>
</template>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<template v-else-if="users.length != 0">
<div class="user" v-for="_user in users">
<router-link class="avatar-anchor" :to="`/@${getAcct(_user)}`">
<img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/>
</router-link>
<div class="body">
<router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ _user.name }}</router-link>
<p class="username">@{{ getAcct(_user) }}</p>
</div>
<mk-follow-button :user="_user"/>
</div>
</template>
<p class="empty" v-else>%i18n:desktop.tags.mk-user-recommendation-home-widget.no-one%</p>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import getAcct from '../../../../../common/user/get-acct';
const limit = 3;
export default define({
name: 'users',
props: () => ({
compact: false
})
}).extend({
data() {
return {
users: [],
fetching: true,
page: 0
};
},
mounted() {
this.fetch();
},
methods: {
getAcct,
func() {
this.props.compact = !this.props.compact;
},
fetch() {
this.fetching = true;
this.users = [];
(this as any).api('users/recommendation', {
limit: limit,
offset: limit * this.page
}).then(users => {
this.users = users;
this.fetching = false;
});
},
refresh() {
if (this.users.length < limit) {
this.page = 0;
} else {
this.page++;
}
this.fetch();
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-users
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
> .title
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
border-bottom solid 1px #eee
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .user
padding 16px
border-bottom solid 1px #eee
&:last-child
border-bottom none
&:after
content ""
display block
clear both
> .avatar-anchor
display block
float left
margin 0 12px 0 0
> .avatar
display block
width 42px
height 42px
margin 0
border-radius 8px
vertical-align bottom
> .body
float left
width calc(100% - 54px)
> .name
margin 0
font-size 16px
line-height 24px
color #555
> .username
display block
margin 0
font-size 15px
line-height 16px
color #ccc
> .mk-follow-button
position absolute
top 16px
right 16px
> .empty
margin 0
padding 16px
text-align center
color #aaa
> .fetching
margin 0
padding 16px
text-align center
color #aaa
> [data-fa]
margin-right 4px
</style>