This commit is contained in:
syuilo 2017-11-01 03:17:14 +09:00
parent e7be9248a1
commit ef3100fb8e
25 changed files with 189 additions and 86 deletions

View file

@ -25,6 +25,7 @@ Note that Misskey uses following subdomains:
* **api**.*{primary domain}*
* **auth**.*{primary domain}*
* **about**.*{primary domain}*
* **ch**.*{primary domain}*
* **stats**.*{primary domain}*
* **status**.*{primary domain}*
* **dev**.*{primary domain}*

View file

@ -26,6 +26,7 @@ Misskeyは以下のサブドメインを使います:
* **api**.*{primary domain}*
* **auth**.*{primary domain}*
* **about**.*{primary domain}*
* **ch**.*{primary domain}*
* **stats**.*{primary domain}*
* **status**.*{primary domain}*
* **dev**.*{primary domain}*

View file

@ -164,6 +164,12 @@ common:
mk-uploader:
waiting: "Waiting"
ch:
tags:
mk-index:
new: "Create new channel"
channel-title: "Channel title"
desktop:
tags:
mk-api-info:
@ -241,7 +247,7 @@ desktop:
mk-ui-header-nav:
home: "Home"
messaging: "Messages"
channels: "Channels"
ch: "Channels"
info: "News"
mk-ui-header-search:
@ -352,10 +358,6 @@ desktop:
mk-repost-form-window:
title: "Are you sure you want to repost this post?"
mk-channels-page:
new: "Create new channel"
channel-title: "Channel title"
mobile:
tags:
mk-drive-file-viewer:
@ -496,6 +498,7 @@ mobile:
home: "Home"
notifications: "Notifications"
messaging: "Messages"
ch: "Channels"
drive: "Drive"
settings: "Settings"
about: "About Misskey"

View file

@ -164,6 +164,12 @@ common:
mk-uploader:
waiting: "待機中"
ch:
tags:
mk-index:
new: "チャンネルを作成"
channel-title: "チャンネルのタイトル"
desktop:
tags:
mk-api-info:
@ -241,7 +247,7 @@ desktop:
mk-ui-header-nav:
home: "ホーム"
messaging: "メッセージ"
channels: "チャンネル"
ch: "チャンネル"
info: "お知らせ"
mk-ui-header-search:
@ -352,10 +358,6 @@ desktop:
mk-repost-form-window:
title: "この投稿をRepostしますか"
mk-channels-page:
new: "チャンネルを作成"
channel-title: "チャンネルのタイトル"
mobile:
tags:
mk-drive-file-viewer:
@ -496,6 +498,7 @@ mobile:
home: "ホーム"
notifications: "通知"
messaging: "メッセージ"
ch: "チャンネル"
search: "検索"
drive: "ドライブ"
settings: "設定"

View file

@ -13,7 +13,7 @@ import Watching from '../../models/post-watching';
import serialize from '../../serializers/post';
import notify from '../../common/notify';
import watch from '../../common/watch-post';
import event from '../../event';
import { default as event, publishChannelStream } from '../../event';
import config from '../../../conf';
/**
@ -258,6 +258,11 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
// Publish event to myself's stream
event(user._id, 'post', postObj);
// Publish event to channel
if (channel) {
publishChannelStream(channel._id, 'post', postObj);
}
// Fetch all followers
const followers = await Following
.find({

View file

@ -25,6 +25,10 @@ class MisskeyEvent {
this.publish(`messaging-stream:${userId}-${otherpartyId}`, 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);
}
private publish(channel: string, type: string, value?: any): void {
const message = value == null ?
{ type: type } :
@ -41,3 +45,5 @@ export default ev.publishUserStream.bind(ev);
export const publishPostStream = ev.publishPostStream.bind(ev);
export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
export const publishChannelStream = ev.publishChannelStream.bind(ev);

12
src/api/stream/channel.ts Normal file
View file

@ -0,0 +1,12 @@
import * as websocket from 'websocket';
import * as redis from 'redis';
export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void {
const channel = request.resourceURL.query.channel;
// Subscribe channel stream
subscriber.subscribe(`misskey:channel-stream:${channel}`);
subscriber.on('message', (_, data) => {
connection.send(data);
});
}

View file

@ -9,6 +9,7 @@ import isNativeToken from './common/is-native-token';
import homeStream from './stream/home';
import messagingStream from './stream/messaging';
import serverStream from './stream/server';
import channelStream from './stream/channel';
module.exports = (server: http.Server) => {
/**
@ -26,14 +27,6 @@ module.exports = (server: http.Server) => {
return;
}
const user = await authenticate(request.resourceURL.query.i);
if (user == null) {
connection.send('authentication-failed');
connection.close();
return;
}
// Connect to Redis
const subscriber = redis.createClient(
config.redis.port, config.redis.host);
@ -43,6 +36,19 @@ module.exports = (server: http.Server) => {
subscriber.quit();
});
if (request.resourceURL.pathname === '/channel') {
channelStream(request, connection, subscriber);
return;
}
const user = await authenticate(request.resourceURL.query.i);
if (user == null) {
connection.send('authentication-failed');
connection.close();
return;
}
const channel =
request.resourceURL.pathname === '/' ? homeStream :
request.resourceURL.pathname === '/messaging' ? messagingStream :

View file

@ -88,6 +88,7 @@ type Mixin = {
api_url: string;
auth_url: string;
about_url: string;
ch_url: stirng;
stats_url: string;
status_url: string;
dev_url: string;
@ -122,6 +123,7 @@ export default function load() {
mixin.secondary_scheme = config.secondary_url.substr(0, config.secondary_url.indexOf('://'));
mixin.api_url = `${mixin.scheme}://api.${mixin.host}`;
mixin.auth_url = `${mixin.scheme}://auth.${mixin.host}`;
mixin.ch_url = `${mixin.scheme}://ch.${mixin.host}`;
mixin.dev_url = `${mixin.scheme}://dev.${mixin.host}`;
mixin.about_url = `${mixin.scheme}://about.${mixin.host}`;
mixin.stats_url = `${mixin.scheme}://stats.${mixin.host}`;

32
src/web/app/ch/router.js Normal file
View file

@ -0,0 +1,32 @@
import * as riot from 'riot';
const route = require('page');
let page = null;
export default me => {
route('/', index);
route('/:channel', channel);
route('*', notFound);
function index() {
mount(document.createElement('mk-index'));
}
function channel(ctx) {
const el = document.createElement('mk-channel');
el.setAttribute('id', ctx.params.channel);
mount(el);
}
function notFound() {
mount(document.createElement('mk-not-found'));
}
// EXEC
route();
};
function mount(content) {
if (page) page.unmount();
const body = document.getElementById('app');
page = riot.mount(body.appendChild(content))[0];
}

18
src/web/app/ch/script.js Normal file
View file

@ -0,0 +1,18 @@
/**
* Channels
*/
// Style
import './style.styl';
require('./tags');
import init from '../init';
import route from './router';
/**
* init
*/
init(me => {
// Start routing
route(me);
});

View file

@ -0,0 +1,4 @@
@import "../base"
html
background #efefef

View file

@ -1,14 +1,19 @@
<mk-channel-page>
<mk-ui ref="ui">
<main if={ !parent.fetching }>
<h1>{ parent.channel.title }</h1>
<virtual if={ parent.posts }>
<mk-channel-post each={ parent.posts.reverse() } post={ this } form={ parent.refs.form }/>
</virtual>
<hr>
<mk-channel-form channel={ parent.channel } ref="form"/>
</main>
</mk-ui>
<mk-channel>
<main if={ !fetching }>
<h1>{ channel.title }</h1>
<virtual if={ posts }>
<mk-channel-post each={ posts.slice().reverse() } post={ this } form={ parent.refs.form }/>
</virtual>
<hr>
<mk-channel-form if={ SIGNIN } channel={ channel } ref="form"/>
<div if={ !SIGNIN }>
<p>参加するには<a href={ CONFIG.url }>ログインまたは新規登録</a>してください</p>
</div>
<hr>
<footer>
<small>Misskey ver { version } (葵 aoi)</small>
</footer>
</main>
<style>
:scope
display block
@ -20,16 +25,18 @@
color #f00
</style>
<script>
import Progress from '../../../common/scripts/loading';
import ChannelStream from '../../../common/scripts/channel-stream';
import Progress from '../../common/scripts/loading';
import ChannelStream from '../../common/scripts/channel-stream';
this.mixin('i');
this.mixin('api');
this.id = this.opts.id;
this.fetching = true;
this.channel = null;
this.posts = null;
this.connection = new ChannelStream();
this.connection = new ChannelStream(this.id);
this.version = VERSION;
this.on('mount', () => {
document.documentElement.style.background = '#efefef';
@ -56,9 +63,22 @@
posts: posts
});
});
this.connection.on('post', this.onPost);
});
this.on('unmount', () => {
this.connection.off('post', this.onPost);
this.connection.close();
});
this.onPost = post => {
this.posts.unshift(post);
this.update();
};
</script>
</mk-channel-page>
</mk-channel>
<mk-channel-post>
<header>
@ -127,7 +147,7 @@
</style>
<script>
import CONFIG from '../../../common/scripts/config';
import CONFIG from '../../common/scripts/config';
this.mixin('api');

View file

@ -0,0 +1,2 @@
require('./index.tag');
require('./channel.tag');

View file

@ -0,0 +1,24 @@
<mk-index>
<button onclick={ new }>%i18n:ch.tags.mk-index.new%</button>
<style>
:scope
display block
</style>
<script>
this.mixin('api');
this.on('mount', () => {
});
this.new = () => {
const title = window.prompt('%i18n:ch.tags.mk-index.channel-title%');
this.api('channels/create', {
title: title
}).then(channel => {
location.href = '/' + channel.id;
});
};
</script>
</mk-index>

View file

@ -6,8 +6,10 @@ import Stream from './stream';
* Channel stream connection
*/
class Connection extends Stream {
constructor() {
super('channel');
constructor(channelId) {
super('channel', {
channel: channelId
});
}
}

View file

@ -6,6 +6,7 @@ const host = isRoot ? Url.host : Url.host.substring(Url.host.indexOf('.') + 1, U
const scheme = Url.protocol;
const url = `${scheme}//${host}`;
const apiUrl = `${scheme}//api.${host}`;
const chUrl = `${scheme}//ch.${host}`;
const devUrl = `${scheme}//dev.${host}`;
const aboutUrl = `${scheme}//about.${host}`;
const statsUrl = `${scheme}//stats.${host}`;
@ -16,6 +17,7 @@ export default {
scheme,
url,
apiUrl,
chUrl,
devUrl,
aboutUrl,
statsUrl,

View file

@ -10,8 +10,6 @@ export default me => {
route('/', index);
route('/selectdrive', selectDrive);
route('/i>mentions', mentions);
route('/channel', channels);
route('/channel/:channel', channel);
route('/post::post', post);
route('/search::query', search);
route('/:user', user.bind(null, 'home'));
@ -57,16 +55,6 @@ export default me => {
mount(el);
}
function channel(ctx) {
const el = document.createElement('mk-channel-page');
el.setAttribute('id', ctx.params.channel);
mount(el);
}
function channels() {
mount(document.createElement('mk-channels-page'));
}
function selectDrive() {
mount(document.createElement('mk-selectdrive-page'));
}

View file

@ -61,8 +61,6 @@ require('./pages/user.tag');
require('./pages/post.tag');
require('./pages/search.tag');
require('./pages/not-found.tag');
require('./pages/channel.tag');
require('./pages/channels.tag');
require('./pages/selectdrive.tag');
require('./autocomplete-suggestion.tag');
require('./progress-dialog.tag');

View file

@ -1,28 +0,0 @@
<mk-channels-page>
<mk-ui ref="ui">
<main>
<button onclick={ parent.new }>%i18n:desktop.tags.mk-channels-page.new%</button>
</main>
</mk-ui>
<style>
:scope
display block
</style>
<script>
this.mixin('api');
this.on('mount', () => {
});
this.new = () => {
const title = window.prompt('%i18n:desktop.tags.mk-channels-page.channel-title%');
this.api('channels/create', {
title: title
}).then(channel => {
location.href = '/channel/' + channel.id;
});
};
</script>
</mk-channels-page>

View file

@ -112,7 +112,7 @@
</header>
<div class="body">
<div class="text" ref="text">
<p class="channel" if={ p.channel != null }><a href={ '/channel/' + p.channel.id }>{ p.channel.title }</a>:</p>
<p class="channel" if={ p.channel != null }><a href={ CONFIG.chUrl + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p>
<a class="reply" if={ p.reply_to }>
<i class="fa fa-reply"></i>
</a>

View file

@ -335,10 +335,10 @@
</a>
</li>
</virtual>
<li class="channels">
<a href={ CONFIG.url + '/channel' }>
<li class="ch">
<a href={ CONFIG.chUrl } target="_blank">
<i class="fa fa-television"></i>
<p>%i18n:desktop.tags.mk-ui-header-nav.channels%</p>
<p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p>
</a>
</li>
<li class="info">

View file

@ -164,7 +164,7 @@
</header>
<div class="body">
<div class="text" ref="text">
<p class="channel" if={ p.channel != null }><a href={ '/channel/' + p.channel.id }>{ p.channel.title }</a>:</p>
<p class="channel" if={ p.channel != null }><a href={ CONFIG.chUrl + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p>
<a class="reply" if={ p.reply_to }>
<i class="fa fa-reply"></i>
</a>

View file

@ -231,10 +231,11 @@
<li><a href="/i/messaging"><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-ui-nav.messaging%<i class="i fa fa-circle" if={ hasUnreadMessagingMessages }></i><i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
<li><a onclick={ search }><i class="fa fa-search"></i>%i18n:mobile.tags.mk-ui-nav.search%<i class="fa fa-angle-right"></i></a></li>
<li><a href={ CONFIG.chUrl } target="_blank"><i class="fa fa-television"></i>%i18n:mobile.tags.mk-ui-nav.ch%<i class="fa fa-angle-right"></i></a></li>
<li><a href="/i/drive"><i class="fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
<li><a href="/i/drive"><i class="fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="fa fa-angle-right"></i></a></li>
<li><a onclick={ search }><i class="fa fa-search"></i>%i18n:mobile.tags.mk-ui-nav.search%<i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
<li><a href="/i/settings"><i class="fa fa-cog"></i>%i18n:mobile.tags.mk-ui-nav.settings%<i class="fa fa-angle-right"></i></a></li>

View file

@ -16,6 +16,7 @@ module.exports = langs.map(([lang, locale]) => {
const entry = {
desktop: './src/web/app/desktop/script.js',
mobile: './src/web/app/mobile/script.js',
ch: './src/web/app/ch/script.js',
stats: './src/web/app/stats/script.js',
status: './src/web/app/status/script.js',
dev: './src/web/app/dev/script.js',