merge upstream
This commit is contained in:
		
						commit
						1595683904
					
				
							
								
								
									
										10
									
								
								README.md
								
								
								
								
							
							
						
						
									
										10
									
								
								README.md
								
								
								
								
							|  | @ -42,17 +42,15 @@ Please see [Contribution guide](./CONTRIBUTING.md). | |||
| ---------------------------------------------------------------- | ||||
| <!-- PATREON_START --> | ||||
| <table><tr> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td> | ||||
| <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> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12731202">negao</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||
|  | @ -67,20 +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> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></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> | ||||
| <td><a href="https://www.patreon.com/fujishan">fujishan</a></td> | ||||
| </tr></table> | ||||
| 
 | ||||
| **Last updated:** Sun, 26 Aug 2018 08:55:06 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.20.0", | ||||
| 	"clientVersion": "1.0.9240", | ||||
| 	"version": "8.25.0", | ||||
| 	"clientVersion": "1.0.9297", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
|  | @ -221,7 +221,7 @@ | |||
| 		"vuex-persistedstate": "2.5.4", | ||||
| 		"web-push": "3.3.2", | ||||
| 		"webfinger.js": "2.6.6", | ||||
| 		"webpack": "4.17.1", | ||||
| 		"webpack": "4.17.2", | ||||
| 		"webpack-cli": "3.1.0", | ||||
| 		"websocket": "1.0.26", | ||||
| 		"ws": "6.0.0", | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -83,7 +83,7 @@ export default Vue.extend({ | |||
| 						userId: this.user.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.user.isLocked && this.user.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.user.hasPendingFollowRequestFromYou) { | ||||
| 						this.user = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -55,13 +55,15 @@ export default Vue.extend({ | |||
| 	methods: { | ||||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.user.isFollowing = user.isFollowing; | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onUnfollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.user.isFollowing = user.isFollowing; | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
|  | @ -74,7 +76,7 @@ export default Vue.extend({ | |||
| 						userId: this.u.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.u.hasPendingFollowRequestFromYou) { | ||||
| 						this.u = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ export default Vue.extend({ | |||
| 			const mouseY = e.clientY - rect.top; | ||||
| 			const xp = mouseX / this.$el.offsetWidth * 100; | ||||
| 			const yp = mouseY / this.$el.offsetHeight * 100; | ||||
| 			this.$el.style.backgroundPosition = `${xp}% ${yp}%'; | ||||
| 			this.$el.style.backgroundPosition = `${xp}% ${yp}%`; | ||||
| 			this.$el.style.backgroundImage = `url("${this.image.url}")`; | ||||
| 		}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -48,12 +48,14 @@ export default Vue.extend({ | |||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onUnfollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
|  | @ -66,7 +68,7 @@ export default Vue.extend({ | |||
| 						userId: this.u.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.u.hasPendingFollowRequestFromYou) { | ||||
| 						this.u = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
|  |  | |||
|  | @ -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: `-${this.$el.offsetHeight}px`, | ||||
| 					duration: 500, | ||||
| 					easing: 'easeOutQuad', | ||||
| 					complete: () => this.$destroy() | ||||
|  | @ -35,15 +37,27 @@ export default Vue.extend({ | |||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-notify | ||||
| 	$height = 78px | ||||
| 
 | ||||
| 	position fixed | ||||
| 	z-index 1024 | ||||
| 	bottom -64px | ||||
| 	bottom -($height) | ||||
| 	left 0 | ||||
| 	right 0 | ||||
| 	width 100% | ||||
| 	height 64px | ||||
| 	max-width 500px | ||||
| 	height $height | ||||
| 	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 7px | ||||
| 		overflow hidden | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { URL } from 'url'; | |||
| import * as yaml from 'js-yaml'; | ||||
| import { Source, Mixin } from './types'; | ||||
| import isUrl = require('is-url'); | ||||
| const pkg = require('../../package.json'); | ||||
| 
 | ||||
| /** | ||||
|  * Path of configuration directory | ||||
|  | @ -43,6 +44,7 @@ export default function load() { | |||
| 	mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`; | ||||
| 	mixin.status_url = `${mixin.scheme}://${mixin.host}/status`; | ||||
| 	mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`; | ||||
| 	mixin.user_agent = `Misskey/${pkg.version} (${config.url})`; | ||||
| 
 | ||||
| 	if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256; | ||||
| 	if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8; | ||||
|  |  | |||
|  | @ -114,6 +114,7 @@ export type Mixin = { | |||
| 	status_url: string; | ||||
| 	dev_url: string; | ||||
| 	drive_url: string; | ||||
| 	user_agent: string; | ||||
| }; | ||||
| 
 | ||||
| export type Config = Source & Mixin; | ||||
|  |  | |||
|  | @ -20,7 +20,6 @@ import Logger from './misc/logger'; | |||
| import ProgressBar from './misc/cli/progressbar'; | ||||
| import EnvironmentInfo from './misc/environmentInfo'; | ||||
| import MachineInfo from './misc/machineInfo'; | ||||
| import DependencyInfo from './misc/dependencyInfo'; | ||||
| import serverStats from './daemons/server-stats'; | ||||
| import notesStats from './daemons/notes-stats'; | ||||
| import loadConfig from './config/load'; | ||||
|  | @ -116,7 +115,6 @@ async function init(): Promise<Config> { | |||
| 	new Logger('Deps').info(`Node.js ${process.version}`); | ||||
| 	MachineInfo.show(); | ||||
| 	EnvironmentInfo.show(); | ||||
| 	new DependencyInfo().showAll(); | ||||
| 
 | ||||
| 	const configLogger = new Logger('Config'); | ||||
| 	let config; | ||||
|  |  | |||
|  | @ -47,11 +47,9 @@ export default function(html: string): string { | |||
| 						//#region ホスト名部分が省略されているので復元する
 | ||||
| 						const acct = `${txt}@${(new URL(href.value)).hostname}`; | ||||
| 						text += acct; | ||||
| 						break; | ||||
| 						//#endregion
 | ||||
| 					} else if (part.length == 3) { | ||||
| 						text += txt; | ||||
| 						break; | ||||
| 					} | ||||
| 				// その他
 | ||||
| 				} else { | ||||
|  |  | |||
|  | @ -1,32 +0,0 @@ | |||
| import Logger from './logger'; | ||||
| import { execSync } from 'child_process'; | ||||
| 
 | ||||
| export default class { | ||||
| 	private logger: Logger; | ||||
| 
 | ||||
| 	constructor() { | ||||
| 		this.logger = new Logger('Deps'); | ||||
| 	} | ||||
| 
 | ||||
| 	public showAll(): void { | ||||
| 		this.show('MongoDB', 'mongo --version', x => x.match(/^MongoDB shell version:? v(.*)\r?\n/)); | ||||
| 		this.show('Redis', 'redis-server --version', x => x.match(/v=([0-9\.]*)/)); | ||||
| 	} | ||||
| 
 | ||||
| 	public show(serviceName: string, command: string, transform: (x: string) => RegExpMatchArray): void { | ||||
| 		try { | ||||
| 			// ステータス0以外のときにexecSyncはstderrをコンソール上に出力してしまうので
 | ||||
| 			// プロセスからのstderrをすべて無視するように stdio オプションをセット
 | ||||
| 			const x = execSync(command, { stdio: ['pipe', 'pipe', 'ignore'] }); | ||||
| 			const ver = transform(x.toString()); | ||||
| 			if (ver != null) { | ||||
| 				this.logger.succ(`${serviceName} ${ver[1]} found`); | ||||
| 			} else { | ||||
| 				this.logger.warn(`${serviceName} not found`); | ||||
| 				this.logger.warn(`Regexp used for version check of ${serviceName} is probably messed up`); | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			this.logger.warn(`${serviceName} not found`); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -193,5 +193,10 @@ export const pack = ( | |||
| 		*/ | ||||
| 	} | ||||
| 
 | ||||
| 	delete _target.withoutChunks; | ||||
| 	delete _target.storage; | ||||
| 	delete _target.storageProps; | ||||
| 	delete _target.isRemote; | ||||
| 
 | ||||
| 	resolve(_target); | ||||
| }); | ||||
|  |  | |||
|  | @ -432,10 +432,10 @@ export const pack = ( | |||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
| 			}), | ||||
| 			_user.isLocked ? FollowRequest.findOne({ | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: meId, | ||||
| 				followeeId: _user.id | ||||
| 			}) : Promise.resolve(null), | ||||
| 			}), | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
|  |  | |||
|  | @ -0,0 +1,4 @@ | |||
| export default (id: string) => ({ | ||||
| 	id, | ||||
| 	type: 'Tombstone' | ||||
| }); | ||||
|  | @ -27,6 +27,7 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso | |||
| 		method: 'POST', | ||||
| 		path: pathname + search, | ||||
| 		headers: { | ||||
| 			'User-Agent': config.user_agent, | ||||
| 			'Content-Type': 'application/activity+json', | ||||
| 			'Digest': `SHA-256=${hash}` | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import * as request from 'request-promise-native'; | ||||
| import * as debug from 'debug'; | ||||
| import { IObject } from './type'; | ||||
| //import config from '../../config';
 | ||||
| import config from '../../config'; | ||||
| 
 | ||||
| const log = debug('misskey:activitypub:resolver'); | ||||
| 
 | ||||
|  | @ -51,6 +51,7 @@ export default class Resolver { | |||
| 		const object = await request({ | ||||
| 			url: value, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent, | ||||
| 				Accept: 'application/activity+json, application/ld+json' | ||||
| 			}, | ||||
| 			json: true | ||||
|  |  | |||
|  | @ -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,7 +31,11 @@ export default (params: any) => new Promise(async (res, rej) => { | |||
| 
 | ||||
| 	const set = {} as any; | ||||
| 
 | ||||
| 	if (ps.disableRegistration === true || ps.disableRegistration === false) { | ||||
| 	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 | ||||
| 	}); | ||||
|  |  | |||
|  | @ -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(); | ||||
| }); | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -34,7 +34,12 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul | |||
| 	// write content at URL to temp file
 | ||||
| 	await new Promise((res, rej) => { | ||||
| 		const writable = fs.createWriteStream(path); | ||||
| 		request(url) | ||||
| 		request({ | ||||
| 			url, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent | ||||
| 			} | ||||
| 		}) | ||||
| 			.on('error', rej) | ||||
| 			.on('end', () => { | ||||
| 				writable.close(); | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { deliver } from '../../queue'; | |||
| import createFollowRequest from './requests/create'; | ||||
| 
 | ||||
| export default async function(follower: IUser, followee: IUser) { | ||||
| 	if (followee.isLocked) { | ||||
| 	if (followee.isLocked || isLocalUser(follower) && isRemoteUser(followee)) { | ||||
| 		await createFollowRequest(follower, followee); | ||||
| 	} else { | ||||
| 		const following = await Following.insert({ | ||||
|  | @ -72,11 +72,6 @@ export default async function(follower: IUser, followee: IUser) { | |||
| 			notify(followee._id, follower._id, 'follow'); | ||||
| 		} | ||||
| 
 | ||||
| 		if (isLocalUser(follower) && isRemoteUser(followee)) { | ||||
| 			const content = pack(renderFollow(follower, followee)); | ||||
| 			deliver(follower, content, followee.inbox); | ||||
| 		} | ||||
| 
 | ||||
| 		if (isRemoteUser(follower) && isLocalUser(followee)) { | ||||
| 			const content = pack(renderAccept(renderFollow(follower, followee))); | ||||
| 			deliver(followee, content, follower.inbox); | ||||
|  |  | |||
|  | @ -75,4 +75,6 @@ export default async function(followee: IUser, follower: IUser) { | |||
| 	packUser(followee, followee, { | ||||
| 		detail: true | ||||
| 	}).then(packed => publishUserStream(followee._id, 'meUpdated', packed)); | ||||
| 
 | ||||
| 	packUser(followee, follower).then(packed => publishUserStream(follower._id, 'follow', packed)); | ||||
| } | ||||
|  |  | |||
|  | @ -7,8 +7,6 @@ import { deliver } from '../../../queue'; | |||
| import FollowRequest from '../../../models/follow-request'; | ||||
| 
 | ||||
| export default async function(follower: IUser, followee: IUser) { | ||||
| 	if (!followee.isLocked) throw '対象のアカウントは鍵アカウントではありません'; | ||||
| 
 | ||||
| 	await FollowRequest.insert({ | ||||
| 		createdAt: new Date(), | ||||
| 		followerId: follower._id, | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| import User, { IUser, isRemoteUser, ILocalUser } from '../../../models/user'; | ||||
| import User, { IUser, isRemoteUser, ILocalUser, pack as packUser } from '../../../models/user'; | ||||
| import FollowRequest from '../../../models/follow-request'; | ||||
| import pack from '../../../remote/activitypub/renderer'; | ||||
| import renderFollow from '../../../remote/activitypub/renderer/follow'; | ||||
| import renderReject from '../../../remote/activitypub/renderer/reject'; | ||||
| import { deliver } from '../../../queue'; | ||||
| import { publishUserStream } from '../../../stream'; | ||||
| 
 | ||||
| export default async function(followee: IUser, follower: IUser) { | ||||
| 	if (isRemoteUser(follower)) { | ||||
|  | @ -21,4 +22,6 @@ export default async function(followee: IUser, follower: IUser) { | |||
| 			pendingReceivedFollowRequestsCount: -1 | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	packUser(followee, follower).then(packed => publishUserStream(follower._id, 'unfollow', packed)); | ||||
| } | ||||
|  |  | |||
|  | @ -5,8 +5,9 @@ import renderDelete from '../../remote/activitypub/renderer/delete'; | |||
| import pack from '../../remote/activitypub/renderer'; | ||||
| import { deliver } from '../../queue'; | ||||
| import Following from '../../models/following'; | ||||
| import renderNote from '../../remote/activitypub/renderer/note'; | ||||
| import renderTombstone from '../../remote/activitypub/renderer/tombstone'; | ||||
| import { updateNoteStats } from '../update-chart'; | ||||
| import config from '../../config'; | ||||
| 
 | ||||
| /** | ||||
|  * 投稿を削除します。 | ||||
|  | @ -32,7 +33,7 @@ export default async function(user: IUser, note: INote) { | |||
| 
 | ||||
| 	//#region ローカルの投稿なら削除アクティビティを配送
 | ||||
| 	if (isLocalUser(user)) { | ||||
| 		const content = pack(renderDelete(await renderNote(note), user)); | ||||
| 		const content = pack(renderDelete(renderTombstone(`${config.url}/notes/${note._id}`), user)); | ||||
| 
 | ||||
| 		const followings = await Following.find({ | ||||
| 			followeeId: user._id, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue