Clean up
This commit is contained in:
@ -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>
|
||||
|
Reference in New Issue
Block a user