Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					921609cab1 | ||
| 
						 | 
					199573ccee | ||
| 
						 | 
					977200b7cd | ||
| 
						 | 
					6abff253ea | ||
| 
						 | 
					ba64de334a | ||
| 
						 | 
					dc1d7fa9d7 | ||
| 
						 | 
					f42665d4bc | ||
| 
						 | 
					a5eb19c878 | ||
| 
						 | 
					a9ae9a65c8 | ||
| 
						 | 
					3698c679e2 | ||
| 
						 | 
					ba38f64353 | ||
| 
						 | 
					db3ae303cb | ||
| 
						 | 
					66f3a155e6 | ||
| 
						 | 
					09843a409b | ||
| 
						 | 
					d94b3757be | ||
| 
						 | 
					13e822cba6 | ||
| 
						 | 
					c57bf87f52 | ||
| 
						 | 
					99fbd60265 | ||
| 
						 | 
					ea9b48db3c | ||
| 
						 | 
					c145c994a9 | ||
| 
						 | 
					d033998b56 | ||
| 
						 | 
					3136c714bf | ||
| 
						 | 
					c0ee134f19 | ||
| 
						 | 
					d15ebe5732 | ||
| 
						 | 
					ef630195fa | ||
| 
						 | 
					e31921151e | ||
| 
						 | 
					f94992abbe | ||
| 
						 | 
					b00060c09c | ||
| 
						 | 
					f6217d96d2 | ||
| 
						 | 
					3a6947c7ed | 
@@ -45,7 +45,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/1?token-time=2145916800&token-hash=DVrSdOqQq2dufgNgWZ3XMnEtl_ZAktr8Lhf2tbHKtoA%3D" alt="Axella"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Axella"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
 | 
			
		||||
@@ -65,18 +65,16 @@ Please see [Contribution guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Sun, 02 Sep 2018 00:05:05 UTC
 | 
			
		||||
**Last updated:** Sun, 02 Sep 2018 05:30:06 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ Please visit https://www.google.com/recaptcha/intro/ and generate keys.
 | 
			
		||||
 | 
			
		||||
*(optional)* Generating VAPID keys
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
If you want to enable ServiceWroker, you need to generate VAPID keys:
 | 
			
		||||
If you want to enable ServiceWorker, you need to generate VAPID keys:
 | 
			
		||||
Unless you have set your global node_modules location elsewhere, you need to run this in root.
 | 
			
		||||
 | 
			
		||||
``` shell
 | 
			
		||||
 
 | 
			
		||||
@@ -375,6 +375,10 @@ common/views/components/visibility-chooser.vue:
 | 
			
		||||
  specified-desc: "指定したユーザーにのみ公開"
 | 
			
		||||
  private: "非公開"
 | 
			
		||||
 | 
			
		||||
common/views/components/trends.vue:
 | 
			
		||||
  count: "{}人が投稿"
 | 
			
		||||
  empty: "トレンドなし"
 | 
			
		||||
 | 
			
		||||
common/views/widgets/broadcast.vue:
 | 
			
		||||
  fetching: "確認中"
 | 
			
		||||
  no-broadcasts: "お知らせはありません"
 | 
			
		||||
@@ -403,8 +407,6 @@ common/views/widgets/posts-monitor.vue:
 | 
			
		||||
 | 
			
		||||
common/views/widgets/hashtags.vue:
 | 
			
		||||
  title: "ハッシュタグ"
 | 
			
		||||
  count: "{}人が投稿"
 | 
			
		||||
  empty: "トレンドなし"
 | 
			
		||||
 | 
			
		||||
common/views/widgets/server.vue:
 | 
			
		||||
  title: "サーバー情報"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "8.21.1",
 | 
			
		||||
	"clientVersion": "1.0.9264",
 | 
			
		||||
	"version": "8.25.0",
 | 
			
		||||
	"clientVersion": "1.0.9297",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,10 @@ import MiOS from '../../../../../mios';
 | 
			
		||||
 | 
			
		||||
export class ReversiGameStream extends Stream {
 | 
			
		||||
	constructor(os: MiOS, me, game) {
 | 
			
		||||
		super(os, 'games/reversi-game', {
 | 
			
		||||
			i: me ? me.token : null,
 | 
			
		||||
		super(os, 'games/reversi-game', me ? {
 | 
			
		||||
			i: me.token,
 | 
			
		||||
			game: game.id
 | 
			
		||||
		} : {
 | 
			
		||||
			game: game.id
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,9 @@ import MiOS from '../../../mios';
 | 
			
		||||
 */
 | 
			
		||||
export class LocalTimelineStream extends Stream {
 | 
			
		||||
	constructor(os: MiOS, me) {
 | 
			
		||||
		super(os, 'local-timeline', {
 | 
			
		||||
		super(os, 'local-timeline', me ? {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
		} : {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import trends from './trends.vue';
 | 
			
		||||
import analogClock from './analog-clock.vue';
 | 
			
		||||
import menu from './menu.vue';
 | 
			
		||||
import noteHeader from './note-header.vue';
 | 
			
		||||
@@ -40,6 +41,7 @@ import uiSelect from './ui/select.vue';
 | 
			
		||||
import formButton from './ui/form/button.vue';
 | 
			
		||||
import formRadio from './ui/form/radio.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-trends', trends);
 | 
			
		||||
Vue.component('mk-analog-clock', analogClock);
 | 
			
		||||
Vue.component('mk-menu', menu);
 | 
			
		||||
Vue.component('mk-note-header', noteHeader);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-menu">
 | 
			
		||||
<div class="onchrpzrvnoruiaenfcqvccjfuupzzwv">
 | 
			
		||||
	<div class="backdrop" ref="backdrop" @click="close"></div>
 | 
			
		||||
	<div class="popover" :class="{ hukidasi }" ref="popover">
 | 
			
		||||
		<template v-for="item in items">
 | 
			
		||||
@@ -119,9 +119,10 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
$border-color = rgba(27, 31, 35, 0.15)
 | 
			
		||||
root(isDark)
 | 
			
		||||
	$bg-color = isDark ? #2c303c : #fff
 | 
			
		||||
	$border-color = rgba(27, 31, 35, 0.15)
 | 
			
		||||
 | 
			
		||||
.mk-menu
 | 
			
		||||
	position initial
 | 
			
		||||
 | 
			
		||||
	> .backdrop
 | 
			
		||||
@@ -131,14 +132,14 @@ $border-color = rgba(27, 31, 35, 0.15)
 | 
			
		||||
		z-index 10000
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100%
 | 
			
		||||
		background rgba(#000, 0.1)
 | 
			
		||||
		background rgba(#000, isDark ? 0.5 : 0.1)
 | 
			
		||||
		opacity 0
 | 
			
		||||
 | 
			
		||||
	> .popover
 | 
			
		||||
		position absolute
 | 
			
		||||
		z-index 10001
 | 
			
		||||
		padding 8px 0
 | 
			
		||||
		background #fff
 | 
			
		||||
		background $bg-color
 | 
			
		||||
		border 1px solid $border-color
 | 
			
		||||
		border-radius 4px
 | 
			
		||||
		box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
 | 
			
		||||
@@ -172,12 +173,13 @@ $border-color = rgba(27, 31, 35, 0.15)
 | 
			
		||||
				border-top solid $balloon-size transparent
 | 
			
		||||
				border-left solid $balloon-size transparent
 | 
			
		||||
				border-right solid $balloon-size transparent
 | 
			
		||||
				border-bottom solid $balloon-size #fff
 | 
			
		||||
				border-bottom solid $balloon-size $bg-color
 | 
			
		||||
 | 
			
		||||
		> button
 | 
			
		||||
			display block
 | 
			
		||||
			padding 8px 16px
 | 
			
		||||
			width 100%
 | 
			
		||||
			color isDark ? #d6dce2 : #111
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				color $theme-color-foreground
 | 
			
		||||
@@ -191,6 +193,12 @@ $border-color = rgba(27, 31, 35, 0.15)
 | 
			
		||||
		> div
 | 
			
		||||
			margin 8px 0
 | 
			
		||||
			height 1px
 | 
			
		||||
			background #eee
 | 
			
		||||
			background isDark ? #1c2023 : #eee
 | 
			
		||||
 | 
			
		||||
.onchrpzrvnoruiaenfcqvccjfuupzzwv[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.onchrpzrvnoruiaenfcqvccjfuupzzwv:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ export default Vue.extend({
 | 
			
		||||
			cursor wait !important
 | 
			
		||||
 | 
			
		||||
	> .avatar
 | 
			
		||||
		margin 16px auto 0 auto
 | 
			
		||||
		margin 0 auto 0 auto
 | 
			
		||||
		width 64px
 | 
			
		||||
		height 64px
 | 
			
		||||
		background #ddd
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								src/client/app/common/views/components/trends.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/client/app/common/views/components/trends.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc">
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
			
		||||
	<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
 | 
			
		||||
	<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
			
		||||
	<!-- <transition-group v-else tag="div" name="chart"> -->
 | 
			
		||||
	<div>
 | 
			
		||||
		<div v-for="stat in stats" :key="stat.tag">
 | 
			
		||||
			<div class="tag">
 | 
			
		||||
				<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
 | 
			
		||||
				<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
 | 
			
		||||
			</div>
 | 
			
		||||
			<x-chart class="chart" :src="stat.chart"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<!-- </transition-group> -->
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XChart from './trends.chart.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XChart
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			stats: [],
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			clock: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetch();
 | 
			
		||||
		this.clock = setInterval(this.fetch, 1000 * 60);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		clearInterval(this.clock);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			(this as any).api('hashtags/trend').then(stats => {
 | 
			
		||||
				this.stats = stats;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	> .fetching
 | 
			
		||||
	> .empty
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
 | 
			
		||||
		> [data-fa]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		.chart-move
 | 
			
		||||
			transition transform 1s ease
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			display flex
 | 
			
		||||
			align-items center
 | 
			
		||||
			padding 14px 16px
 | 
			
		||||
 | 
			
		||||
			&:not(:last-child)
 | 
			
		||||
				border-bottom solid 1px isDark ? #393f4f : #eee
 | 
			
		||||
 | 
			
		||||
			> .tag
 | 
			
		||||
				flex 1
 | 
			
		||||
				overflow hidden
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				color isDark ? #9baec8 : #65727b
 | 
			
		||||
 | 
			
		||||
				> a
 | 
			
		||||
					display block
 | 
			
		||||
					width 100%
 | 
			
		||||
					white-space nowrap
 | 
			
		||||
					overflow hidden
 | 
			
		||||
					text-overflow ellipsis
 | 
			
		||||
					color inherit
 | 
			
		||||
 | 
			
		||||
				> p
 | 
			
		||||
					margin 0
 | 
			
		||||
					font-size 75%
 | 
			
		||||
					opacity 0.7
 | 
			
		||||
 | 
			
		||||
			> .chart
 | 
			
		||||
				height 30px
 | 
			
		||||
 | 
			
		||||
.csqvmxybqbycalfhkxvyfrgbrdalkaoc[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.csqvmxybqbycalfhkxvyfrgbrdalkaoc:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { toUnicode as decodePunycode } from 'punycode';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['url', 'target'],
 | 
			
		||||
	data() {
 | 
			
		||||
@@ -27,11 +28,11 @@ export default Vue.extend({
 | 
			
		||||
	created() {
 | 
			
		||||
		const url = new URL(this.url);
 | 
			
		||||
		this.schema = url.protocol;
 | 
			
		||||
		this.hostname = url.hostname;
 | 
			
		||||
		this.hostname = decodePunycode(url.hostname);
 | 
			
		||||
		this.port = url.port;
 | 
			
		||||
		this.pathname = url.pathname;
 | 
			
		||||
		this.query = url.search;
 | 
			
		||||
		this.hash = url.hash;
 | 
			
		||||
		this.pathname = decodeURIComponent(url.pathname);
 | 
			
		||||
		this.query = decodeURIComponent(url.search);
 | 
			
		||||
		this.hash = decodeURIComponent(url.hash);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -31,15 +31,30 @@ export default Vue.extend({
 | 
			
		||||
			default: undefined
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			notes: []
 | 
			
		||||
			notes: [],
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetch();
 | 
			
		||||
 | 
			
		||||
		this.connection = (this as any).os.streams.localTimelineStream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.streams.localTimelineStream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('note', this.onNote);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('note', this.onNote);
 | 
			
		||||
		(this as any).os.streams.localTimelineStream.dispose(this.connectionId);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch(cb?) {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
@@ -49,13 +64,20 @@ export default Vue.extend({
 | 
			
		||||
				reply: false,
 | 
			
		||||
				renote: false,
 | 
			
		||||
				media: false,
 | 
			
		||||
				poll: false,
 | 
			
		||||
				bot: false
 | 
			
		||||
				poll: false
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				this.notes = notes;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			if (note.replyId != null) return;
 | 
			
		||||
			if (note.renoteId != null) return;
 | 
			
		||||
			if (note.poll != null) return;
 | 
			
		||||
 | 
			
		||||
			this.notes.unshift(note);
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-broadcast"
 | 
			
		||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi"
 | 
			
		||||
	:data-found="broadcasts.length != 0"
 | 
			
		||||
	:data-melt="props.design == 1"
 | 
			
		||||
	:data-mobile="platform == 'mobile'"
 | 
			
		||||
@@ -25,7 +25,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import define from '../../../common/define-widget';
 | 
			
		||||
import { lang } from '../../../config';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'broadcast',
 | 
			
		||||
@@ -42,15 +41,7 @@ export default define({
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			let broadcasts = [];
 | 
			
		||||
			if (meta.broadcasts) {
 | 
			
		||||
				meta.broadcasts.forEach(broadcast => {
 | 
			
		||||
					if (broadcast[lang]) {
 | 
			
		||||
						broadcasts.push(broadcast[lang]);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			this.broadcasts = broadcasts;
 | 
			
		||||
			this.broadcasts = meta.broadcasts;
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
@@ -75,7 +66,7 @@ export default define({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mkw-broadcast
 | 
			
		||||
root(isDark)
 | 
			
		||||
	padding 10px
 | 
			
		||||
	border solid 1px #4078c0
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
@@ -142,15 +133,11 @@ export default define({
 | 
			
		||||
		z-index 1
 | 
			
		||||
		margin 0
 | 
			
		||||
		font-size 0.7em
 | 
			
		||||
		color #555
 | 
			
		||||
		color isDark ? #fff : #555
 | 
			
		||||
 | 
			
		||||
		&.fetching
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
		a
 | 
			
		||||
			color #555
 | 
			
		||||
			text-decoration underline
 | 
			
		||||
 | 
			
		||||
	> a
 | 
			
		||||
		display block
 | 
			
		||||
		font-size 0.7em
 | 
			
		||||
@@ -159,4 +146,10 @@ export default define({
 | 
			
		||||
		> p
 | 
			
		||||
			color #fff
 | 
			
		||||
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,7 @@
 | 
			
		||||
		<template slot="header">%fa:hashtag%%i18n:@title%</template>
 | 
			
		||||
 | 
			
		||||
		<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
 | 
			
		||||
			<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
			
		||||
			<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
 | 
			
		||||
			<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
			
		||||
			<!-- <transition-group v-else tag="div" name="chart"> -->
 | 
			
		||||
			<div>
 | 
			
		||||
				<div v-for="stat in stats" :key="stat.tag">
 | 
			
		||||
					<div class="tag">
 | 
			
		||||
						<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
 | 
			
		||||
						<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
 | 
			
		||||
					</div>
 | 
			
		||||
					<x-chart class="chart" :src="stat.chart"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<!-- </transition-group> -->
 | 
			
		||||
			<mk-trends/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -25,7 +12,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import define from '../../../common/define-widget';
 | 
			
		||||
import XChart from './hashtags.chart.vue';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'hashtags',
 | 
			
		||||
@@ -33,89 +19,11 @@ export default define({
 | 
			
		||||
		compact: false
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XChart
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			stats: [],
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			clock: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetch();
 | 
			
		||||
		this.clock = setInterval(this.fetch, 1000 * 60);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		clearInterval(this.clock);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		func() {
 | 
			
		||||
			this.props.compact = !this.props.compact;
 | 
			
		||||
			this.save();
 | 
			
		||||
		},
 | 
			
		||||
		fetch() {
 | 
			
		||||
			(this as any).api('hashtags/trend').then(stats => {
 | 
			
		||||
				this.stats = stats;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	.mkw-hashtags--body
 | 
			
		||||
		> .fetching
 | 
			
		||||
		> .empty
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
 | 
			
		||||
			> [data-fa]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			.chart-move
 | 
			
		||||
				transition transform 1s ease
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				display flex
 | 
			
		||||
				align-items center
 | 
			
		||||
				padding 14px 16px
 | 
			
		||||
 | 
			
		||||
				&:not(:last-child)
 | 
			
		||||
					border-bottom solid 1px isDark ? #393f4f : #eee
 | 
			
		||||
 | 
			
		||||
				> .tag
 | 
			
		||||
					flex 1
 | 
			
		||||
					overflow hidden
 | 
			
		||||
					font-size 14px
 | 
			
		||||
					color isDark ? #9baec8 : #65727b
 | 
			
		||||
 | 
			
		||||
					> a
 | 
			
		||||
						display block
 | 
			
		||||
						width 100%
 | 
			
		||||
						white-space nowrap
 | 
			
		||||
						overflow hidden
 | 
			
		||||
						text-overflow ellipsis
 | 
			
		||||
						color inherit
 | 
			
		||||
 | 
			
		||||
					> p
 | 
			
		||||
						margin 0
 | 
			
		||||
						font-size 75%
 | 
			
		||||
						opacity 0.7
 | 
			
		||||
 | 
			
		||||
				> .chart
 | 
			
		||||
					height 30px
 | 
			
		||||
 | 
			
		||||
.mkw-hashtags[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.mkw-hashtags:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
	<button class="upload" title="%i18n:@attach-media-from-local%" @click="chooseFile">%fa:upload%</button>
 | 
			
		||||
	<button class="drive" title="%i18n:@attach-media-from-drive%" @click="chooseFileFromDrive">%fa:cloud%</button>
 | 
			
		||||
	<button class="kao" title="%i18n:@insert-a-kao%" @click="kao">%fa:R smile%</button>
 | 
			
		||||
	<button class="poll" title="%i18n:@create-poll%" @click="poll = true">%fa:chart-pie%</button>
 | 
			
		||||
	<button class="poll" title="%i18n:@create-poll%" @click="poll = !poll">%fa:chart-pie%</button>
 | 
			
		||||
	<button class="poll" title="%i18n:@hide-contents%" @click="useCw = !useCw">%fa:eye-slash%</button>
 | 
			
		||||
	<button class="geo" title="%i18n:@attach-location-information%" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
 | 
			
		||||
	<button class="visibility" title="%i18n:@visibility%" @click="setVisibility" ref="visibilityButton">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
 | 
			
		||||
	<header>%i18n:@announcements%</header>
 | 
			
		||||
	<textarea v-model="broadcasts"></textarea>
 | 
			
		||||
	<button class="ui" @click="save">%i18n:@save%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			broadcasts: '',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.broadcasts = JSON.stringify(meta.broadcasts, null, '  ');
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		save() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				broadcasts: JSON.parse(this.broadcasts)
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.qldxjjsrseehkusjuoooapmsprvfrxyl
 | 
			
		||||
	textarea
 | 
			
		||||
		width 100%
 | 
			
		||||
		min-height 300px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
 | 
			
		||||
			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
 | 
			
		||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
 | 
			
		||||
			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
 | 
			
		||||
			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 | 
			
		||||
		</ul>
 | 
			
		||||
@@ -13,6 +14,9 @@
 | 
			
		||||
			<x-dashboard/>
 | 
			
		||||
			<x-charts/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-show="page == 'announcements'">
 | 
			
		||||
			<x-announcements/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="page == 'users'">
 | 
			
		||||
			<x-suspend-user/>
 | 
			
		||||
			<x-unsuspend-user/>
 | 
			
		||||
@@ -28,6 +32,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import XDashboard from "./admin.dashboard.vue";
 | 
			
		||||
import XAnnouncements from "./admin.announcements.vue";
 | 
			
		||||
import XSuspendUser from "./admin.suspend-user.vue";
 | 
			
		||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
 | 
			
		||||
import XVerifyUser from "./admin.verify-user.vue";
 | 
			
		||||
@@ -37,6 +42,7 @@ import XCharts from "../../components/charts.vue";
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
		XSuspendUser,
 | 
			
		||||
		XUnsuspendUser,
 | 
			
		||||
		XVerifyUser,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,60 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-welcome">
 | 
			
		||||
	<img ref="pointer" class="pointer" src="/assets/pointer.png" alt="">
 | 
			
		||||
	<button @click="dark">
 | 
			
		||||
		<template v-if="$store.state.device.darkmode">%fa:moon%</template>
 | 
			
		||||
		<template v-else>%fa:R moon%</template>
 | 
			
		||||
	</button>
 | 
			
		||||
 | 
			
		||||
	<mk-forkit class="forkit"/>
 | 
			
		||||
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<div class="container">
 | 
			
		||||
		<div class="main block">
 | 
			
		||||
			<h1 v-if="name != 'Misskey'">{{ name }}</h1>
 | 
			
		||||
			<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
 | 
			
		||||
 | 
			
		||||
			<div class="info">
 | 
			
		||||
				<span><b>{{ host }}</b></span>
 | 
			
		||||
				<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span>
 | 
			
		||||
				<span class="stats" v-if="stats">
 | 
			
		||||
					<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
 | 
			
		||||
					<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
 | 
			
		||||
				</span>
 | 
			
		||||
			</div>
 | 
			
		||||
			<main>
 | 
			
		||||
				<div class="about">
 | 
			
		||||
					<h1 v-if="name != 'Misskey'">{{ name }}</h1>
 | 
			
		||||
					<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
 | 
			
		||||
					<p class="powerd-by" v-if="name != 'Misskey'" v-html="'%i18n:@powered-by-misskey%'"></p>
 | 
			
		||||
					<p class="desc" v-html="description || '%i18n:common.about%'"></p>
 | 
			
		||||
					<a ref="signup" @click="signup">📦 %i18n:@signup%</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="login">
 | 
			
		||||
					<mk-signin/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</main>
 | 
			
		||||
			<div class="hashtags">
 | 
			
		||||
				<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
 | 
			
		||||
 | 
			
		||||
			<p class="desc" v-html="description || '%i18n:common.about%'"></p>
 | 
			
		||||
 | 
			
		||||
			<p class="sign">
 | 
			
		||||
				<span class="signup" @click="signup">%i18n:@signup%</span>
 | 
			
		||||
				<span class="divider">|</span>
 | 
			
		||||
				<span class="signin" @click="signin">%i18n:@signin%</span>
 | 
			
		||||
			</p>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="broadcasts block">
 | 
			
		||||
			<div v-for="broadcast in broadcasts">
 | 
			
		||||
				<h1 v-html="broadcast.title"></h1>
 | 
			
		||||
				<div v-html="broadcast.text"></div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="nav block">
 | 
			
		||||
			<mk-nav class="nav"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<mk-forkit class="forkit"/>
 | 
			
		||||
		<img src="assets/title.dark.svg" :alt="name">
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="tl">
 | 
			
		||||
		<mk-welcome-timeline :max="20"/>
 | 
			
		||||
 | 
			
		||||
		<div class="side">
 | 
			
		||||
			<mk-trends class="trends block"/>
 | 
			
		||||
 | 
			
		||||
			<mk-welcome-timeline class="tl block" :max="20"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<modal name="signup" width="500px" height="auto" scrollable>
 | 
			
		||||
		<header :class="$style.signupFormHeader">%i18n:@signup%</header>
 | 
			
		||||
		<mk-signup :class="$style.signupForm"/>
 | 
			
		||||
	<modal name="signup" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable>
 | 
			
		||||
		<header class="formHeader">%i18n:@signup%</header>
 | 
			
		||||
		<mk-signup class="form"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
 | 
			
		||||
	<modal name="signin" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable>
 | 
			
		||||
		<header class="formHeader">%i18n:@signin%</header>
 | 
			
		||||
		<mk-signin class="form"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -57,37 +71,22 @@ export default Vue.extend({
 | 
			
		||||
			host,
 | 
			
		||||
			name: 'Misskey',
 | 
			
		||||
			description: '',
 | 
			
		||||
			pointerInterval: null,
 | 
			
		||||
			tags: []
 | 
			
		||||
			broadcasts: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.name = meta.name;
 | 
			
		||||
			this.description = meta.description;
 | 
			
		||||
			this.broadcasts = meta.broadcasts;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
			this.stats = stats;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('hashtags/trend').then(stats => {
 | 
			
		||||
			this.tags = stats.map(x => x.tag);
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.point();
 | 
			
		||||
		this.pointerInterval = setInterval(this.point, 100);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		clearInterval(this.pointerInterval);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		point() {
 | 
			
		||||
			const x = this.$refs.signup.getBoundingClientRect();
 | 
			
		||||
			this.$refs.pointer.style.top = x.top + x.height + 'px';
 | 
			
		||||
			this.$refs.pointer.style.left = x.left + 'px';
 | 
			
		||||
		},
 | 
			
		||||
		signup() {
 | 
			
		||||
			this.$modal.show('signup');
 | 
			
		||||
		},
 | 
			
		||||
@@ -104,11 +103,40 @@ export default Vue.extend({
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
#wait {
 | 
			
		||||
	right: auto;
 | 
			
		||||
	left: 15px;
 | 
			
		||||
}
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
#wait
 | 
			
		||||
	right auto
 | 
			
		||||
	left 15px
 | 
			
		||||
 | 
			
		||||
.v--modal-overlay
 | 
			
		||||
	background rgba(0, 0, 0, 0.6)
 | 
			
		||||
 | 
			
		||||
.modal-light
 | 
			
		||||
	.v--modal-box
 | 
			
		||||
		color #777
 | 
			
		||||
 | 
			
		||||
		.formHeader
 | 
			
		||||
			border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
.modal-dark
 | 
			
		||||
	.v--modal-box
 | 
			
		||||
		background #313543
 | 
			
		||||
		color #fff
 | 
			
		||||
 | 
			
		||||
		.formHeader
 | 
			
		||||
			border-bottom solid 1px rgba(#000, 0.2)
 | 
			
		||||
 | 
			
		||||
.modal-light
 | 
			
		||||
.modal-dark
 | 
			
		||||
	.form
 | 
			
		||||
		padding 24px 48px 48px 48px
 | 
			
		||||
 | 
			
		||||
	.formHeader
 | 
			
		||||
		text-align center
 | 
			
		||||
		padding 48px 0 12px 0
 | 
			
		||||
		margin 0 48px
 | 
			
		||||
		font-size 1.5em
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@@ -117,122 +145,87 @@ export default Vue.extend({
 | 
			
		||||
root(isDark)
 | 
			
		||||
	display flex
 | 
			
		||||
	min-height 100vh
 | 
			
		||||
	//background-color #00070F
 | 
			
		||||
	//background-image url('/assets/bg.jpg')
 | 
			
		||||
	//background-position center
 | 
			
		||||
	//background-size cover
 | 
			
		||||
 | 
			
		||||
	> .pointer
 | 
			
		||||
		display block
 | 
			
		||||
	> .forkit
 | 
			
		||||
		position absolute
 | 
			
		||||
		z-index 1
 | 
			
		||||
		top 0
 | 
			
		||||
		right 0
 | 
			
		||||
		width 180px
 | 
			
		||||
		margin 0 0 0 -180px
 | 
			
		||||
		transform rotateY(180deg) translateX(-10px) translateY(-48px)
 | 
			
		||||
		pointer-events none
 | 
			
		||||
 | 
			
		||||
	> button
 | 
			
		||||
		position fixed
 | 
			
		||||
		z-index 1
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		bottom 16px
 | 
			
		||||
		left 16px
 | 
			
		||||
		padding 16px
 | 
			
		||||
		font-size 18px
 | 
			
		||||
		color #fff
 | 
			
		||||
 | 
			
		||||
		display none // TODO
 | 
			
		||||
		color isDark ? #fff : #444
 | 
			
		||||
 | 
			
		||||
	> .body
 | 
			
		||||
		flex 1
 | 
			
		||||
		padding 64px 0 0 0
 | 
			
		||||
		text-align center
 | 
			
		||||
		background #578394
 | 
			
		||||
		background-position center
 | 
			
		||||
		background-size cover
 | 
			
		||||
		display grid
 | 
			
		||||
		grid-template-rows 0.5fr 0.5fr 64px
 | 
			
		||||
		grid-template-columns 1fr 350px
 | 
			
		||||
		gap 16px
 | 
			
		||||
		width 100%
 | 
			
		||||
		max-width 1200px
 | 
			
		||||
		height 100vh
 | 
			
		||||
		min-height 800px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		padding 64px
 | 
			
		||||
 | 
			
		||||
		&:before
 | 
			
		||||
			content ''
 | 
			
		||||
			display block
 | 
			
		||||
			position absolute
 | 
			
		||||
			top 0
 | 
			
		||||
			left 0
 | 
			
		||||
			right 0
 | 
			
		||||
			bottom 0
 | 
			
		||||
			background rgba(#000, 0.5)
 | 
			
		||||
		.block
 | 
			
		||||
			color isDark ? #fff : #444
 | 
			
		||||
			background isDark ? #313543 : #fff
 | 
			
		||||
			box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
 | 
			
		||||
			//border-radius 8px
 | 
			
		||||
			overflow auto
 | 
			
		||||
 | 
			
		||||
		> .forkit
 | 
			
		||||
			position absolute
 | 
			
		||||
			top 0
 | 
			
		||||
			right 0
 | 
			
		||||
		> .main
 | 
			
		||||
			grid-row 1
 | 
			
		||||
			grid-column 1
 | 
			
		||||
			padding 32px
 | 
			
		||||
			border-top solid 5px $theme-color
 | 
			
		||||
 | 
			
		||||
		> img
 | 
			
		||||
			position absolute
 | 
			
		||||
			bottom 16px
 | 
			
		||||
			right 16px
 | 
			
		||||
			width 150px
 | 
			
		||||
			> h1
 | 
			
		||||
				margin 0
 | 
			
		||||
 | 
			
		||||
		> .container
 | 
			
		||||
			$aboutWidth = 380px
 | 
			
		||||
			$loginWidth = 340px
 | 
			
		||||
			$width = $aboutWidth + $loginWidth
 | 
			
		||||
				> img
 | 
			
		||||
					margin -8px 0 0 -16px
 | 
			
		||||
					max-width 280px
 | 
			
		||||
 | 
			
		||||
			> .info
 | 
			
		||||
				margin 0 auto 16px auto
 | 
			
		||||
				width $width
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				color #fff
 | 
			
		||||
 | 
			
		||||
				> .stats
 | 
			
		||||
					margin-left 16px
 | 
			
		||||
					padding-left 16px
 | 
			
		||||
					border-left solid 1px #fff
 | 
			
		||||
					border-left solid 1px isDark ? #fff : #444
 | 
			
		||||
 | 
			
		||||
					> *
 | 
			
		||||
						margin-right 16px
 | 
			
		||||
 | 
			
		||||
			> main
 | 
			
		||||
				display flex
 | 
			
		||||
				margin auto
 | 
			
		||||
				width $width
 | 
			
		||||
				border-radius 8px
 | 
			
		||||
				overflow hidden
 | 
			
		||||
				box-shadow 0 2px 8px rgba(#000, 0.3)
 | 
			
		||||
			> .sign
 | 
			
		||||
				font-size 120%
 | 
			
		||||
 | 
			
		||||
				> .about
 | 
			
		||||
					width $aboutWidth
 | 
			
		||||
					color #444
 | 
			
		||||
					background #fff
 | 
			
		||||
				> .divider
 | 
			
		||||
					margin 0 16px
 | 
			
		||||
 | 
			
		||||
					> h1
 | 
			
		||||
						margin 0 0 16px 0
 | 
			
		||||
						padding 32px 32px 0 32px
 | 
			
		||||
						color #444
 | 
			
		||||
				> .signin
 | 
			
		||||
				> .signup
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
						> img
 | 
			
		||||
							width 170px
 | 
			
		||||
							vertical-align bottom
 | 
			
		||||
 | 
			
		||||
					> .powerd-by
 | 
			
		||||
						margin 16px
 | 
			
		||||
						opacity 0.7
 | 
			
		||||
 | 
			
		||||
					> .desc
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0 32px 16px 32px
 | 
			
		||||
 | 
			
		||||
					> a
 | 
			
		||||
						display inline-block
 | 
			
		||||
						margin 0 0 32px 0
 | 
			
		||||
						font-weight bold
 | 
			
		||||
 | 
			
		||||
				> .login
 | 
			
		||||
					width $loginWidth
 | 
			
		||||
					padding 16px 32px 32px 32px
 | 
			
		||||
					background isDark ? #2e3440 : #f5f5f5
 | 
			
		||||
					&:hover
 | 
			
		||||
						color $theme-color
 | 
			
		||||
 | 
			
		||||
			> .hashtags
 | 
			
		||||
				margin 16px auto
 | 
			
		||||
				width $width
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				color #fff
 | 
			
		||||
				background rgba(#000, 0.3)
 | 
			
		||||
				border-radius 8px
 | 
			
		||||
 | 
			
		||||
@@ -240,22 +233,47 @@ root(isDark)
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin 14px
 | 
			
		||||
 | 
			
		||||
			> .nav
 | 
			
		||||
				display block
 | 
			
		||||
				margin 16px 0
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				color #fff
 | 
			
		||||
		> .broadcasts
 | 
			
		||||
			grid-row 2
 | 
			
		||||
			grid-column 1
 | 
			
		||||
			padding 32px
 | 
			
		||||
 | 
			
		||||
	> .tl
 | 
			
		||||
		margin 0
 | 
			
		||||
		width 410px
 | 
			
		||||
		height 100vh
 | 
			
		||||
		text-align left
 | 
			
		||||
		background isDark ? #313543 : #fff
 | 
			
		||||
			> div
 | 
			
		||||
				padding 0 0 16px 0
 | 
			
		||||
				margin 0 0 16px 0
 | 
			
		||||
				border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
 | 
			
		||||
 | 
			
		||||
		> *
 | 
			
		||||
			max-height 100%
 | 
			
		||||
			overflow auto
 | 
			
		||||
				> h1
 | 
			
		||||
					margin 0
 | 
			
		||||
					font-size 1.5em
 | 
			
		||||
 | 
			
		||||
		> .nav
 | 
			
		||||
			display flex
 | 
			
		||||
			justify-content center
 | 
			
		||||
			align-items center
 | 
			
		||||
			grid-row 3
 | 
			
		||||
			grid-column 1
 | 
			
		||||
			font-size 14px
 | 
			
		||||
 | 
			
		||||
		> .side
 | 
			
		||||
			display grid
 | 
			
		||||
			grid-row 1 / 4
 | 
			
		||||
			grid-column 2
 | 
			
		||||
			grid-template-rows 1fr 350px
 | 
			
		||||
			grid-template-columns 1fr
 | 
			
		||||
			gap 16px
 | 
			
		||||
 | 
			
		||||
			> .tl
 | 
			
		||||
				grid-row 1
 | 
			
		||||
				grid-column 1
 | 
			
		||||
				text-align left
 | 
			
		||||
				max-height 100%
 | 
			
		||||
				overflow auto
 | 
			
		||||
 | 
			
		||||
			> .trends
 | 
			
		||||
				grid-row 2
 | 
			
		||||
				grid-column 1
 | 
			
		||||
				padding 8px
 | 
			
		||||
 | 
			
		||||
.mk-welcome[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
@@ -264,29 +282,3 @@ root(isDark)
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.signupForm
 | 
			
		||||
	padding 24px 48px 48px 48px
 | 
			
		||||
 | 
			
		||||
.signupFormHeader
 | 
			
		||||
	padding 48px 0 12px 0
 | 
			
		||||
	margin: 0 48px
 | 
			
		||||
	font-size 1.5em
 | 
			
		||||
	color #777
 | 
			
		||||
	border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
.signinForm
 | 
			
		||||
	padding 24px 48px 48px 48px
 | 
			
		||||
 | 
			
		||||
.signinFormHeader
 | 
			
		||||
	padding 48px 0 12px 0
 | 
			
		||||
	margin: 0 48px
 | 
			
		||||
	font-size 1.5em
 | 
			
		||||
	color #777
 | 
			
		||||
	border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
.nav
 | 
			
		||||
	a
 | 
			
		||||
		color #666
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
import initStore from './store';
 | 
			
		||||
import { apiUrl, swPublickey, version, lang, googleMapsApiKey } from './config';
 | 
			
		||||
import { apiUrl, version, lang } from './config';
 | 
			
		||||
import Progress from './common/scripts/loading';
 | 
			
		||||
import Connection from './common/scripts/streaming/stream';
 | 
			
		||||
import { HomeStreamManager } from './common/scripts/streaming/home';
 | 
			
		||||
@@ -230,13 +230,13 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
		//#region Init stream managers
 | 
			
		||||
		this.streams.serverStatsStream = new ServerStatsStreamManager(this);
 | 
			
		||||
		this.streams.notesStatsStream = new NotesStatsStreamManager(this);
 | 
			
		||||
		this.streams.localTimelineStream = new LocalTimelineStreamManager(this, this.store.state.i);
 | 
			
		||||
 | 
			
		||||
		this.once('signedin', () => {
 | 
			
		||||
			// Init home stream manager
 | 
			
		||||
			this.stream = new HomeStreamManager(this, this.store.state.i);
 | 
			
		||||
 | 
			
		||||
			// Init other stream manager
 | 
			
		||||
			this.streams.localTimelineStream = new LocalTimelineStreamManager(this, this.store.state.i);
 | 
			
		||||
			this.streams.hybridTimelineStream = new HybridTimelineStreamManager(this, this.store.state.i);
 | 
			
		||||
			this.streams.globalTimelineStream = new GlobalTimelineStreamManager(this, this.store.state.i);
 | 
			
		||||
			this.streams.driveStream = new DriveStreamManager(this, this.store.state.i);
 | 
			
		||||
@@ -361,7 +361,7 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
				// A public key your push server will use to send
 | 
			
		||||
				// messages to client apps via a push server.
 | 
			
		||||
				applicationServerKey: urlBase64ToUint8Array(swPublickey)
 | 
			
		||||
				applicationServerKey: urlBase64ToUint8Array(this.meta.data.swPublickey)
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			// Subscribe push notification
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-drive-file-chooser">
 | 
			
		||||
<div class="cdxzvcfawjxdyxsekbxbfgtplebnoneb">
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<header>
 | 
			
		||||
			<h1>%i18n:@select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
 | 
			
		||||
			<button class="close" @click="cancel">%fa:times%</button>
 | 
			
		||||
			<button v-if="multiple" class="ok" @click="ok">%fa:check%</button>
 | 
			
		||||
		</header>
 | 
			
		||||
		<mk-drive ref="browser"
 | 
			
		||||
		<mk-drive class="drive" ref="browser"
 | 
			
		||||
			:select-file="true"
 | 
			
		||||
			:multiple="multiple"
 | 
			
		||||
			@change-selection="onChangeSelection"
 | 
			
		||||
@@ -46,7 +46,7 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-drive-file-chooser
 | 
			
		||||
root(isDark)
 | 
			
		||||
	position fixed
 | 
			
		||||
	z-index 20000
 | 
			
		||||
	top 0
 | 
			
		||||
@@ -59,10 +59,11 @@ export default Vue.extend({
 | 
			
		||||
	> .body
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100%
 | 
			
		||||
		background #fff
 | 
			
		||||
		background isDark ? #282c37 : #fff
 | 
			
		||||
 | 
			
		||||
		> header
 | 
			
		||||
			border-bottom solid 1px #eee
 | 
			
		||||
			border-bottom solid 1px isDark ? #1b1f29 : #eee
 | 
			
		||||
			color isDark ? #fff : #111
 | 
			
		||||
 | 
			
		||||
			> h1
 | 
			
		||||
				margin 0
 | 
			
		||||
@@ -90,9 +91,15 @@ export default Vue.extend({
 | 
			
		||||
				line-height 42px
 | 
			
		||||
				width 42px
 | 
			
		||||
 | 
			
		||||
		> .mk-drive
 | 
			
		||||
		> .drive
 | 
			
		||||
			height calc(100% - 42px)
 | 
			
		||||
			overflow scroll
 | 
			
		||||
			-webkit-overflow-scrolling touch
 | 
			
		||||
 | 
			
		||||
.cdxzvcfawjxdyxsekbxbfgtplebnoneb[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.cdxzvcfawjxdyxsekbxbfgtplebnoneb:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -471,10 +471,6 @@ root(isDark)
 | 
			
		||||
					&.reacted
 | 
			
		||||
						color $theme-color
 | 
			
		||||
 | 
			
		||||
					&.menu
 | 
			
		||||
						@media (max-width 350px)
 | 
			
		||||
							display none
 | 
			
		||||
 | 
			
		||||
.note[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-notify">
 | 
			
		||||
	<mk-notification-preview :notification="notification"/>
 | 
			
		||||
	<div>
 | 
			
		||||
		<mk-notification-preview :notification="notification"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +24,7 @@ export default Vue.extend({
 | 
			
		||||
			setTimeout(() => {
 | 
			
		||||
				anime({
 | 
			
		||||
					targets: this.$el,
 | 
			
		||||
					bottom: '-64px',
 | 
			
		||||
					bottom: '-72px',
 | 
			
		||||
					duration: 500,
 | 
			
		||||
					easing: 'easeOutQuad',
 | 
			
		||||
					complete: () => this.$destroy()
 | 
			
		||||
@@ -37,13 +39,22 @@ export default Vue.extend({
 | 
			
		||||
.mk-notify
 | 
			
		||||
	position fixed
 | 
			
		||||
	z-index 1024
 | 
			
		||||
	bottom -64px
 | 
			
		||||
	bottom -72px
 | 
			
		||||
	left 0
 | 
			
		||||
	right 0
 | 
			
		||||
	width 100%
 | 
			
		||||
	height 64px
 | 
			
		||||
	max-width 500px
 | 
			
		||||
	height 72px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	padding 8px
 | 
			
		||||
	pointer-events none
 | 
			
		||||
	-webkit-backdrop-filter blur(2px)
 | 
			
		||||
	backdrop-filter blur(2px)
 | 
			
		||||
	background-color rgba(#000, 0.5)
 | 
			
		||||
	font-size 80%
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		height 100%
 | 
			
		||||
		-webkit-backdrop-filter blur(2px)
 | 
			
		||||
		backdrop-filter blur(2px)
 | 
			
		||||
		background-color rgba(#000, 0.5)
 | 
			
		||||
		border-radius 6px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,17 @@ export const meta = {
 | 
			
		||||
	requireAdmin: true,
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		broadcasts: $.arr($.obj()).optional.nullable.note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': 'ブロードキャスト'
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		disableRegistration: $.bool.optional.nullable.note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '招待制か否か'
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -25,6 +31,10 @@ export default (params: any) => new Promise(async (res, rej) => {
 | 
			
		||||
 | 
			
		||||
	const set = {} as any;
 | 
			
		||||
 | 
			
		||||
	if (ps.broadcasts) {
 | 
			
		||||
		set.broadcasts = ps.broadcasts;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (typeof ps.disableRegistration === 'boolean') {
 | 
			
		||||
		set.disableRegistration = ps.disableRegistration;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 | 
			
		||||
		createdAt: new Date(),
 | 
			
		||||
		fileId: file ? file._id : undefined,
 | 
			
		||||
		recipientId: recipient._id,
 | 
			
		||||
		text: text ? text : undefined,
 | 
			
		||||
		text: text ? text.trim() : undefined,
 | 
			
		||||
		userId: user._id,
 | 
			
		||||
		isRead: false
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ export default () => new Promise(async (res, rej) => {
 | 
			
		||||
		},
 | 
			
		||||
		broadcasts: meta.broadcasts,
 | 
			
		||||
		disableRegistration: meta.disableRegistration,
 | 
			
		||||
		driveCapacityPerLocalUserMb: config.localDriveCapacityMb,
 | 
			
		||||
		recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
 | 
			
		||||
		swPublickey: config.sw ? config.sw.public_key : null
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import ID from '../../../../../misc/cafy-id';
 | 
			
		||||
import UserList, { deleteUserList } from '../../../../../models/user-list';
 | 
			
		||||
import { ILocalUser } from '../../../../../models/user';
 | 
			
		||||
import getParams from '../../../get-params';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	desc: {
 | 
			
		||||
		'ja-JP': '指定したユーザーリストを削除します。',
 | 
			
		||||
		'en-US': 'Delete a user list'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	requireCredential: true,
 | 
			
		||||
 | 
			
		||||
	kind: 'account-write',
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		listId: $.type(ID).note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '対象となるユーザーリストのID',
 | 
			
		||||
				'en-US': 'ID of target user list'
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 | 
			
		||||
	const [ps, psErr] = getParams(meta, params);
 | 
			
		||||
	if (psErr) return rej(psErr);
 | 
			
		||||
 | 
			
		||||
	const userList = await UserList.findOne({
 | 
			
		||||
		_id: ps.listId,
 | 
			
		||||
		userId: user._id
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (userList == null) {
 | 
			
		||||
		return rej('list not found');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deleteUserList(userList);
 | 
			
		||||
 | 
			
		||||
	res();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										56
									
								
								src/server/api/endpoints/users/lists/update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/server/api/endpoints/users/lists/update.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import ID from '../../../../../misc/cafy-id';
 | 
			
		||||
import UserList, { pack } from '../../../../../models/user-list';
 | 
			
		||||
import { ILocalUser } from '../../../../../models/user';
 | 
			
		||||
import getParams from '../../../get-params';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	desc: {
 | 
			
		||||
		'ja-JP': '指定したユーザーリストを更新します。',
 | 
			
		||||
		'en-US': 'Update a user list'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	requireCredential: true,
 | 
			
		||||
 | 
			
		||||
	kind: 'account-write',
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		listId: $.type(ID).note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '対象となるユーザーリストのID',
 | 
			
		||||
				'en-US': 'ID of target user list'
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		title: $.str.range(1, 100).note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': 'このユーザーリストの名前',
 | 
			
		||||
				'en-US': 'name of this user list'
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 | 
			
		||||
	const [ps, psErr] = getParams(meta, params);
 | 
			
		||||
	if (psErr) throw psErr;
 | 
			
		||||
 | 
			
		||||
	// Fetch the list
 | 
			
		||||
	const userList = await UserList.findOne({
 | 
			
		||||
		_id: ps.listId,
 | 
			
		||||
		userId: user._id
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (userList == null) {
 | 
			
		||||
		return rej('list not found');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update
 | 
			
		||||
	await UserList.update({ _id: userList._id }, {
 | 
			
		||||
		$set: {
 | 
			
		||||
			title: ps.title
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Response
 | 
			
		||||
	res(await pack(userList._id));
 | 
			
		||||
});
 | 
			
		||||
@@ -9,10 +9,10 @@ export default async function(
 | 
			
		||||
	request: websocket.request,
 | 
			
		||||
	connection: websocket.connection,
 | 
			
		||||
	subscriber: Xev,
 | 
			
		||||
	user: IUser
 | 
			
		||||
	user?: IUser
 | 
			
		||||
) {
 | 
			
		||||
	const mute = await Mute.find({ muterId: user._id });
 | 
			
		||||
	const mutedUserIds = mute.map(m => m.muteeId.toString());
 | 
			
		||||
	const mute = user ? await Mute.find({ muterId: user._id }) : null;
 | 
			
		||||
	const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : [];
 | 
			
		||||
 | 
			
		||||
	// Subscribe stream
 | 
			
		||||
	subscriber.on('local-timeline', async note => {
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,11 @@ module.exports = (server: http.Server) => {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (request.resourceURL.pathname === '/local-timeline') {
 | 
			
		||||
			localTimelineStream(request, connection, ev, user);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (user == null) {
 | 
			
		||||
			connection.send('authentication-failed');
 | 
			
		||||
			connection.close();
 | 
			
		||||
@@ -60,7 +65,6 @@ module.exports = (server: http.Server) => {
 | 
			
		||||
 | 
			
		||||
		const channel: any =
 | 
			
		||||
			request.resourceURL.pathname === '/' ? homeStream :
 | 
			
		||||
			request.resourceURL.pathname === '/local-timeline' ? localTimelineStream :
 | 
			
		||||
			request.resourceURL.pathname === '/hybrid-timeline' ? hybridTimelineStream :
 | 
			
		||||
			request.resourceURL.pathname === '/global-timeline' ? globalTimelineStream :
 | 
			
		||||
			request.resourceURL.pathname === '/user-list' ? userListStream :
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ router.get('/apple-touch-icon.png', async ctx => {
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// ServiceWroker
 | 
			
		||||
// ServiceWorker
 | 
			
		||||
router.get(/^\/sw\.(.+?)\.js$/, async ctx => {
 | 
			
		||||
	await send(ctx, `/assets/sw.${ctx.params[0]}.js`, {
 | 
			
		||||
		root: client
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user