Clean up
This commit is contained in:
parent
ddad9da1c1
commit
e6eb1b2ae1
|
@ -324,8 +324,6 @@ desktop/views/components/home.vue:
|
|||
polls: "投票"
|
||||
post-form: "投稿フォーム"
|
||||
messaging: "メッセージ"
|
||||
channel: "チャンネル"
|
||||
access-log: "アクセスログ"
|
||||
server: "サーバー情報"
|
||||
donation: "寄付のお願い"
|
||||
nav: "ナビゲーション"
|
||||
|
@ -336,12 +334,6 @@ desktop/views/input-dialog.vue:
|
|||
cancel: "キャンセル"
|
||||
ok: "決定"
|
||||
|
||||
desktop/views/components/mentions.vue:
|
||||
all: "すべて"
|
||||
followed: "フォロー中"
|
||||
empty: "あなた宛ての投稿はありません。"
|
||||
empty-followed: "あなたがフォローしているユーザーからの言及はありません。"
|
||||
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "メッセージ:"
|
||||
|
||||
|
@ -660,11 +652,6 @@ desktop/views/widgets/users.vue:
|
|||
refresh: "他を見る"
|
||||
no-one: "いません!"
|
||||
|
||||
desktop/views/widgets/channel.vue:
|
||||
title: "チャンネル"
|
||||
settings: "ウィジェットの設定"
|
||||
get-started: "右上の歯車をクリックして受信するチャンネルを指定してください"
|
||||
|
||||
mobile/views/components/drive.vue:
|
||||
drive: "ドライブ"
|
||||
used: "使用中"
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* Channels
|
||||
*/
|
||||
|
||||
// Style
|
||||
import './style.styl';
|
||||
|
||||
require('./tags');
|
||||
import init from '../init';
|
||||
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
init(() => {
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
@import "../app"
|
||||
|
||||
html
|
||||
padding 8px
|
||||
background #efefef
|
||||
|
||||
#wait
|
||||
top auto
|
||||
bottom 15px
|
||||
left 15px
|
|
@ -1,409 +0,0 @@
|
|||
<mk-channel>
|
||||
<mk-header/>
|
||||
<hr>
|
||||
<main v-if="!fetching">
|
||||
<h1>{ channel.title }</h1>
|
||||
|
||||
<div v-if="$root.$data.os.isSignedIn">
|
||||
<p v-if="channel.isWatching">このチャンネルをウォッチしています <a @click="unwatch">ウォッチ解除</a></p>
|
||||
<p v-if="!channel.isWatching"><a @click="watch">このチャンネルをウォッチする</a></p>
|
||||
</div>
|
||||
|
||||
<div class="share">
|
||||
<mk-twitter-button/>
|
||||
<mk-line-button/>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<p v-if="notesFetching">読み込み中<mk-ellipsis/></p>
|
||||
<div v-if="!notesFetching">
|
||||
<p v-if="notes == null || notes.length == 0">まだ投稿がありません</p>
|
||||
<template v-if="notes != null">
|
||||
<mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<mk-channel-form v-if="$root.$data.os.isSignedIn" channel={ channel } ref="form"/>
|
||||
<div v-if="!$root.$data.os.isSignedIn">
|
||||
<p>参加するには<a href={ _URL_ }>ログインまたは新規登録</a>してください</p>
|
||||
</div>
|
||||
<hr>
|
||||
<footer>
|
||||
<small><a href={ _URL_ }>Misskey</a> ver { _VERSION_ } (葵 aoi)</small>
|
||||
</footer>
|
||||
</main>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> main
|
||||
> h1
|
||||
font-size 1.5em
|
||||
color #f00
|
||||
|
||||
> .share
|
||||
> *
|
||||
margin-right 4px
|
||||
|
||||
> .body
|
||||
margin 8px 0 0 0
|
||||
|
||||
> mk-channel-form
|
||||
max-width 500px
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import Progress from '../../common/scripts/loading';
|
||||
import ChannelStream from '../../common/scripts/streaming/channel-stream';
|
||||
|
||||
this.mixin('i');
|
||||
this.mixin('api');
|
||||
|
||||
this.id = this.opts.id;
|
||||
this.fetching = true;
|
||||
this.notesFetching = true;
|
||||
this.channel = null;
|
||||
this.notes = null;
|
||||
this.connection = new ChannelStream(this.id);
|
||||
this.unreadCount = 0;
|
||||
|
||||
this.on('mount', () => {
|
||||
document.documentElement.style.background = '#efefef';
|
||||
|
||||
Progress.start();
|
||||
|
||||
let fetched = false;
|
||||
|
||||
// チャンネル概要読み込み
|
||||
this.$root.$data.os.api('channels/show', {
|
||||
channelId: this.id
|
||||
}).then(channel => {
|
||||
if (fetched) {
|
||||
Progress.done();
|
||||
} else {
|
||||
Progress.set(0.5);
|
||||
fetched = true;
|
||||
}
|
||||
|
||||
this.update({
|
||||
fetching: false,
|
||||
channel: channel
|
||||
});
|
||||
|
||||
document.title = channel.title + ' | Misskey'
|
||||
});
|
||||
|
||||
// 投稿読み込み
|
||||
this.$root.$data.os.api('channels/notes', {
|
||||
channelId: this.id
|
||||
}).then(notes => {
|
||||
if (fetched) {
|
||||
Progress.done();
|
||||
} else {
|
||||
Progress.set(0.5);
|
||||
fetched = true;
|
||||
}
|
||||
|
||||
this.update({
|
||||
notesFetching: false,
|
||||
notes: notes
|
||||
});
|
||||
});
|
||||
|
||||
this.connection.on('note', this.onNote);
|
||||
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
|
||||
});
|
||||
|
||||
this.on('unmount', () => {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.connection.close();
|
||||
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
||||
});
|
||||
|
||||
this.onNote = note => {
|
||||
this.notes.unshift(note);
|
||||
this.update();
|
||||
|
||||
if (document.hidden && this.$root.$data.os.isSignedIn && note.userId !== this.$root.$data.os.i.id) {
|
||||
this.unreadCount++;
|
||||
document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`;
|
||||
}
|
||||
};
|
||||
|
||||
this.onVisibilitychange = () => {
|
||||
if (!document.hidden) {
|
||||
this.unreadCount = 0;
|
||||
document.title = this.channel.title + ' | Misskey'
|
||||
}
|
||||
};
|
||||
|
||||
this.watch = () => {
|
||||
this.$root.$data.os.api('channels/watch', {
|
||||
channelId: this.id
|
||||
}).then(() => {
|
||||
this.channel.isWatching = true;
|
||||
this.update();
|
||||
}, e => {
|
||||
alert('error');
|
||||
});
|
||||
};
|
||||
|
||||
this.unwatch = () => {
|
||||
this.$root.$data.os.api('channels/unwatch', {
|
||||
channelId: this.id
|
||||
}).then(() => {
|
||||
this.channel.isWatching = false;
|
||||
this.update();
|
||||
}, e => {
|
||||
alert('error');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-channel>
|
||||
|
||||
<mk-channel-note>
|
||||
<header>
|
||||
<a class="index" @click="reply">{ note.index }:</a>
|
||||
<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a>
|
||||
<mk-time time={ note.createdAt }/>
|
||||
<mk-time time={ note.createdAt } mode="detail"/>
|
||||
<span>ID:<i>{ acct }</i></span>
|
||||
</header>
|
||||
<div>
|
||||
<a v-if="note.reply">>>{ note.reply.index }</a>
|
||||
{ note.text }
|
||||
<div class="media" v-if="note.media">
|
||||
<template each={ file in note.media }>
|
||||
<a href={ file.url } target="_blank">
|
||||
<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
> header
|
||||
position -webkit-sticky
|
||||
position sticky
|
||||
z-index 1
|
||||
top 0
|
||||
background rgba(239, 239, 239, 0.9)
|
||||
|
||||
> .index
|
||||
margin-right 0.25em
|
||||
color #000
|
||||
|
||||
> .name
|
||||
margin-right 0.5em
|
||||
color #008000
|
||||
|
||||
> mk-time
|
||||
margin-right 0.5em
|
||||
|
||||
&:first-of-type
|
||||
display none
|
||||
|
||||
@media (max-width 600px)
|
||||
> mk-time
|
||||
&:first-of-type
|
||||
display initial
|
||||
|
||||
&:last-of-type
|
||||
display none
|
||||
|
||||
> div
|
||||
padding 0 0 1em 2em
|
||||
|
||||
> .media
|
||||
> a
|
||||
display inline-block
|
||||
|
||||
> img
|
||||
max-width 100%
|
||||
vertical-align bottom
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import getAcct from '../../../../acct/render';
|
||||
import getUserName from '../../../../renderers/get-user-name';
|
||||
|
||||
this.note = this.opts.note;
|
||||
this.form = this.opts.form;
|
||||
this.acct = getAcct(this.note.user);
|
||||
this.name = getUserName(this.note.user);
|
||||
|
||||
this.reply = () => {
|
||||
this.form.update({
|
||||
reply: this.note
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-channel-note>
|
||||
|
||||
<mk-channel-form>
|
||||
<p v-if="reply"><b>>>{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p>
|
||||
<textarea ref="text" disabled={ wait } oninput={ update } onkeydown={ onkeydown } onpaste={ onpaste } placeholder="%i18n:ch.tags.mk-channel-form.textarea%"></textarea>
|
||||
<div class="actions">
|
||||
<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button>
|
||||
<button @click="drive">%fa:cloud%%i18n:ch.tags.mk-channel-form.drive%</button>
|
||||
<button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="note">
|
||||
<template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:!ch.tags.mk-channel-form.posting%' : '%i18n:!ch.tags.mk-channel-form.note%' }<mk-ellipsis v-if="wait"/>
|
||||
</button>
|
||||
</div>
|
||||
<mk-uploader ref="uploader"/>
|
||||
<ol v-if="files">
|
||||
<li each={ files }>{ name }</li>
|
||||
</ol>
|
||||
<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> textarea
|
||||
width 100%
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
min-height 5em
|
||||
|
||||
> .actions
|
||||
display flex
|
||||
|
||||
> button
|
||||
> [data-fa]
|
||||
margin-right 0.25em
|
||||
|
||||
&:last-child
|
||||
margin-left auto
|
||||
|
||||
&.wait
|
||||
cursor wait
|
||||
|
||||
> input[type='file']
|
||||
display none
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
import getUserName from '../../../../renderers/get-user-name';
|
||||
|
||||
this.mixin('api');
|
||||
|
||||
this.channel = this.opts.channel;
|
||||
this.files = null;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.$refs.uploader.on('uploaded', file => {
|
||||
this.update({
|
||||
files: [file]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.upload = file => {
|
||||
this.$refs.uploader.upload(file);
|
||||
};
|
||||
|
||||
this.clearReply = () => {
|
||||
this.update({
|
||||
reply: null
|
||||
});
|
||||
};
|
||||
|
||||
this.clear = () => {
|
||||
this.clearReply();
|
||||
this.update({
|
||||
files: null
|
||||
});
|
||||
this.$refs.text.value = '';
|
||||
};
|
||||
|
||||
this.note = () => {
|
||||
this.update({
|
||||
wait: true
|
||||
});
|
||||
|
||||
const files = this.files && this.files.length > 0
|
||||
? this.files.map(f => f.id)
|
||||
: undefined;
|
||||
|
||||
this.$root.$data.os.api('notes/create', {
|
||||
text: this.$refs.text.value == '' ? undefined : this.$refs.text.value,
|
||||
mediaIds: files,
|
||||
replyId: this.reply ? this.reply.id : undefined,
|
||||
channelId: this.channel.id
|
||||
}).then(data => {
|
||||
this.clear();
|
||||
}).catch(err => {
|
||||
alert('失敗した');
|
||||
}).then(() => {
|
||||
this.update({
|
||||
wait: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.changeFile = () => {
|
||||
Array.from(this.$refs.file.files).forEach(this.upload);
|
||||
};
|
||||
|
||||
this.selectFile = () => {
|
||||
this.$refs.file.click();
|
||||
};
|
||||
|
||||
this.drive = () => {
|
||||
window['cb'] = files => {
|
||||
this.update({
|
||||
files: files
|
||||
});
|
||||
};
|
||||
|
||||
window.open(_URL_ + '/selectdrive?multiple=true',
|
||||
'drive_window',
|
||||
'height=500,width=800');
|
||||
};
|
||||
|
||||
this.onkeydown = e => {
|
||||
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
|
||||
};
|
||||
|
||||
this.onpaste = e => {
|
||||
Array.from(e.clipboardData.items).forEach(item => {
|
||||
if (item.kind == 'file') {
|
||||
this.upload(item.getAsFile());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.getUserName = getUserName;
|
||||
</script>
|
||||
</mk-channel-form>
|
||||
|
||||
<mk-twitter-button>
|
||||
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-show-count="false">Tweet</a>
|
||||
<script lang="typescript">
|
||||
this.on('mount', () => {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('src', 'https://platform.twitter.com/widgets.js');
|
||||
script.setAttribute('async', 'async');
|
||||
head.appendChild(script);
|
||||
});
|
||||
</script>
|
||||
</mk-twitter-button>
|
||||
|
||||
<mk-line-button>
|
||||
<div class="line-it-button" data-lang="ja" data-type="share-a" data-url={ _CH_URL_ } style="display: none;"></div>
|
||||
<script lang="typescript">
|
||||
this.on('mount', () => {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('src', 'https://d.line-scdn.net/r/web/social-plugin/js/thirdparty/loader.min.js');
|
||||
script.setAttribute('async', 'async');
|
||||
head.appendChild(script);
|
||||
});
|
||||
</script>
|
||||
</mk-line-button>
|
|
@ -1,20 +0,0 @@
|
|||
<mk-header>
|
||||
<div>
|
||||
<a href={ _CH_URL_ }>Index</a> | <a href={ _URL_ }>Misskey</a>
|
||||
</div>
|
||||
<div>
|
||||
<a v-if="!$root.$data.os.isSignedIn" href={ _URL_ }>ログイン(新規登録)</a>
|
||||
<a v-if="$root.$data.os.isSignedIn" href={ _URL_ + '/@' + I.username }>{ I.username }</a>
|
||||
</div>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display flex
|
||||
|
||||
> div:last-child
|
||||
margin-left auto
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.mixin('i');
|
||||
</script>
|
||||
</mk-header>
|
|
@ -1,37 +0,0 @@
|
|||
<mk-index>
|
||||
<mk-header/>
|
||||
<hr>
|
||||
<button @click="n">%i18n:ch.tags.mk-index.new%</button>
|
||||
<hr>
|
||||
<ul v-if="channels">
|
||||
<li each={ channels }><a href={ '/' + this.id }>{ this.title }</a></li>
|
||||
</ul>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.mixin('api');
|
||||
|
||||
this.on('mount', () => {
|
||||
this.$root.$data.os.api('channels', {
|
||||
limit: 100
|
||||
}).then(channels => {
|
||||
this.update({
|
||||
channels: channels
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.n = () => {
|
||||
const title = window.prompt('%i18n:!ch.tags.mk-index.channel-title%');
|
||||
|
||||
this.$root.$data.os.api('channels/create', {
|
||||
title: title
|
||||
}).then(channel => {
|
||||
location.href = '/' + channel.id;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</mk-index>
|
|
@ -1,3 +0,0 @@
|
|||
require('./index.tag');
|
||||
require('./channel.tag');
|
||||
require('./header.tag');
|
|
@ -1,13 +0,0 @@
|
|||
import Stream from './stream';
|
||||
import MiOS from '../../../mios';
|
||||
|
||||
/**
|
||||
* Channel stream connection
|
||||
*/
|
||||
export default class Connection extends Stream {
|
||||
constructor(os: MiOS, channelId) {
|
||||
super(os, 'channel', {
|
||||
channel: channelId
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
<template>
|
||||
<div class="mkw-access-log">
|
||||
<mk-widget-container :show-header="props.design == 0">
|
||||
<template slot="header">%fa:server%%i18n:@title%</template>
|
||||
|
||||
<div :class="$style.logs" ref="log">
|
||||
<p v-for="req in requests">
|
||||
<span :class="$style.ip" :style="`color:${ req.fg }; background:${ req.bg }`">{{ req.ip }}</span>
|
||||
<b>{{ req.method }}</b>
|
||||
<span>{{ req.path }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</mk-widget-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import define from '../../../common/define-widget';
|
||||
import * as seedrandom from 'seedrandom';
|
||||
|
||||
export default define({
|
||||
name: 'broadcast',
|
||||
props: () => ({
|
||||
design: 0
|
||||
})
|
||||
}).extend({
|
||||
data() {
|
||||
return {
|
||||
requests: [],
|
||||
connection: null,
|
||||
connectionId: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.connection = (this as any).os.streams.requestsStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.requestsStream.use();
|
||||
this.connection.on('request', this.onRequest);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.connection.off('request', this.onRequest);
|
||||
(this as any).os.streams.requestsStream.dispose(this.connectionId);
|
||||
},
|
||||
methods: {
|
||||
onRequest(request) {
|
||||
const random = seedrandom(request.ip);
|
||||
const r = Math.floor(random() * 255);
|
||||
const g = Math.floor(random() * 255);
|
||||
const b = Math.floor(random() * 255);
|
||||
const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b); // SMPTE C, Rec. 709 weightings
|
||||
request.bg = `rgb(${r}, ${g}, ${b})`;
|
||||
request.fg = luma >= 165 ? '#000' : '#fff';
|
||||
|
||||
this.requests.push(request);
|
||||
if (this.requests.length > 30) this.requests.shift();
|
||||
|
||||
(this.$refs.log as any).scrollTop = (this.$refs.log as any).scrollHeight;
|
||||
},
|
||||
func() {
|
||||
if (this.props.design == 1) {
|
||||
this.props.design = 0;
|
||||
} else {
|
||||
this.props.design++;
|
||||
}
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" module>
|
||||
.logs
|
||||
max-height 250px
|
||||
overflow auto
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 8px
|
||||
font-size 0.8em
|
||||
color #555
|
||||
|
||||
&:nth-child(odd)
|
||||
background rgba(#000, 0.025)
|
||||
|
||||
> b
|
||||
margin-right 4px
|
||||
|
||||
.ip
|
||||
margin-right 4px
|
||||
padding 0 4px
|
||||
|
||||
</style>
|
|
@ -1,6 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import wAccessLog from './access-log.vue';
|
||||
import wVersion from './version.vue';
|
||||
import wRss from './rss.vue';
|
||||
import wServer from './server.vue';
|
||||
|
@ -22,4 +21,3 @@ Vue.component('mkw-broadcast', wBroadcast);
|
|||
Vue.component('mkw-server', wServer);
|
||||
Vue.component('mkw-rss', wRss);
|
||||
Vue.component('mkw-version', wVersion);
|
||||
Vue.component('mkw-access-log', wAccessLog);
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
<option value="polls">%i18n:@polls%</option>
|
||||
<option value="post-form">%i18n:@post-form%</option>
|
||||
<option value="messaging">%i18n:@messaging%</option>
|
||||
<option value="channel">%i18n:@channel%</option>
|
||||
<option value="access-log">%i18n:@access-log%</option>
|
||||
<option value="server">%i18n:@server%</option>
|
||||
<option value="donation">%i18n:@donation%</option>
|
||||
<option value="nav">%i18n:@nav%</option>
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
<template>
|
||||
<div class="mk-mentions">
|
||||
<header>
|
||||
<span :data-active="mode == 'all'" @click="mode = 'all'">%i18n:@all%</span>
|
||||
<span :data-active="mode == 'following'" @click="mode = 'following'">%i18n:@followed%</span>
|
||||
</header>
|
||||
<div class="fetching" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
<p class="empty" v-if="notes.length == 0 && !fetching">
|
||||
%fa:R comments%
|
||||
<span v-if="mode == 'all'">%i18n:@empty%</span>
|
||||
<span v-if="mode == 'following'">%i18n:@empty-followed%</span>
|
||||
</p>
|
||||
<mk-notes :notes="notes" ref="timeline"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
mode: 'all',
|
||||
notes: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode() {
|
||||
this.fetch();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
|
||||
this.fetch(() => this.$emit('loaded'));
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
onDocumentKeydown(e) {
|
||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
||||
if (e.which == 84) { // t
|
||||
(this.$refs.timeline as any).focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
onScroll() {
|
||||
const current = window.scrollY + window.innerHeight;
|
||||
if (current > document.body.offsetHeight - 8) this.more();
|
||||
},
|
||||
fetch(cb?) {
|
||||
this.fetching = true;
|
||||
this.notes = [];
|
||||
(this as any).api('notes/mentions', {
|
||||
following: this.mode == 'following'
|
||||
}).then(notes => {
|
||||
this.notes = notes;
|
||||
this.fetching = false;
|
||||
if (cb) cb();
|
||||
});
|
||||
},
|
||||
more() {
|
||||
if (this.moreFetching || this.fetching || this.notes.length == 0) return;
|
||||
this.moreFetching = true;
|
||||
(this as any).api('notes/mentions', {
|
||||
following: this.mode == 'following',
|
||||
untilId: this.notes[this.notes.length - 1].id
|
||||
}).then(notes => {
|
||||
this.notes = this.notes.concat(notes);
|
||||
this.moreFetching = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.mk-mentions
|
||||
background #fff
|
||||
border solid 1px rgba(#000, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> header
|
||||
padding 8px 16px
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> span
|
||||
margin-right 16px
|
||||
line-height 27px
|
||||
font-size 18px
|
||||
color #555
|
||||
|
||||
&:not([data-active])
|
||||
color $theme-color
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .fetching
|
||||
padding 64px 0
|
||||
|
||||
> .empty
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 32px
|
||||
max-width 400px
|
||||
text-align center
|
||||
color #999
|
||||
|
||||
> [data-fa]
|
||||
display block
|
||||
margin-bottom 16px
|
||||
font-size 3em
|
||||
color #ccc
|
||||
|
||||
</style>
|
|
@ -33,9 +33,6 @@
|
|||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p class="channel" v-if="p.channel">
|
||||
<a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>:
|
||||
</p>
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
||||
|
@ -574,9 +571,6 @@ root(isDark)
|
|||
.mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .channel
|
||||
margin 0
|
||||
|
||||
> .mk-poll
|
||||
font-size 80%
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<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).notes.find(p => p.index.toString() == index);
|
||||
this.text = this.text.replace(/^>>([0-9]+) /, '');
|
||||
}
|
||||
|
||||
(this as any).api('notes/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>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="note">
|
||||
<header>
|
||||
<a class="index" @click="reply">{{ note.index }}:</a>
|
||||
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id"><b>{{ note.user | userName }}</b></router-link>
|
||||
<span>ID:<i>{{ note.user | acct }}</i></span>
|
||||
</header>
|
||||
<div>
|
||||
<a v-if="note.reply">>>{{ note.reply.index }}</a>
|
||||
{{ note.text }}
|
||||
<div class="media" v-if="note.media">
|
||||
<a v-for="file in note.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';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['note'],
|
||||
methods: {
|
||||
reply() {
|
||||
this.$emit('reply', this.note);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.note
|
||||
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>
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<div class="channel">
|
||||
<p v-if="fetching">読み込み中<mk-ellipsis/></p>
|
||||
<div v-if="!fetching" ref="notes" class="notes">
|
||||
<p v-if="notes.length == 0">まだ投稿がありません</p>
|
||||
<x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.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 XNote from './channel.channel.note.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XForm,
|
||||
XNote
|
||||
},
|
||||
props: ['channel'],
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
notes: [],
|
||||
connection: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
channel() {
|
||||
this.zap();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.zap();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disconnect();
|
||||
},
|
||||
methods: {
|
||||
zap() {
|
||||
this.fetching = true;
|
||||
|
||||
(this as any).api('channels/notes', {
|
||||
channelId: this.channel.id
|
||||
}).then(notes => {
|
||||
this.notes = notes;
|
||||
this.fetching = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.disconnect();
|
||||
this.connection = new ChannelStream((this as any).os, this.channel.id);
|
||||
this.connection.on('note', this.onNote);
|
||||
});
|
||||
},
|
||||
disconnect() {
|
||||
if (this.connection) {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.connection.close();
|
||||
}
|
||||
},
|
||||
onNote(note) {
|
||||
this.notes.unshift(note);
|
||||
this.scrollToBottom();
|
||||
},
|
||||
scrollToBottom() {
|
||||
(this.$refs.notes as any).scrollTop = (this.$refs.notes as any).scrollHeight;
|
||||
},
|
||||
reply(note) {
|
||||
(this.$refs.form as any).text = `>>${ note.index } `;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.channel
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .notes
|
||||
height calc(100% - 38px)
|
||||
overflow auto
|
||||
font-size 0.9em
|
||||
|
||||
> .note
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
&:last-child
|
||||
border-bottom none
|
||||
|
||||
> .form
|
||||
position absolute
|
||||
left 0
|
||||
bottom 0
|
||||
|
||||
</style>
|
|
@ -1,108 +0,0 @@
|
|||
<template>
|
||||
<div class="mkw-channel">
|
||||
<template v-if="!props.compact">
|
||||
<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:!@title%' }}</p>
|
||||
<button @click="settings" title="%i18n:@settings%">%fa:cog%</button>
|
||||
</template>
|
||||
<p class="get-started" v-if="props.channel == null">%i18n:@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;
|
||||
this.save();
|
||||
},
|
||||
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(#000, 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(#000, 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>
|
|
@ -8,7 +8,6 @@ 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);
|
||||
|
@ -19,5 +18,4 @@ 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);
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
||||
|
@ -470,9 +469,6 @@ root(isDark)
|
|||
.mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .channel
|
||||
margin 0
|
||||
|
||||
> .tags
|
||||
margin 4px 0 0 0
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<option value="photo-stream">フォトストリーム</option>
|
||||
<option value="slideshow">スライドショー</option>
|
||||
<option value="version">バージョン</option>
|
||||
<option value="access-log">アクセスログ</option>
|
||||
<option value="server">サーバー情報</option>
|
||||
<option value="donation">寄付のお願い</option>
|
||||
<option value="nav">ナビゲーション</option>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const ChannelWatching = db.get<IChannelWatching>('channelWatching');
|
||||
export default ChannelWatching;
|
||||
|
||||
export interface IChannelWatching {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
deletedAt: Date;
|
||||
channelId: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import deepcopy = require('deepcopy');
|
||||
import { IUser } from './user';
|
||||
import Watching from './channel-watching';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Channel = db.get<IChannel>('channels');
|
||||
export default Channel;
|
||||
|
||||
export type IChannel = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
title: string;
|
||||
userId: mongo.ObjectID;
|
||||
index: number;
|
||||
watchingCount: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a channel for API response
|
||||
*
|
||||
* @param channel target
|
||||
* @param me? serializee
|
||||
* @return response
|
||||
*/
|
||||
export const pack = (
|
||||
channel: string | mongo.ObjectID | IChannel,
|
||||
me?: string | mongo.ObjectID | IUser
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
|
||||
let _channel: any;
|
||||
|
||||
// Populate the channel if 'channel' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(channel)) {
|
||||
_channel = await Channel.findOne({
|
||||
_id: channel
|
||||
});
|
||||
} else if (typeof channel === 'string') {
|
||||
_channel = await Channel.findOne({
|
||||
_id: new mongo.ObjectID(channel)
|
||||
});
|
||||
} else {
|
||||
_channel = deepcopy(channel);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_channel.id = _channel._id;
|
||||
delete _channel._id;
|
||||
|
||||
// Remove needless properties
|
||||
delete _channel.userId;
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
: (me as IUser)._id
|
||||
: null;
|
||||
|
||||
if (me) {
|
||||
//#region Watchしているかどうか
|
||||
const watch = await Watching.findOne({
|
||||
userId: meId,
|
||||
channelId: _channel.id,
|
||||
deletedAt: { $exists: false }
|
||||
});
|
||||
|
||||
_channel.isWatching = watch !== null;
|
||||
//#endregion
|
||||
}
|
||||
|
||||
resolve(_channel);
|
||||
});
|
|
@ -4,7 +4,6 @@ import rap from '@prezzemolo/rap';
|
|||
import db from '../db/mongodb';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packApp } from './app';
|
||||
import { pack as packChannel } from './channel';
|
||||
import PollVote, { deletePollVote } from './poll-vote';
|
||||
import Reaction, { deleteNoteReaction } from './note-reaction';
|
||||
import { pack as packFile } from './drive-file';
|
||||
|
@ -29,7 +28,6 @@ export function isValidCw(text: string): boolean {
|
|||
|
||||
export type INote = {
|
||||
_id: mongo.ObjectID;
|
||||
channelId: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
deletedAt: Date;
|
||||
mediaIds: mongo.ObjectID[];
|
||||
|
@ -258,11 +256,6 @@ export const pack = async (
|
|||
_note.app = packApp(_note.appId);
|
||||
}
|
||||
|
||||
// Populate channel
|
||||
if (_note.channelId) {
|
||||
_note.channel = packChannel(_note.channelId);
|
||||
}
|
||||
|
||||
// Populate media
|
||||
_note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
|
||||
packFile(fileId)
|
||||
|
|
|
@ -45,10 +45,6 @@ class MisskeyEvent {
|
|||
this.publish(`othello-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
public publishChannelStream(channelId: ID, type: string, value?: any): void {
|
||||
this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
public publishLocalTimelineStream(note: any): void {
|
||||
this.redisClient.publish('misskey:local-timeline', JSON.stringify(note));
|
||||
}
|
||||
|
@ -79,4 +75,3 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
|
|||
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
|
||||
export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
|
||||
export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
|
||||
export const publishChannelStream = ev.publishChannelStream.bind(ev);
|
||||
|
|
|
@ -9,9 +9,6 @@ const summarize = (note: any): string => {
|
|||
|
||||
let summary = '';
|
||||
|
||||
// チャンネル
|
||||
summary += note.channel ? `${note.channel.title}:` : '';
|
||||
|
||||
// 本文
|
||||
summary += note.text ? note.text : '';
|
||||
|
||||
|
|
|
@ -621,33 +621,7 @@ const endpoints: Endpoint[] = [
|
|||
name: 'messaging/messages/create',
|
||||
withCredential: true,
|
||||
kind: 'messaging-write'
|
||||
},
|
||||
{
|
||||
name: 'channels/create',
|
||||
withCredential: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 3,
|
||||
minInterval: ms('10seconds')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'channels/show'
|
||||
},
|
||||
{
|
||||
name: 'channels/notes'
|
||||
},
|
||||
{
|
||||
name: 'channels/watch',
|
||||
withCredential: true
|
||||
},
|
||||
{
|
||||
name: 'channels/unwatch',
|
||||
withCredential: true
|
||||
},
|
||||
{
|
||||
name: 'channels'
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
export default endpoints;
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy'; import ID from '../../../cafy-id';
|
||||
import Channel, { pack } from '../../../models/channel';
|
||||
|
||||
/**
|
||||
* Get all channels
|
||||
*
|
||||
* @param {any} params
|
||||
* @param {any} me
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
module.exports = (params, me) => new Promise(async (res, rej) => {
|
||||
// Get 'limit' parameter
|
||||
const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
|
||||
if (limitErr) return rej('invalid limit param');
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
|
||||
if (sinceIdErr) return rej('invalid sinceId param');
|
||||
|
||||
// Get 'untilId' parameter
|
||||
const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
|
||||
if (untilIdErr) return rej('invalid untilId param');
|
||||
|
||||
// Check if both of sinceId and untilId is specified
|
||||
if (sinceId && untilId) {
|
||||
return rej('cannot set sinceId and untilId');
|
||||
}
|
||||
|
||||
// Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
const query = {} as any;
|
||||
if (sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: sinceId
|
||||
};
|
||||
} else if (untilId) {
|
||||
query._id = {
|
||||
$lt: untilId
|
||||
};
|
||||
}
|
||||
|
||||
// Issue query
|
||||
const channels = await Channel
|
||||
.find(query, {
|
||||
limit: limit,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
// Serialize
|
||||
res(await Promise.all(channels.map(async channel =>
|
||||
await pack(channel, me))));
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy';
|
||||
import Channel from '../../../../models/channel';
|
||||
import Watching from '../../../../models/channel-watching';
|
||||
import { pack } from '../../../../models/channel';
|
||||
|
||||
/**
|
||||
* Create a channel
|
||||
*/
|
||||
module.exports = async (params, user) => new Promise(async (res, rej) => {
|
||||
// Get 'title' parameter
|
||||
const [title, titleErr] = $.str.range(1, 100).get(params.title);
|
||||
if (titleErr) return rej('invalid title param');
|
||||
|
||||
// Create a channel
|
||||
const channel = await Channel.insert({
|
||||
createdAt: new Date(),
|
||||
userId: user._id,
|
||||
title: title,
|
||||
index: 0,
|
||||
watchingCount: 1
|
||||
});
|
||||
|
||||
// Response
|
||||
res(await pack(channel));
|
||||
|
||||
// Create Watching
|
||||
await Watching.insert({
|
||||
createdAt: new Date(),
|
||||
userId: user._id,
|
||||
channelId: channel._id
|
||||
});
|
||||
});
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import { default as Channel, IChannel } from '../../../../models/channel';
|
||||
import Note, { pack } from '../../../../models/note';
|
||||
|
||||
/**
|
||||
* Show a notes of a channel
|
||||
*/
|
||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||
// Get 'limit' parameter
|
||||
const [limit = 1000, limitErr] = $.num.optional().range(1, 1000).get(params.limit);
|
||||
if (limitErr) return rej('invalid limit param');
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
|
||||
if (sinceIdErr) return rej('invalid sinceId param');
|
||||
|
||||
// Get 'untilId' parameter
|
||||
const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
|
||||
if (untilIdErr) return rej('invalid untilId param');
|
||||
|
||||
// Check if both of sinceId and untilId is specified
|
||||
if (sinceId && untilId) {
|
||||
return rej('cannot set sinceId and untilId');
|
||||
}
|
||||
|
||||
// Get 'channelId' parameter
|
||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
||||
if (channelIdErr) return rej('invalid channelId param');
|
||||
|
||||
// Fetch channel
|
||||
const channel: IChannel = await Channel.findOne({
|
||||
_id: channelId
|
||||
});
|
||||
|
||||
if (channel === null) {
|
||||
return rej('channel not found');
|
||||
}
|
||||
|
||||
//#region Construct query
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
|
||||
const query = {
|
||||
channelId: channel._id
|
||||
} as any;
|
||||
|
||||
if (sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: sinceId
|
||||
};
|
||||
} else if (untilId) {
|
||||
query._id = {
|
||||
$lt: untilId
|
||||
};
|
||||
}
|
||||
//#endregion Construct query
|
||||
|
||||
// Issue query
|
||||
const notes = await Note
|
||||
.find(query, {
|
||||
limit: limit,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
// Serialize
|
||||
res(await Promise.all(notes.map(async (note) =>
|
||||
await pack(note, user)
|
||||
)));
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import Channel, { IChannel, pack } from '../../../../models/channel';
|
||||
|
||||
/**
|
||||
* Show a channel
|
||||
*/
|
||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||
// Get 'channelId' parameter
|
||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
||||
if (channelIdErr) return rej('invalid channelId param');
|
||||
|
||||
// Fetch channel
|
||||
const channel: IChannel = await Channel.findOne({
|
||||
_id: channelId
|
||||
});
|
||||
|
||||
if (channel === null) {
|
||||
return rej('channel not found');
|
||||
}
|
||||
|
||||
// Serialize
|
||||
res(await pack(channel, user));
|
||||
});
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import Channel from '../../../../models/channel';
|
||||
import Watching from '../../../../models/channel-watching';
|
||||
|
||||
/**
|
||||
* Unwatch a channel
|
||||
*/
|
||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||
// Get 'channelId' parameter
|
||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
||||
if (channelIdErr) return rej('invalid channelId param');
|
||||
|
||||
//#region Fetch channel
|
||||
const channel = await Channel.findOne({
|
||||
_id: channelId
|
||||
});
|
||||
|
||||
if (channel === null) {
|
||||
return rej('channel not found');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Check whether not watching
|
||||
const exist = await Watching.findOne({
|
||||
userId: user._id,
|
||||
channelId: channel._id,
|
||||
deletedAt: { $exists: false }
|
||||
});
|
||||
|
||||
if (exist === null) {
|
||||
return rej('already not watching');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// Delete watching
|
||||
await Watching.update({
|
||||
_id: exist._id
|
||||
}, {
|
||||
$set: {
|
||||
deletedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Send response
|
||||
res();
|
||||
|
||||
// Decrement watching count
|
||||
Channel.update(channel._id, {
|
||||
$inc: {
|
||||
watchingCount: -1
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import Channel from '../../../../models/channel';
|
||||
import Watching from '../../../../models/channel-watching';
|
||||
|
||||
/**
|
||||
* Watch a channel
|
||||
*/
|
||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||
// Get 'channelId' parameter
|
||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
||||
if (channelIdErr) return rej('invalid channelId param');
|
||||
|
||||
//#region Fetch channel
|
||||
const channel = await Channel.findOne({
|
||||
_id: channelId
|
||||
});
|
||||
|
||||
if (channel === null) {
|
||||
return rej('channel not found');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Check whether already watching
|
||||
const exist = await Watching.findOne({
|
||||
userId: user._id,
|
||||
channelId: channel._id,
|
||||
deletedAt: { $exists: false }
|
||||
});
|
||||
|
||||
if (exist !== null) {
|
||||
return rej('already watching');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// Create Watching
|
||||
await Watching.insert({
|
||||
createdAt: new Date(),
|
||||
userId: user._id,
|
||||
channelId: channel._id
|
||||
});
|
||||
|
||||
// Send response
|
||||
res();
|
||||
|
||||
// Increment watching count
|
||||
Channel.update(channel._id, {
|
||||
$inc: {
|
||||
watchingCount: 1
|
||||
}
|
||||
});
|
||||
});
|
|
@ -4,7 +4,6 @@
|
|||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
|
||||
import User, { ILocalUser } from '../../../../models/user';
|
||||
import Channel, { IChannel } from '../../../../models/channel';
|
||||
import DriveFile from '../../../../models/drive-file';
|
||||
import create from '../../../../services/note/create';
|
||||
import { IApp } from '../../../../models/app';
|
||||
|
@ -89,7 +88,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
|||
if (renoteIdErr) return rej('invalid renoteId');
|
||||
|
||||
let renote: INote = null;
|
||||
let isQuote = false;
|
||||
if (renoteId !== undefined) {
|
||||
// Fetch renote to note
|
||||
renote = await Note.findOne({
|
||||
|
@ -101,8 +99,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
|||
} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
|
||||
return rej('cannot renote to renote');
|
||||
}
|
||||
|
||||
isQuote = text != null || files != null;
|
||||
}
|
||||
|
||||
// Get 'replyId' parameter
|
||||
|
@ -126,47 +122,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
|||
}
|
||||
}
|
||||
|
||||
// Get 'channelId' parameter
|
||||
const [channelId, channelIdErr] = $.type(ID).optional().get(params.channelId);
|
||||
if (channelIdErr) return rej('invalid channelId');
|
||||
|
||||
let channel: IChannel = null;
|
||||
if (channelId !== undefined) {
|
||||
// Fetch channel
|
||||
channel = await Channel.findOne({
|
||||
_id: channelId
|
||||
});
|
||||
|
||||
if (channel === null) {
|
||||
return rej('channel not found');
|
||||
}
|
||||
|
||||
// 返信対象の投稿がこのチャンネルじゃなかったらダメ
|
||||
if (reply && !channelId.equals(reply.channelId)) {
|
||||
return rej('チャンネル内部からチャンネル外部の投稿に返信することはできません');
|
||||
}
|
||||
|
||||
// Renote対象の投稿がこのチャンネルじゃなかったらダメ
|
||||
if (renote && !channelId.equals(renote.channelId)) {
|
||||
return rej('チャンネル内部からチャンネル外部の投稿をRenoteすることはできません');
|
||||
}
|
||||
|
||||
// 引用ではないRenoteはダメ
|
||||
if (renote && !isQuote) {
|
||||
return rej('チャンネル内部では引用ではないRenoteをすることはできません');
|
||||
}
|
||||
} else {
|
||||
// 返信対象の投稿がチャンネルへの投稿だったらダメ
|
||||
if (reply && reply.channelId != null) {
|
||||
return rej('チャンネル外部からチャンネル内部の投稿に返信することはできません');
|
||||
}
|
||||
|
||||
// Renote対象の投稿がチャンネルへの投稿だったらダメ
|
||||
if (renote && renote.channelId != null) {
|
||||
return rej('チャンネル外部からチャンネル内部の投稿をRenoteすることはできません');
|
||||
}
|
||||
}
|
||||
|
||||
// Get 'poll' parameter
|
||||
const [poll, pollErr] = $.obj.optional().strict()
|
||||
.have('choices', $.arr($.str)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||
import Note from '../../../../models/note';
|
||||
import Mute from '../../../../models/mute';
|
||||
import ChannelWatching from '../../../../models/channel-watching';
|
||||
import { getFriends } from '../../common/get-friends';
|
||||
import { pack } from '../../../../models/note';
|
||||
|
||||
|
@ -45,18 +44,11 @@ module.exports = async (params, user, app) => {
|
|||
const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
|
||||
if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
|
||||
|
||||
const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([
|
||||
const [followings, mutedUserIds] = await Promise.all([
|
||||
// フォローを取得
|
||||
// Fetch following
|
||||
getFriends(user._id),
|
||||
|
||||
// Watchしているチャンネルを取得
|
||||
ChannelWatching.find({
|
||||
userId: user._id,
|
||||
// 削除されたドキュメントは除く
|
||||
deletedAt: { $exists: false }
|
||||
}).then(watches => watches.map(w => w.channelId)),
|
||||
|
||||
// ミュートしているユーザーを取得
|
||||
Mute.find({
|
||||
muterId: user._id
|
||||
|
@ -93,26 +85,9 @@ module.exports = async (params, user, app) => {
|
|||
|
||||
const query = {
|
||||
$and: [{
|
||||
$or: [{
|
||||
$and: [{
|
||||
// フォローしている人のタイムラインへの投稿
|
||||
$or: followQuery
|
||||
}, {
|
||||
// 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
|
||||
$or: [{
|
||||
channelId: {
|
||||
$exists: false
|
||||
}
|
||||
}, {
|
||||
channelId: null
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
// Watchしているチャンネルへの投稿
|
||||
channelId: {
|
||||
$in: watchingChannelIds
|
||||
}
|
||||
}],
|
||||
// フォローしている人の投稿
|
||||
$or: followQuery,
|
||||
|
||||
// mute
|
||||
userId: {
|
||||
$nin: mutedUserIds
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import * as websocket from 'websocket';
|
||||
import * as redis from 'redis';
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
|
||||
export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void {
|
||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
||||
const channel = q.channel;
|
||||
|
||||
// Subscribe channel stream
|
||||
subscriber.subscribe(`misskey:channel-stream:${channel}`);
|
||||
subscriber.on('message', (_, data) => {
|
||||
connection.send(data);
|
||||
});
|
||||
}
|
|
@ -14,7 +14,6 @@ import othelloGameStream from './stream/othello-game';
|
|||
import othelloStream from './stream/othello';
|
||||
import serverStream from './stream/server';
|
||||
import requestsStream from './stream/requests';
|
||||
import channelStream from './stream/channel';
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
import authenticate from './authenticate';
|
||||
|
||||
|
@ -48,11 +47,6 @@ module.exports = (server: http.Server) => {
|
|||
subscriber.quit();
|
||||
});
|
||||
|
||||
if (request.resourceURL.pathname === '/channel') {
|
||||
channelStream(request, connection, subscriber);
|
||||
return;
|
||||
}
|
||||
|
||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
||||
const [user, app] = await authenticate(q.i as string);
|
||||
|
||||
|
|
Loading…
Reference in a new issue