Merge branch 'master' into l10n_master
| 
						 | 
					@ -1,67 +1,136 @@
 | 
				
			||||||
# インスタンス名
 | 
					name: example-instance-name # Name of your instance
 | 
				
			||||||
name:
 | 
					description: example-description # Description of your instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# インスタンスの紹介
 | 
					 | 
				
			||||||
description:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# サーバーのメンテナ情報
 | 
					 | 
				
			||||||
maintainer:
 | 
					maintainer:
 | 
				
			||||||
  # メンテナの名前
 | 
					  name: example-maitainer-name # Your name
 | 
				
			||||||
  name:
 | 
					  url: http://example.com/ # Your contact (http or mailto)
 | 
				
			||||||
 | 
					  repository_url: https://github.com/syuilo/misskey # Repository URL
 | 
				
			||||||
 | 
					  feedback_url: https://github.com/syuilo/misskey/issues # Feedback URL (e.g. github issue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # メンテナの連絡先(URLかmailto形式のURL)
 | 
					# URL and Port settings overview
 | 
				
			||||||
  url:
 | 
					# e.g., If you want to realize following structure:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#               +--- https://example.com:123 ----------+
 | 
				
			||||||
 | 
					# +------+      |+-------------+      +---------------+|
 | 
				
			||||||
 | 
					# | User | ---> || Proxy (123) | ---> | Misskey (456) ||
 | 
				
			||||||
 | 
					# +------+      |+-------------+      +---------------+|
 | 
				
			||||||
 | 
					#               +--------------------------------------+
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You need to set 'https://example.com:123' to 'url' prop and
 | 
				
			||||||
 | 
					# You need to set 456 to 'port' prop.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# In other words, the 'url' prop should be the final accessible URL seen by a user.
 | 
				
			||||||
 | 
					# 'port' prop is a port that the Misskey server should actually listen
 | 
				
			||||||
 | 
					# on and it is not necessarily the port that a user accesses.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# (Misskeyを動かす)URL
 | 
					url: http://localhost/
 | 
				
			||||||
url:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 待受ポート
 | 
					# A port that your Misskey server should listen.
 | 
				
			||||||
port:
 | 
					# This value is not a port to use when accessing with a browser.
 | 
				
			||||||
 | 
					port: 80
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TLSの設定(利用しない場合は省略してください)
 | 
					 | 
				
			||||||
https:
 | 
					 | 
				
			||||||
  # 証明書のパス...
 | 
					 | 
				
			||||||
  key:
 | 
					 | 
				
			||||||
  cert:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# MongoDBの設定
 | 
					 | 
				
			||||||
mongodb:
 | 
					mongodb:
 | 
				
			||||||
  host: localhost
 | 
					  host: localhost
 | 
				
			||||||
  port: 27017
 | 
					  port: 27017
 | 
				
			||||||
  db: misskey
 | 
					  db: misskey
 | 
				
			||||||
  user:
 | 
					  user: example-misskey-user
 | 
				
			||||||
  pass:
 | 
					  pass: example-misskey-pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Redisの設定
 | 
					 | 
				
			||||||
redis:
 | 
					redis:
 | 
				
			||||||
  host: localhost
 | 
					  host: localhost
 | 
				
			||||||
  port: 6379
 | 
					  port: 6379
 | 
				
			||||||
  pass:
 | 
					  pass: example-pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# reCAPTCHAの設定
 | 
					# Drive capacity of a local user (MB)
 | 
				
			||||||
recaptcha:
 | 
					localDriveCapacityMb: 256
 | 
				
			||||||
  site_key:
 | 
					 | 
				
			||||||
  secret_key:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ServiceWrokerの設定
 | 
					# Drive capacity of a remote user (MB)
 | 
				
			||||||
sw:
 | 
					remoteDriveCapacityMb: 8
 | 
				
			||||||
  # VAPIDの公開鍵
 | 
					 | 
				
			||||||
  public_key:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # VAPIDの秘密鍵
 | 
					# If enabled:
 | 
				
			||||||
  private_key:
 | 
					#  Server will not cache remote files (Using direct link instead).
 | 
				
			||||||
 | 
					#  You can save your storage.
 | 
				
			||||||
# Google Maps API
 | 
					#  Users cannot see remote images when they turn off "Show media from a remote server" setting.
 | 
				
			||||||
google_maps_api_key:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Twitterインテグレーションの設定(利用しない場合は省略可能)
 | 
					 | 
				
			||||||
twitter:
 | 
					 | 
				
			||||||
  # インテグレーション用アプリのコンシューマーキー
 | 
					 | 
				
			||||||
  consumer_key:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # インテグレーション用アプリのコンシューマーシークレット
 | 
					 | 
				
			||||||
  consumer_secret:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# true にすると、リモートのファイルをキャッシュしなくなります(直リンクします)。
 | 
					 | 
				
			||||||
# ストレージ容量を節約することができますが、「リモートメディアを表示しない」設定をオンにしているユーザーは、リモートの画像などは見えなくなります。
 | 
					 | 
				
			||||||
preventCache: false
 | 
					preventCache: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					drive:
 | 
				
			||||||
 | 
					  storage: 'db'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # OR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # storage: 'minio'
 | 
				
			||||||
 | 
					  # bucket:
 | 
				
			||||||
 | 
					  # prefix:
 | 
				
			||||||
 | 
					  # config:
 | 
				
			||||||
 | 
					  #   endPoint:
 | 
				
			||||||
 | 
					  #   port:
 | 
				
			||||||
 | 
					  #   secure:
 | 
				
			||||||
 | 
					  #   accessKey:
 | 
				
			||||||
 | 
					  #   secretKey:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # S3 example
 | 
				
			||||||
 | 
					  # storage: 'minio'
 | 
				
			||||||
 | 
					  # bucket: bucket-name
 | 
				
			||||||
 | 
					  # prefix: files
 | 
				
			||||||
 | 
					  # config:
 | 
				
			||||||
 | 
					  #   endPoint: s3-us-west-2.amazonaws.com
 | 
				
			||||||
 | 
					  #   region: us-west-2
 | 
				
			||||||
 | 
					  #   secure: true
 | 
				
			||||||
 | 
					  #   accessKey: XXX
 | 
				
			||||||
 | 
					  #   secretKey: YYY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # S3 example (with CDN, custom domain)
 | 
				
			||||||
 | 
					  # storage: 'minio'
 | 
				
			||||||
 | 
					  # bucket: drive.example.com
 | 
				
			||||||
 | 
					  # prefix: files
 | 
				
			||||||
 | 
					  # baseUrl: https://drive.example.com
 | 
				
			||||||
 | 
					  # config:
 | 
				
			||||||
 | 
					  #   endPoint: s3-us-west-2.amazonaws.com
 | 
				
			||||||
 | 
					  #   region: us-west-2
 | 
				
			||||||
 | 
					  #   secure: true
 | 
				
			||||||
 | 
					  #   accessKey: XXX
 | 
				
			||||||
 | 
					  #   secretKey: YYY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Below settings are optional
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TLS
 | 
				
			||||||
 | 
					# https:
 | 
				
			||||||
 | 
					#   # path for certification
 | 
				
			||||||
 | 
					#   key: example-tls-key
 | 
				
			||||||
 | 
					#   cert: example-tls-cert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Elasticsearch
 | 
				
			||||||
 | 
					# elasticsearch:
 | 
				
			||||||
 | 
					#   host: localhost
 | 
				
			||||||
 | 
					#   port: 9200
 | 
				
			||||||
 | 
					#   pass: null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# reCAPTCHA
 | 
				
			||||||
 | 
					# recaptcha:
 | 
				
			||||||
 | 
					#   site_key: example-site-key
 | 
				
			||||||
 | 
					#  secret_key: example-secret-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ServiceWorker
 | 
				
			||||||
 | 
					# sw:
 | 
				
			||||||
 | 
					#   # Public key of VAPID
 | 
				
			||||||
 | 
					#   public_key: example-sw-public-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#   # Private key of VAPID
 | 
				
			||||||
 | 
					#   private_key: example-sw-private-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# google_maps_api_key: example-google-maps-api-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Twitter integration
 | 
				
			||||||
 | 
					# twitter:
 | 
				
			||||||
 | 
					#   consumer_key: example-twitter-consumer-key
 | 
				
			||||||
 | 
					#   consumer_secret: example-twitter-consumer-secret-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ghost
 | 
				
			||||||
 | 
					# Ghost account is an account used for the purpose of delegating
 | 
				
			||||||
 | 
					# followers when putting users in the list.
 | 
				
			||||||
 | 
					# ghost: user-id-of-your-ghost-account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Clustering
 | 
				
			||||||
 | 
					# clusterLimit: 1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								.gitattributes
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -2,3 +2,4 @@
 | 
				
			||||||
*.psd -diff -text
 | 
					*.psd -diff -text
 | 
				
			||||||
*.ai -diff -text
 | 
					*.ai -diff -text
 | 
				
			||||||
yarn.lock -diff -text
 | 
					yarn.lock -diff -text
 | 
				
			||||||
 | 
					package-lock.json -diff -text
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								.github/ISSUE_TEMPLATE
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
<!--
 | 
					 | 
				
			||||||
Misskeyへの貢献ありがとうございます。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
バグの報告や提案などで、可能であれば以下の情報を含めてください。
 | 
					 | 
				
			||||||
* お使いのブラウザ
 | 
					 | 
				
			||||||
* デスクトップ版Misskeyかモバイル版Misskeyか
 | 
					 | 
				
			||||||
-->
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Bug Report
 | 
				
			||||||
 | 
					about: Create a report to help us improve
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Summary
 | 
				
			||||||
 | 
					<!-- Tell us what the bug is -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Expected Behavior
 | 
				
			||||||
 | 
					<!--- Tell us what should happen -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Actual Behavior
 | 
				
			||||||
 | 
					<!--- Tell us what happens instead of the expected behavior -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Steps to Reproduce
 | 
				
			||||||
 | 
					1.
 | 
				
			||||||
 | 
					2.
 | 
				
			||||||
 | 
					3.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Environment
 | 
				
			||||||
 | 
					<!-- Tell us where on the platform it happens -->
 | 
				
			||||||
 | 
					<!-- e.g. desktop or mobile version, your browser, your OS -->
 | 
				
			||||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Feature Request
 | 
				
			||||||
 | 
					about: Suggest an idea for this project
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Summary
 | 
				
			||||||
 | 
					<!-- Tell us what the suggestion is -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Environment
 | 
				
			||||||
 | 
					<!-- Tell us where on the platform it related -->
 | 
				
			||||||
 | 
					<!-- e.g. desktop or mobile version, your browser, your OS -->
 | 
				
			||||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -10,5 +10,4 @@ npm-debug.log
 | 
				
			||||||
*.pem
 | 
					*.pem
 | 
				
			||||||
run.bat
 | 
					run.bat
 | 
				
			||||||
api-docs.json
 | 
					api-docs.json
 | 
				
			||||||
package-lock.json
 | 
					 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.npmrc
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,2 +1,2 @@
 | 
				
			||||||
package-lock = false
 | 
					 | 
				
			||||||
save-exact=true
 | 
					save-exact=true
 | 
				
			||||||
 | 
					package-lock = false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								.vscode/extensions.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"recommendations": [
 | 
				
			||||||
 | 
							"ducksoupdev.vue2",
 | 
				
			||||||
 | 
							"editorconfig.editorconfig",
 | 
				
			||||||
 | 
							"eg2.tslint",
 | 
				
			||||||
 | 
							"eg2.vscode-npm-script",
 | 
				
			||||||
 | 
							"hollowtree.vue-snippets",
 | 
				
			||||||
 | 
							"ms-vscode.typescript-javascript-grammar",
 | 
				
			||||||
 | 
							"octref.vetur",
 | 
				
			||||||
 | 
							"sysoev.language-stylus"
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,15 @@ ChangeLog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This document describes breaking changes only.
 | 
					This document describes breaking changes only.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5.0.0
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Migration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					起動する前に、`node cli/migration/5.0.0`してください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please run `node cli/migration/5.0.0` before launch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4.0.0
 | 
					4.0.0
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								README.md
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -7,7 +7,7 @@
 | 
				
			||||||
[![][dependencies-badge]][dependencies-link]
 | 
					[![][dependencies-badge]][dependencies-link]
 | 
				
			||||||
[](http://makeapullrequest.com) [](https://greenkeeper.io/)
 | 
					[](http://makeapullrequest.com) [](https://greenkeeper.io/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> Lead Maintainer: [syuilo][syuilo-link]
 | 
					**Microblogging. Redefined.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**[Misskey](https://misskey.xyz)** is a completely open source,
 | 
					**[Misskey](https://misskey.xyz)** is a completely open source,
 | 
				
			||||||
ultimately sophisticated professional microblogging software.
 | 
					ultimately sophisticated professional microblogging software.
 | 
				
			||||||
| 
						 | 
					@ -18,14 +18,13 @@ ultimately sophisticated professional microblogging software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:sparkles: Features
 | 
					:sparkles: Features
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					* Rich text contents
 | 
				
			||||||
* Reactions
 | 
					* Reactions
 | 
				
			||||||
* User lists
 | 
					* User lists
 | 
				
			||||||
* Customizable column view (known as MisskeyDeck)
 | 
					* Customizable column view (called MisskeyDeck)
 | 
				
			||||||
  * and widgets!
 | 
					  * and widgets!
 | 
				
			||||||
* Private messages
 | 
					* Private messages
 | 
				
			||||||
* Mute
 | 
					* ActivityPub support
 | 
				
			||||||
* Real-time timelines
 | 
					 | 
				
			||||||
* ActivityPub compatible
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
 | 
					and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,15 +43,15 @@ If you want to...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:heart: Backers & Sponsors
 | 
					:heart: Backers & Sponsors
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
| ![][nagarus-icon] | ![][dansup-icon] |
 | 
					| <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"> | <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"> |
 | 
				
			||||||
|:-:|:-:|
 | 
					|:-:|:-:|:-:|:-:|
 | 
				
			||||||
| [nagarus][nagarus-link] | [dansup][dansup-link] |
 | 
					| [Gargron](https://www.patreon.com/mastodon) | [39ff](https://www.patreon.com/user/creators?u=12378075) | [dansup](https://www.patreon.com/dansup) | [Takashi Shibuya](https://www.patreon.com/user/creators?u=12531784) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:four_leaf_clover: Copyright
 | 
					:four_leaf_clover: Copyright
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
> Copyright (c) 2014-2018 syuilo
 | 
					> Copyright (c) 2014-2018 syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
 | 
					Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[![][agpl-3.0-badge]][AGPL-3.0]
 | 
					[![][agpl-3.0-badge]][AGPL-3.0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,9 +72,3 @@ Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[syuilo-link]:      https://syuilo.com
 | 
					[syuilo-link]:      https://syuilo.com
 | 
				
			||||||
[syuilo-icon]:      https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
 | 
					[syuilo-icon]:      https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
 | 
				
			||||||
 | 
					 | 
				
			||||||
[nagarus-link]: https://www.patreon.com/user/creators?u=11601413
 | 
					 | 
				
			||||||
[nagarus-icon]: https://c10.patreonusercontent.com/3/eyJ2IjoiMSIsInciOjIwMH0%3D/patreon-media/user/11601413/20cb15f209924302b399b99d3c98b850?token-time=2145916800&token-hash=IO31nK6VZCMWBWU2VAk2c824BX2QZ4DNPKyHHZXS0iw%3D
 | 
					 | 
				
			||||||
[dansup-link]: https://www.patreon.com/dansup
 | 
					 | 
				
			||||||
[dansup-icon]: https://c10.patreonusercontent.com/3/eyJ2IjoiMSIsInciOjIwMH0%3D/patreon-media/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb?token-time=2145916800&token-hash=opXAM_pnhUTuN1jCA6p_Nn_YsaqohY465YFjWFqMEEE%3D
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								appveyor.yml
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,41 +0,0 @@
 | 
				
			||||||
# appveyor file
 | 
					 | 
				
			||||||
# http://www.appveyor.com/docs/appveyor-yml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
environment:
 | 
					 | 
				
			||||||
  matrix:
 | 
					 | 
				
			||||||
    - nodejs_version: 10.1.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cache:
 | 
					 | 
				
			||||||
  - node_modules
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build: off
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install:
 | 
					 | 
				
			||||||
  # Update Node.js
 | 
					 | 
				
			||||||
  # 標準で入っている Node.js を更新します (2014/11/13 時点では、v0.10.32 が標準)
 | 
					 | 
				
			||||||
  - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
 | 
					 | 
				
			||||||
  - node --version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Update NPM
 | 
					 | 
				
			||||||
  - npm install -g npm
 | 
					 | 
				
			||||||
  - npm --version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Update node-gyp
 | 
					 | 
				
			||||||
  # 必須! node-gyp のバージョンを上げないと、ネイティブモジュールのコンパイルに失敗します
 | 
					 | 
				
			||||||
  - npm install -g node-gyp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - npm install
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
init:
 | 
					 | 
				
			||||||
  # git clone の際の改行を変換しないようにします
 | 
					 | 
				
			||||||
  - git config --global core.autocrlf false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
before_test:
 | 
					 | 
				
			||||||
  # 設定ファイルを配置
 | 
					 | 
				
			||||||
  - cp ./.travis/default.yml ./.config
 | 
					 | 
				
			||||||
  - cp ./.travis/test.yml ./.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - npm run build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test_script:
 | 
					 | 
				
			||||||
  - npm test
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 3.9 KiB  | 
| 
		 Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.8 KiB  | 
| 
		 Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/title.png
									
										
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 3.8 KiB  | 
| 
						 | 
					@ -9,7 +9,7 @@ const q = {
 | 
				
			||||||
	'metadata._user.host': {
 | 
						'metadata._user.host': {
 | 
				
			||||||
		$ne: null
 | 
							$ne: null
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	'metadata.isMetaOnly': false
 | 
						'metadata.withoutChunks': false
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main() {
 | 
					async function main() {
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ async function main() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					DriveFile.update({ _id: file._id }, {
 | 
										DriveFile.update({ _id: file._id }, {
 | 
				
			||||||
						$set: {
 | 
											$set: {
 | 
				
			||||||
							'metadata.isMetaOnly': true
 | 
												'metadata.withoutChunks': true
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
				]).then(async () => {
 | 
									]).then(async () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										168
									
								
								cli/init.js
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,168 +0,0 @@
 | 
				
			||||||
const fs = require('fs');
 | 
					 | 
				
			||||||
const path = require('path');
 | 
					 | 
				
			||||||
const yaml = require('js-yaml');
 | 
					 | 
				
			||||||
const inquirer = require('inquirer');
 | 
					 | 
				
			||||||
const chalk = require('chalk');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const configDirPath = `${__dirname}/../.config`;
 | 
					 | 
				
			||||||
const configPath = `${configDirPath}/default.yml`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const form = [{
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'maintainerName',
 | 
					 | 
				
			||||||
	message: 'Your name:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'maintainerUrl',
 | 
					 | 
				
			||||||
	message: 'Your home page URL or your mailto URL:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'url',
 | 
					 | 
				
			||||||
	message: 'URL you want to run Misskey:',
 | 
					 | 
				
			||||||
	validate: function(wannabeurl) {
 | 
					 | 
				
			||||||
		return wannabeurl.match('^http\(s?\)://') ? true :
 | 
					 | 
				
			||||||
		       'URL needs to start with http:// or https://';
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'port',
 | 
					 | 
				
			||||||
	message: 'Listen port (e.g. 443):'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'confirm',
 | 
					 | 
				
			||||||
	name: 'https',
 | 
					 | 
				
			||||||
	message: 'Use TLS?',
 | 
					 | 
				
			||||||
	default: false
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'https_key',
 | 
					 | 
				
			||||||
	message: 'Path of tls key:',
 | 
					 | 
				
			||||||
	when: ctx => ctx.https
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'https_cert',
 | 
					 | 
				
			||||||
	message: 'Path of tls cert:',
 | 
					 | 
				
			||||||
	when: ctx => ctx.https
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'https_ca',
 | 
					 | 
				
			||||||
	message: 'Path of tls ca:',
 | 
					 | 
				
			||||||
	when: ctx => ctx.https
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'mongo_host',
 | 
					 | 
				
			||||||
	message: 'MongoDB\'s host:',
 | 
					 | 
				
			||||||
	default: 'localhost'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'mongo_port',
 | 
					 | 
				
			||||||
	message: 'MongoDB\'s port:',
 | 
					 | 
				
			||||||
	default: '27017'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'mongo_db',
 | 
					 | 
				
			||||||
	message: 'MongoDB\'s db:',
 | 
					 | 
				
			||||||
	default: 'misskey'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'mongo_user',
 | 
					 | 
				
			||||||
	message: 'MongoDB\'s user:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'password',
 | 
					 | 
				
			||||||
	name: 'mongo_pass',
 | 
					 | 
				
			||||||
	message: 'MongoDB\'s password:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'redis_host',
 | 
					 | 
				
			||||||
	message: 'Redis\'s host:',
 | 
					 | 
				
			||||||
	default: 'localhost'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'redis_port',
 | 
					 | 
				
			||||||
	message: 'Redis\'s port:',
 | 
					 | 
				
			||||||
	default: '6379'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'password',
 | 
					 | 
				
			||||||
	name: 'redis_pass',
 | 
					 | 
				
			||||||
	message: 'Redis\'s password:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'confirm',
 | 
					 | 
				
			||||||
	name: 'elasticsearch',
 | 
					 | 
				
			||||||
	message: 'Use Elasticsearch?',
 | 
					 | 
				
			||||||
	default: false
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'es_host',
 | 
					 | 
				
			||||||
	message: 'Elasticsearch\'s host:',
 | 
					 | 
				
			||||||
	default: 'localhost',
 | 
					 | 
				
			||||||
	when: ctx => ctx.elasticsearch
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'es_port',
 | 
					 | 
				
			||||||
	message: 'Elasticsearch\'s port:',
 | 
					 | 
				
			||||||
	default: '9200',
 | 
					 | 
				
			||||||
	when: ctx => ctx.elasticsearch
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'password',
 | 
					 | 
				
			||||||
	name: 'es_pass',
 | 
					 | 
				
			||||||
	message: 'Elasticsearch\'s password:',
 | 
					 | 
				
			||||||
	when: ctx => ctx.elasticsearch
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'recaptcha_site',
 | 
					 | 
				
			||||||
	message: 'reCAPTCHA\'s site key:'
 | 
					 | 
				
			||||||
}, {
 | 
					 | 
				
			||||||
	type: 'input',
 | 
					 | 
				
			||||||
	name: 'recaptcha_secret',
 | 
					 | 
				
			||||||
	message: 'reCAPTCHA\'s secret key:'
 | 
					 | 
				
			||||||
}];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
inquirer.prompt(form).then(as => {
 | 
					 | 
				
			||||||
	// Mapping answers
 | 
					 | 
				
			||||||
	const conf = {
 | 
					 | 
				
			||||||
		maintainer: {
 | 
					 | 
				
			||||||
			name: as['maintainerName'],
 | 
					 | 
				
			||||||
			url: as['maintainerUrl']
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		url: as['url'],
 | 
					 | 
				
			||||||
		port: parseInt(as['port'], 10),
 | 
					 | 
				
			||||||
		mongodb: {
 | 
					 | 
				
			||||||
			host: as['mongo_host'],
 | 
					 | 
				
			||||||
			port: parseInt(as['mongo_port'], 10),
 | 
					 | 
				
			||||||
			db: as['mongo_db'],
 | 
					 | 
				
			||||||
			user: as['mongo_user'],
 | 
					 | 
				
			||||||
			pass: as['mongo_pass']
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		redis: {
 | 
					 | 
				
			||||||
			host: as['redis_host'],
 | 
					 | 
				
			||||||
			port: parseInt(as['redis_port'], 10),
 | 
					 | 
				
			||||||
			pass: as['redis_pass']
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		elasticsearch: {
 | 
					 | 
				
			||||||
			enable: as['elasticsearch'],
 | 
					 | 
				
			||||||
			host: as['es_host'] || null,
 | 
					 | 
				
			||||||
			port: parseInt(as['es_port'], 10) || null,
 | 
					 | 
				
			||||||
			pass: as['es_pass'] || null
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		recaptcha: {
 | 
					 | 
				
			||||||
			site_key: as['recaptcha_site'],
 | 
					 | 
				
			||||||
			secret_key: as['recaptcha_secret']
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (as['https']) {
 | 
					 | 
				
			||||||
		conf.https = {
 | 
					 | 
				
			||||||
			key: as['https_key'] || null,
 | 
					 | 
				
			||||||
			cert: as['https_cert'] || null,
 | 
					 | 
				
			||||||
			ca: as['https_ca'] || null
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	console.log(`Thanks. Writing the configuration to ${chalk.bold(path.resolve(configPath))}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try {
 | 
					 | 
				
			||||||
		fs.writeFileSync(configPath, yaml.dump(conf));
 | 
					 | 
				
			||||||
		console.log(chalk.green('Well done.'));
 | 
					 | 
				
			||||||
	} catch (e) {
 | 
					 | 
				
			||||||
		console.error(e);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										23
									
								
								cli/mark-admin.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					const mongo = require('mongodb');
 | 
				
			||||||
 | 
					const User = require('../built/models/user').default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const args = process.argv.slice(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const user = args[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const q = user.startsWith('@') ? {
 | 
				
			||||||
 | 
						username: user.split('@')[1],
 | 
				
			||||||
 | 
						host: user.split('@')[2] || null
 | 
				
			||||||
 | 
					} : { _id: new mongo.ObjectID(user) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(`Mark as admin ${user}...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					User.update(q, {
 | 
				
			||||||
 | 
						$set: {
 | 
				
			||||||
 | 
							isAdmin: true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}).then(() => {
 | 
				
			||||||
 | 
						console.log(`Done ${user}`);
 | 
				
			||||||
 | 
					}, e => {
 | 
				
			||||||
 | 
						console.error(e);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										23
									
								
								cli/mark-verified.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					const mongo = require('mongodb');
 | 
				
			||||||
 | 
					const User = require('../built/models/user').default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const args = process.argv.slice(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const user = args[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const q = user.startsWith('@') ? {
 | 
				
			||||||
 | 
						username: user.split('@')[1],
 | 
				
			||||||
 | 
						host: user.split('@')[2] || null
 | 
				
			||||||
 | 
					} : { _id: new mongo.ObjectID(user) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(`Mark as verfied ${user}...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					User.update(q, {
 | 
				
			||||||
 | 
						$set: {
 | 
				
			||||||
 | 
							isVerified: true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}).then(() => {
 | 
				
			||||||
 | 
						console.log(`Done ${user}`);
 | 
				
			||||||
 | 
					}, e => {
 | 
				
			||||||
 | 
						console.error(e);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,8 @@
 | 
				
			||||||
const chalk = require('chalk');
 | 
					const chalk = require('chalk');
 | 
				
			||||||
const sequential = require('promise-sequential');
 | 
					const sequential = require('promise-sequential');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { default: User } = require('../built/models/user');
 | 
					const { default: User } = require('../../built/models/user');
 | 
				
			||||||
const { default: DriveFile } = require('../built/models/drive-file');
 | 
					const { default: DriveFile } = require('../../built/models/drive-file');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main() {
 | 
					async function main() {
 | 
				
			||||||
	const promiseGens = [];
 | 
						const promiseGens = [];
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,8 @@
 | 
				
			||||||
const chalk = require('chalk');
 | 
					const chalk = require('chalk');
 | 
				
			||||||
const sequential = require('promise-sequential');
 | 
					const sequential = require('promise-sequential');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { default: User } = require('../built/models/user');
 | 
					const { default: User } = require('../../built/models/user');
 | 
				
			||||||
const { default: DriveFile } = require('../built/models/drive-file');
 | 
					const { default: DriveFile } = require('../../built/models/drive-file');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main() {
 | 
					async function main() {
 | 
				
			||||||
	const promiseGens = [];
 | 
						const promiseGens = [];
 | 
				
			||||||
							
								
								
									
										9
									
								
								cli/migration/5.0.0.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					const { default: DriveFile } = require('../../built/models/drive-file');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DriveFile.update({}, {
 | 
				
			||||||
 | 
						$rename: {
 | 
				
			||||||
 | 
							'metadata.isMetaOnly': 'metadata.withoutChunks'
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
 | 
						multi: true
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										29
									
								
								cli/reset-password.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					const mongo = require('mongodb');
 | 
				
			||||||
 | 
					const bcrypt = require('bcryptjs');
 | 
				
			||||||
 | 
					const User = require('../built/models/user').default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const args = process.argv.slice(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const user = args[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const q = user.startsWith('@') ? {
 | 
				
			||||||
 | 
						username: user.split('@')[1],
 | 
				
			||||||
 | 
						host: user.split('@')[2] || null
 | 
				
			||||||
 | 
					} : { _id: new mongo.ObjectID(user) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(`Resetting password for ${user}...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const passwd = 'yo';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Generate hash of password
 | 
				
			||||||
 | 
					const hash = bcrypt.hashSync(passwd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					User.update(q, {
 | 
				
			||||||
 | 
						$set: {
 | 
				
			||||||
 | 
							password: hash
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}).then(() => {
 | 
				
			||||||
 | 
						console.log(`Password of ${user} is now '${passwd}'`);
 | 
				
			||||||
 | 
					}, e => {
 | 
				
			||||||
 | 
						console.error(e);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ RUN pacman -S --noconfirm pacman
 | 
				
			||||||
RUN pacman-db-upgrade
 | 
					RUN pacman-db-upgrade
 | 
				
			||||||
RUN pacman -S --noconfirm archlinux-keyring
 | 
					RUN pacman -S --noconfirm archlinux-keyring
 | 
				
			||||||
RUN pacman -Syyu --noconfirm
 | 
					RUN pacman -Syyu --noconfirm
 | 
				
			||||||
RUN pacman -S --noconfirm git nodejs npm mongodb redis imagemagick
 | 
					RUN pacman -S --noconfirm git nodejs npm mongodb redis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY misskey.sh /root/misskey.sh
 | 
					COPY misskey.sh /root/misskey.sh
 | 
				
			||||||
RUN chmod u+x /root/misskey.sh
 | 
					RUN chmod u+x /root/misskey.sh
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								docs/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					# Docs
 | 
				
			||||||
 | 
					These docs are for contributors of Misskey or admins of instance of Misskey.
 | 
				
			||||||
 | 
					Docs for users are located in `src/docs`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					これらのドキュメントはMisskeyの開発者またはMisskeyインスタンス運営者向けです。
 | 
				
			||||||
 | 
					利用者向けのドキュメントは`src/docs`にあります。
 | 
				
			||||||
							
								
								
									
										46
									
								
								docs/manage.en.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					# Management guide
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Check the status of the job queue
 | 
				
			||||||
 | 
					coming soon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Mark as 'admin' user
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/mark-admin (User-ID or Username)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Mark as 'verified' user
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/mark-verified (User-ID or Username)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Suspend users
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/suspend (User-ID or Username)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					e.g.
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					# Use id
 | 
				
			||||||
 | 
					node cli/suspend 57d01a501fdf2d07be417afe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Use username
 | 
				
			||||||
 | 
					node cli/suspend @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Use username (remote)
 | 
				
			||||||
 | 
					node cli/suspend @syuilo@misskey.xyz
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Reset password
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/reset-password (User-ID or Username)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Clean up cached remote files
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/clean-cached-remote-files
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Clean up unused drive files
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/clean-unused-drive-files
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					> We recommend that you announce a user that unused drive files will be deleted before performing this operation, as it may delete the user's important files.
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,46 @@
 | 
				
			||||||
# 運営ガイド
 | 
					# 運営ガイド
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ジョブキューの状態を調べる
 | 
					## ジョブキューの状態を調べる
 | 
				
			||||||
Misskeyのディレクトリで:
 | 
					coming soon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 管理者ユーザーを設定する
 | 
				
			||||||
``` shell
 | 
					``` shell
 | 
				
			||||||
node_modules/kue/bin/kue-dashboard -p 3050
 | 
					node cli/mark-admin (ユーザーID または ユーザー名)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 'verified'ユーザーを設定する
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/mark-verified (ユーザーID または ユーザー名)
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
ポート3050にアクセスするとUIが表示されます
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ユーザーを凍結する
 | 
					## ユーザーを凍結する
 | 
				
			||||||
``` shell
 | 
					``` shell
 | 
				
			||||||
node cli/suspend (ユーザーID)
 | 
					node cli/suspend (ユーザーID または ユーザー名)
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					例:
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					# ユーザーID
 | 
				
			||||||
 | 
					node cli/suspend 57d01a501fdf2d07be417afe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ユーザー名
 | 
				
			||||||
 | 
					node cli/suspend @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ユーザー名 (リモート)
 | 
				
			||||||
 | 
					node cli/suspend @syuilo@misskey.xyz
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ユーザーのパスワードをリセットする
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/reset-password (ユーザーID または ユーザー名)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## キャッシュされたリモートファイルをクリーンアップする
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/clean-cached-remote-files
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 使われていないドライブのファイルをクリーンアップする
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					node cli/clean-unused-drive-files
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					> ユーザーの大事なファイルを削除する可能性があるので、この操作を実行する前にユーザーに告知することをお勧めします。
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										110
									
								
								docs/setup.en.md
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -8,18 +8,13 @@ This guide describes how to install and setup Misskey.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*1.* reCAPTCHA tokens
 | 
					*1.* Create Misskey user
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
Misskey requires reCAPTCHA tokens.
 | 
					Running misskey on root is not a good idea so we create a user for that.
 | 
				
			||||||
Please visit https://www.google.com/recaptcha/intro/ and generate keys.
 | 
					In debian for exemple :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*(optional)* Generating VAPID keys
 | 
					```
 | 
				
			||||||
----------------------------------------------------------------
 | 
					adduser --disabled-password --disabled-login misskey
 | 
				
			||||||
If you want to enable ServiceWroker, you need to generate VAPID keys:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
``` shell
 | 
					 | 
				
			||||||
npm install web-push -g
 | 
					 | 
				
			||||||
web-push generate-vapid-keys
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*2.* Install dependencies
 | 
					*2.* Install dependencies
 | 
				
			||||||
| 
						 | 
					@ -27,25 +22,52 @@ web-push generate-vapid-keys
 | 
				
			||||||
Please install and setup these softwares:
 | 
					Please install and setup these softwares:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Dependencies :package:
 | 
					#### Dependencies :package:
 | 
				
			||||||
* *Node.js* and *npm*
 | 
					* **[Node.js](https://nodejs.org/en/)**
 | 
				
			||||||
* **[MongoDB](https://www.mongodb.com/)**
 | 
					* **[MongoDB](https://www.mongodb.com/)** >= 3.6
 | 
				
			||||||
* **[Redis](https://redis.io/)**
 | 
					* **[Redis](https://redis.io/)**
 | 
				
			||||||
* **[ImageMagick](http://www.imagemagick.org/script/index.php)** >= 7.0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### Optional
 | 
					##### Optional
 | 
				
			||||||
* [Elasticsearch](https://www.elastic.co/) - used to provide searching feature instead of MongoDB
 | 
					* [Elasticsearch](https://www.elastic.co/) - used to provide searching feature instead of MongoDB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*3.* Install Misskey
 | 
					 | 
				
			||||||
----------------------------------------------------------------
 | 
					 | 
				
			||||||
1. `git clone -b master git://github.com/syuilo/misskey.git`
 | 
					 | 
				
			||||||
2. `cd misskey`
 | 
					 | 
				
			||||||
3. `npm install`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
*4.* Prepare configuration
 | 
					*3.* Setup MongoDB
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
You need to generate config file via `npm run config` command.
 | 
					In root :
 | 
				
			||||||
 | 
					1. `mongo` Go to the mongo shell
 | 
				
			||||||
 | 
					2. `use misskey` Use the misskey database
 | 
				
			||||||
 | 
					3. `db.users.save( {dummy:"dummy"} )` Write dummy data to initialize the db.
 | 
				
			||||||
 | 
					4. `db.createUser( { user: "misskey", pwd: "<password>", roles: [ { role: "readWrite", db: "misskey" } ] } )` Create the misskey user.
 | 
				
			||||||
 | 
					5. `exit` You're done !
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*5.* Build Misskey
 | 
					*4.* Install Misskey
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					1. `su - misskey` Connect to misskey user.
 | 
				
			||||||
 | 
					2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch.
 | 
				
			||||||
 | 
					3. `cd misskey` Navigate to misskey directory
 | 
				
			||||||
 | 
					4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
 | 
				
			||||||
 | 
					5. `npm install` Install misskey dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*(optional)* reCAPTCHA tokens
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					If you want to enable reCAPTCHA, you need to generate reCAPTCHA tokens:
 | 
				
			||||||
 | 
					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:
 | 
				
			||||||
 | 
					Unless you have set your global node_modules location elsewhere, you need to run this in root.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					npm install web-push -g
 | 
				
			||||||
 | 
					web-push generate-vapid-keys
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*5.* Make configuration file
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
 | 
				
			||||||
 | 
					2. Edit `default.yml`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*6.* Build Misskey
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Build misskey with the following:
 | 
					Build misskey with the following:
 | 
				
			||||||
| 
						 | 
					@ -61,14 +83,48 @@ If you're still encountering errors about some modules, use node-gyp:
 | 
				
			||||||
3. `node-gyp build`
 | 
					3. `node-gyp build`
 | 
				
			||||||
4. `npm run build`
 | 
					4. `npm run build`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*6.* That is it.
 | 
					*7.* That is it.
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
Well done! Now, you have an environment that run to Misskey.
 | 
					Well done! Now, you have an environment that run to Misskey.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Launch
 | 
					### Launch normally
 | 
				
			||||||
Just `sudo npm start`. GLHF!
 | 
					Just `npm start`. GLHF!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Launch with systemd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Create a systemd service here: `/etc/systemd/system/misskey.service`
 | 
				
			||||||
 | 
					2. Edit it, and paste this and save:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=Misskey daemon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=simple
 | 
				
			||||||
 | 
					User=misskey
 | 
				
			||||||
 | 
					ExecStart=/usr/bin/npm start
 | 
				
			||||||
 | 
					WorkingDirectory=/home/misskey/misskey
 | 
				
			||||||
 | 
					TimeoutSec=60
 | 
				
			||||||
 | 
					StandardOutput=syslog
 | 
				
			||||||
 | 
					StandardError=syslog
 | 
				
			||||||
 | 
					SyslogIdentifier=misskey
 | 
				
			||||||
 | 
					Restart=always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. `systemctl daemon-reload ; systemctl enable misskey` Reload systemd and enable the misskey service.
 | 
				
			||||||
 | 
					4. `systemctl start misskey` Start the misskey service.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can check if the service is running with `systemctl status misskey`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Way to Update to latest version of your Misskey
 | 
					### Way to Update to latest version of your Misskey
 | 
				
			||||||
1. `git reset --hard && git pull origin master`
 | 
					1. `git fetch`
 | 
				
			||||||
2. `npm install`
 | 
					2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
 | 
				
			||||||
3. `npm run build`
 | 
					3. `npm install`
 | 
				
			||||||
 | 
					4. `npm run build`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you have any questions or troubles, feel free to contact us!
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										127
									
								
								docs/setup.ja.md
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -8,10 +8,48 @@ Misskeyサーバーの構築にご関心をお寄せいただきありがとう
 | 
				
			||||||
 | 
					
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*1.* reCAPTCHAトークンの用意
 | 
					*1.* Misskeyユーザーの作成
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
MisskeyはreCAPTCHAトークンを必要とします。
 | 
					Misskeyのrootで実行しない方がよいため、代わりにユーザーを作成します。
 | 
				
			||||||
https://www.google.com/recaptcha/intro/ にアクセスしてトークンを生成してください。
 | 
					Debianの例:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					adduser --disabled-password --disabled-login misskey
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*2.* 依存関係をインストールする
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					これらのソフトウェアをインストール・設定してください:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 依存関係 :package:
 | 
				
			||||||
 | 
					* **[Node.js](https://nodejs.org/en/)**
 | 
				
			||||||
 | 
					* **[MongoDB](https://www.mongodb.com/)** (3.6以上)
 | 
				
			||||||
 | 
					* **[Redis](https://redis.io/)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					##### オプション
 | 
				
			||||||
 | 
					* [Elasticsearch](https://www.elastic.co/) - 検索機能を向上させるために用います。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*3.* MongoDBの設定
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					ルートで:
 | 
				
			||||||
 | 
					1. `mongo` mongoシェルを起動
 | 
				
			||||||
 | 
					2. `use misskey` misskeyデータベースを使用
 | 
				
			||||||
 | 
					3. `db.users.save( {dummy:"dummy"} )` ダミーデータを書き込みDBを初期化
 | 
				
			||||||
 | 
					4. `db.createUser( { user: "misskey", pwd: "<password>", roles: [ { role: "readWrite", db: "misskey" } ] } )` misskeyユーザーを作成
 | 
				
			||||||
 | 
					5. `exit` mongoシェルを終了
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*4.* Misskeyのインストール
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					1. `su - misskey` misskeyユーザーを使用
 | 
				
			||||||
 | 
					2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
 | 
				
			||||||
 | 
					3. `cd misskey` misskeyディレクトリに移動
 | 
				
			||||||
 | 
					4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
 | 
				
			||||||
 | 
					5. `npm install` Misskeyの依存パッケージをインストール
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*(オプション)* reCAPTCHAトークン
 | 
				
			||||||
 | 
					----------------------------------------------------------------
 | 
				
			||||||
 | 
					reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。
 | 
				
			||||||
 | 
					https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*(オプション)* VAPIDキーペアの生成
 | 
					*(オプション)* VAPIDキーペアの生成
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -22,56 +60,67 @@ npm install web-push -g
 | 
				
			||||||
web-push generate-vapid-keys
 | 
					web-push generate-vapid-keys
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*2.* 依存関係をインストールする
 | 
					*5.* 設定ファイルを作成する
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
これらのソフトウェアをインストール・設定してください:
 | 
					1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。
 | 
				
			||||||
 | 
					2. `default.yml` を編集する。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### 依存関係 :package:
 | 
					*6.* Misskeyのビルド
 | 
				
			||||||
* *Node.js* と *npm*
 | 
					 | 
				
			||||||
* **[MongoDB](https://www.mongodb.com/)**
 | 
					 | 
				
			||||||
* **[Redis](https://redis.io/)**
 | 
					 | 
				
			||||||
* **[ImageMagick](http://www.imagemagick.org/script/index.php)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### オプション
 | 
					 | 
				
			||||||
* [Elasticsearch](https://www.elastic.co/) - 検索機能を向上させるために用います。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
*3.* Misskeyのインストール
 | 
					 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
1. `git clone -b master git://github.com/syuilo/misskey.git`
 | 
					 | 
				
			||||||
2. `cd misskey`
 | 
					 | 
				
			||||||
3. `npm install`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
*4.* 設定ファイルを用意する
 | 
					次のコマンドでMisskeyをビルドしてください:
 | 
				
			||||||
----------------------------------------------------------------
 | 
					 | 
				
			||||||
`npm run config`コマンドを利用して、ガイドに従って情報を入力してください。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
*5.* Misskeyのビルド
 | 
					`npm run build`
 | 
				
			||||||
----------------------------------------------------------------
 | 
					
 | 
				
			||||||
 | 
					Debianをお使いであれば、`build-essential`パッケージをインストールする必要があります。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					何らかのモジュールでエラーが発生する場合はnode-gypを使ってください:
 | 
				
			||||||
1. `npm install -g node-gyp`
 | 
					1. `npm install -g node-gyp`
 | 
				
			||||||
2. `node-gyp configure`
 | 
					2. `node-gyp configure`
 | 
				
			||||||
3. `node-gyp build`
 | 
					3. `node-gyp build`
 | 
				
			||||||
4. `npm run build`
 | 
					4. `npm run build`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*6.* 以上です!
 | 
					*7.* 以上です!
 | 
				
			||||||
----------------------------------------------------------------
 | 
					----------------------------------------------------------------
 | 
				
			||||||
お疲れ様でした。これでMisskeyを動かす準備は整いました。
 | 
					お疲れ様でした。これでMisskeyを動かす準備は整いました。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 起動
 | 
					### 通常起動
 | 
				
			||||||
`sudo npm start`するだけです。GLHF!
 | 
					`npm start`するだけです。GLHF!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### systemdを用いた起動
 | 
				
			||||||
 | 
					1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service`
 | 
				
			||||||
 | 
					2. エディタで開き、以下のコードを貼り付けて保存:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=Misskey daemon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=simple
 | 
				
			||||||
 | 
					User=misskey
 | 
				
			||||||
 | 
					ExecStart=/usr/bin/npm start
 | 
				
			||||||
 | 
					WorkingDirectory=/home/misskey/misskey
 | 
				
			||||||
 | 
					TimeoutSec=60
 | 
				
			||||||
 | 
					StandardOutput=syslog
 | 
				
			||||||
 | 
					StandardError=syslog
 | 
				
			||||||
 | 
					SyslogIdentifier=misskey
 | 
				
			||||||
 | 
					Restart=always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化
 | 
				
			||||||
 | 
					4. `systemctl start misskey` misskeyサービスの起動
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`systemctl status misskey`と入力すると、サービスの状態を調べることができます。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Misskeyを最新バージョンにアップデートする方法:
 | 
					### Misskeyを最新バージョンにアップデートする方法:
 | 
				
			||||||
1. `git reset --hard && git pull origin master`
 | 
					1. `git fetch`
 | 
				
			||||||
2. `npm install`
 | 
					2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
 | 
				
			||||||
3. `npm run build`
 | 
					3. `npm install`
 | 
				
			||||||
 | 
					4. `npm run build`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## メモリが足りなくてビルドできない場合
 | 
					----------------------------------------------------------------
 | 
				
			||||||
Misskeyの(クライアントの)ビルドには、目安として8GBくらいのメモリを必要とします。
 | 
					 | 
				
			||||||
VPSなどでビルドする時は、もしかしたらメモリが足りなくなる可能性があります。
 | 
					 | 
				
			||||||
そうなった場合、もしVPSではなくあなたのPCが十分なメモリを搭載しているなら、あなたのPC上でビルドし、生成されたファイルをVPSにFTPでアップロードする方法を採ることができます。
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. あなたのPC上にMisskeyをインストールする
 | 
					なにかお困りのことがありましたらお気軽にご連絡ください。
 | 
				
			||||||
2. 設定ファイルを用意する。設定ファイルは、サーバーに合わせた設定にします。
 | 
					 | 
				
			||||||
3. npm run webpack
 | 
					 | 
				
			||||||
4. built/client をサーバーにアップロードする
 | 
					 | 
				
			||||||
5. サーバー上で、npm run gulp
 | 
					 | 
				
			||||||
6. 完了
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,19 +4,19 @@ Misskey's Translation
 | 
				
			||||||
If you find an untranslated part on Misskey:
 | 
					If you find an untranslated part on Misskey:
 | 
				
			||||||
--------------------------------------------
 | 
					--------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Look for untranslated parts in the miskey's source code.
 | 
					1. Look for untranslated parts in the misskey's source code.
 | 
				
			||||||
	- For instance, if you find an untranslated part in: `src/client/app/mobile/views/pages/home.vue`.
 | 
						- For instance, if you find an untranslated part in: `src/client/app/mobile/views/pages/home.vue`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Replace the untranslated portion with a character string of the form `%i18n:@foo%`.
 | 
					2. Replace the untranslated portion with a character string of the form `%i18n:@foo%`.
 | 
				
			||||||
	- In fact, `foo` should be a word that is appropriate for the situation and is easy to understand in English.
 | 
						- In fact, `foo` should be a word that is appropriate for the situation and is easy to understand in English.
 | 
				
			||||||
	- For example, if the untranslated portion is the following "タイムライン" you must write: `%i18n:@timeline%`.
 | 
						- For example, if the untranslated portion is the following "タイムライン" you must write: `%i18n:@timeline%`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
3. Open each language file in /locales, check whether the <strong>file name (path)</strong> found in step 1 exists, if not, create it.
 | 
					3. Open the `locales/ja.yml`, check whether the <strong>file name (path)</strong> found in step 1 exists, if not, create it.
 | 
				
			||||||
	- Do not put the beginning of the path `src/client/app/` in the locale file.
 | 
						- Do not put the beginning of the path `src/client/app/` in the locale file.
 | 
				
			||||||
	- For example, in this case we want to modify untranslated parts of `src/client/app/mobile/views/pages/home.vue`, so the key is `mobile/views/pages/home.vue`.
 | 
						- For example, in this case we want to modify untranslated parts of `src/client/app/mobile/views/pages/home.vue`, so the key is `mobile/views/pages/home.vue`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4. Add the translated text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes.
 | 
					4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes.
 | 
				
			||||||
	-   For example, in this case we add timeline: `timeline: "Timeline"` to `locales/en.yml`, and `timeline: "タイムライン"` to `locales/ja.yml`.
 | 
						-   For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja.yml`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
5. And done!
 | 
					5. And done!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,12 +11,12 @@ Misskey内の未翻訳箇所を見つけたら
 | 
				
			||||||
	- `foo`は実際にはその場に適したわかりやすい(英語の)名前にしてください。
 | 
						- `foo`は実際にはその場に適したわかりやすい(英語の)名前にしてください。
 | 
				
			||||||
	- 例えば未翻訳箇所が「タイムライン」というテキストだった場合、`%i18n:@timeline%`のようにします。
 | 
						- 例えば未翻訳箇所が「タイムライン」というテキストだった場合、`%i18n:@timeline%`のようにします。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
3. /locales 内にあるそれぞれの言語ファイルを開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。
 | 
					3. `locales/ja.yml`を開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。
 | 
				
			||||||
	- パスの`src/client/app/`は省略してください。
 | 
						- パスの`src/client/app/`は省略してください。
 | 
				
			||||||
	- 例えば、今回の例では`src/client/app/mobile/views/pages/home.vue`の未翻訳箇所を修正したいので、キーは`mobile/views/pages/home.vue`になります。
 | 
						- 例えば、今回の例では`src/client/app/mobile/views/pages/home.vue`の未翻訳箇所を修正したいので、キーは`mobile/views/pages/home.vue`になります。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4. そのキーの直下に2.で置換した`foo`の部分をキーとし、翻訳後のテキストを値とするプロパティを追加します。
 | 
					4. そのキーの直下に2.で置換した`foo`の部分をキーとし、テキストを値とするプロパティを追加します。
 | 
				
			||||||
	- 例えば、今回の例で言うと`locales/ja.yml`に`timeline: "タイムライン"`、`locales/en.yml`に`timeline: "Timeline"`を追加します。
 | 
						- 例えば、今回の例で言うと`locales/ja.yml`に`timeline: "タイムライン"`を追加します。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
5. 完了です!
 | 
					5. 完了です!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
How to create indexes
 | 
					 | 
				
			||||||
=====================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
``` shell
 | 
					 | 
				
			||||||
curl -XPOST localhost:9200/misskey -d @path/to/mappings.json
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,65 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	"settings": {
 | 
					 | 
				
			||||||
		"analysis": {
 | 
					 | 
				
			||||||
			"analyzer": {
 | 
					 | 
				
			||||||
				"bigram": {
 | 
					 | 
				
			||||||
					"tokenizer": "bigram_tokenizer"
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			"tokenizer": {
 | 
					 | 
				
			||||||
				"bigram_tokenizer": {
 | 
					 | 
				
			||||||
					"type": "nGram",
 | 
					 | 
				
			||||||
					"min_gram": 2,
 | 
					 | 
				
			||||||
					"max_gram": 2,
 | 
					 | 
				
			||||||
					"token_chars": [
 | 
					 | 
				
			||||||
						"letter",
 | 
					 | 
				
			||||||
						"digit"
 | 
					 | 
				
			||||||
					]
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"mappings": {
 | 
					 | 
				
			||||||
		"user": {
 | 
					 | 
				
			||||||
			"properties": {
 | 
					 | 
				
			||||||
				"username": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "analyzed",
 | 
					 | 
				
			||||||
					"analyzer": "bigram"
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				"name": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "analyzed",
 | 
					 | 
				
			||||||
					"analyzer": "bigram"
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				"bio": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "analyzed",
 | 
					 | 
				
			||||||
					"analyzer": "kuromoji"
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		"post": {
 | 
					 | 
				
			||||||
			"properties": {
 | 
					 | 
				
			||||||
				"text": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "analyzed",
 | 
					 | 
				
			||||||
					"analyzer": "kuromoji"
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		"drive_file": {
 | 
					 | 
				
			||||||
			"properties": {
 | 
					 | 
				
			||||||
				"name": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "analyzed",
 | 
					 | 
				
			||||||
					"analyzer": "kuromoji"
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				"user": {
 | 
					 | 
				
			||||||
					"type": "string",
 | 
					 | 
				
			||||||
					"index": "not_analyzed"
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										42
									
								
								gulpfile.ts
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -9,6 +9,7 @@ import * as ts from 'gulp-typescript';
 | 
				
			||||||
const sourcemaps = require('gulp-sourcemaps');
 | 
					const sourcemaps = require('gulp-sourcemaps');
 | 
				
			||||||
import tslint from 'gulp-tslint';
 | 
					import tslint from 'gulp-tslint';
 | 
				
			||||||
const cssnano = require('gulp-cssnano');
 | 
					const cssnano = require('gulp-cssnano');
 | 
				
			||||||
 | 
					const stylus = require('gulp-stylus');
 | 
				
			||||||
import * as uglifyComposer from 'gulp-uglify/composer';
 | 
					import * as uglifyComposer from 'gulp-uglify/composer';
 | 
				
			||||||
import pug = require('gulp-pug');
 | 
					import pug = require('gulp-pug');
 | 
				
			||||||
import * as rimraf from 'rimraf';
 | 
					import * as rimraf from 'rimraf';
 | 
				
			||||||
| 
						 | 
					@ -20,9 +21,8 @@ import * as replace from 'gulp-replace';
 | 
				
			||||||
import * as htmlmin from 'gulp-htmlmin';
 | 
					import * as htmlmin from 'gulp-htmlmin';
 | 
				
			||||||
const uglifyes = require('uglify-es');
 | 
					const uglifyes = require('uglify-es');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import locales from './locales';
 | 
					const locales = require('./locales');
 | 
				
			||||||
import { fa } from './src/build/fa';
 | 
					import { fa } from './src/misc/fa';
 | 
				
			||||||
const client = require('./built/client/meta.json');
 | 
					 | 
				
			||||||
import config from './src/config';
 | 
					import config from './src/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uglify = uglifyComposer(uglifyes, console);
 | 
					const uglify = uglifyComposer(uglifyes, console);
 | 
				
			||||||
| 
						 | 
					@ -38,8 +38,6 @@ if (isDebug) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const constants = require('./src/const.json');
 | 
					const constants = require('./src/const.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require('./src/client/docs/gulpfile.ts');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gulp.task('build', [
 | 
					gulp.task('build', [
 | 
				
			||||||
	'build:ts',
 | 
						'build:ts',
 | 
				
			||||||
	'build:copy',
 | 
						'build:copy',
 | 
				
			||||||
| 
						 | 
					@ -47,8 +45,6 @@ gulp.task('build', [
 | 
				
			||||||
	'doc'
 | 
						'doc'
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('rebuild', ['clean', 'build']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gulp.task('build:ts', () => {
 | 
					gulp.task('build:ts', () => {
 | 
				
			||||||
	const tsProject = ts.createProject('./tsconfig.json');
 | 
						const tsProject = ts.createProject('./tsconfig.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,19 +81,19 @@ gulp.task('lint', () =>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('format', () =>
 | 
					gulp.task('format', () =>
 | 
				
			||||||
gulp.src('./src/**/*.ts')
 | 
						gulp.src('./src/**/*.ts')
 | 
				
			||||||
	.pipe(tslint({
 | 
							.pipe(tslint({
 | 
				
			||||||
		formatter: 'verbose',
 | 
								formatter: 'verbose',
 | 
				
			||||||
		fix: true
 | 
								fix: true
 | 
				
			||||||
	}))
 | 
							}))
 | 
				
			||||||
	.pipe(tslint.report())
 | 
							.pipe(tslint.report())
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('mocha', () =>
 | 
					gulp.task('mocha', () =>
 | 
				
			||||||
	gulp.src([])
 | 
						gulp.src('./test/**/*.ts')
 | 
				
			||||||
		.pipe(mocha({
 | 
							.pipe(mocha({
 | 
				
			||||||
			exit: true,
 | 
								exit: true,
 | 
				
			||||||
			compilers: 'ts:ts-node/register'
 | 
								require: 'ts-node/register'
 | 
				
			||||||
		} as any))
 | 
							} as any))
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,8 +114,9 @@ gulp.task('build:client', [
 | 
				
			||||||
	'copy:client'
 | 
						'copy:client'
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('build:client:script', () =>
 | 
					gulp.task('build:client:script', () => {
 | 
				
			||||||
	gulp.src(['./src/client/app/boot.js', './src/client/app/safe.js'])
 | 
						const client = require('./built/client/meta.json');
 | 
				
			||||||
 | 
						return gulp.src(['./src/client/app/boot.js', './src/client/app/safe.js'])
 | 
				
			||||||
		.pipe(replace('VERSION', JSON.stringify(client.version)))
 | 
							.pipe(replace('VERSION', JSON.stringify(client.version)))
 | 
				
			||||||
		.pipe(replace('API', JSON.stringify(config.api_url)))
 | 
							.pipe(replace('API', JSON.stringify(config.api_url)))
 | 
				
			||||||
		.pipe(replace('ENV', JSON.stringify(env)))
 | 
							.pipe(replace('ENV', JSON.stringify(env)))
 | 
				
			||||||
| 
						 | 
					@ -127,8 +124,8 @@ gulp.task('build:client:script', () =>
 | 
				
			||||||
		.pipe(isProduction ? uglify({
 | 
							.pipe(isProduction ? uglify({
 | 
				
			||||||
			toplevel: true
 | 
								toplevel: true
 | 
				
			||||||
		} as any) : gutil.noop())
 | 
							} as any) : gutil.noop())
 | 
				
			||||||
		.pipe(gulp.dest('./built/client/assets/')) as any
 | 
							.pipe(gulp.dest('./built/client/assets/'));
 | 
				
			||||||
);
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('build:client:styles', () =>
 | 
					gulp.task('build:client:styles', () =>
 | 
				
			||||||
	gulp.src('./src/client/app/init.css')
 | 
						gulp.src('./src/client/app/init.css')
 | 
				
			||||||
| 
						 | 
					@ -201,3 +198,10 @@ gulp.task('build:client:pug', [
 | 
				
			||||||
			}))
 | 
								}))
 | 
				
			||||||
			.pipe(gulp.dest('./built/client/app/'))
 | 
								.pipe(gulp.dest('./built/client/app/'))
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('doc', () =>
 | 
				
			||||||
 | 
						gulp.src('./src/docs/**/*.styl')
 | 
				
			||||||
 | 
							.pipe(stylus())
 | 
				
			||||||
 | 
							.pipe((cssnano as any)())
 | 
				
			||||||
 | 
							.pipe(gulp.dest('./built/docs/assets/'))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -889,6 +889,7 @@ mobile/views/pages/settings/settings.profile.vue:
 | 
				
			||||||
  saved: "Profile updated"
 | 
					  saved: "Profile updated"
 | 
				
			||||||
  uploading: "Uploading"
 | 
					  uploading: "Uploading"
 | 
				
			||||||
  upload-failed: "Failed to upload"
 | 
					  upload-failed: "Failed to upload"
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
mobile/views/pages/search.vue:
 | 
					mobile/views/pages/search.vue:
 | 
				
			||||||
  search: "Search"
 | 
					  search: "Search"
 | 
				
			||||||
  empty: "No posts were found for '{}'"
 | 
					  empty: "No posts were found for '{}'"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ common:
 | 
				
			||||||
    hmm: "Hmm ... ?"
 | 
					    hmm: "Hmm ... ?"
 | 
				
			||||||
    surprise: "Wow"
 | 
					    surprise: "Wow"
 | 
				
			||||||
    congrats: "Félicitations !"
 | 
					    congrats: "Félicitations !"
 | 
				
			||||||
    angry: "En colère"
 | 
					    angry: "Faché"
 | 
				
			||||||
    confused: "Confus"
 | 
					    confused: "Confus"
 | 
				
			||||||
    pudding: "Pudding"
 | 
					    pudding: "Pudding"
 | 
				
			||||||
  note-placeholders:
 | 
					  note-placeholders:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										27
									
								
								locales/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Languages Loader
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const yaml = require('js-yaml');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const loadLang = lang => yaml.safeLoad(
 | 
				
			||||||
 | 
						fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const native = loadLang('ja');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const langs = {
 | 
				
			||||||
 | 
						'de': loadLang('de'),
 | 
				
			||||||
 | 
						'en': loadLang('en'),
 | 
				
			||||||
 | 
						'fr': loadLang('fr'),
 | 
				
			||||||
 | 
						'ja': native,
 | 
				
			||||||
 | 
						'pl': loadLang('pl'),
 | 
				
			||||||
 | 
						'es': loadLang('es')
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Object.values(langs).forEach(locale => {
 | 
				
			||||||
 | 
						// Extend native language (Japanese)
 | 
				
			||||||
 | 
						locale = Object.assign({}, native, locale);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = langs;
 | 
				
			||||||
| 
						 | 
					@ -1,34 +0,0 @@
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Languages Loader
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import * as fs from 'fs';
 | 
					 | 
				
			||||||
import * as yaml from 'js-yaml';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type LangKey = 'de' | 'en' | 'fr' | 'ja' | 'pl' | 'es';
 | 
					 | 
				
			||||||
export type LocaleObject = { [key: string]: any };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const loadLang = (lang: LangKey) => yaml.safeLoad(
 | 
					 | 
				
			||||||
	fs.readFileSync(`./locales/${lang}.yml`, 'utf-8')) as LocaleObject;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const native = loadLang('ja');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const langs: { [key: string]: LocaleObject } = {
 | 
					 | 
				
			||||||
	'de': loadLang('de'),
 | 
					 | 
				
			||||||
	'en': loadLang('en'),
 | 
					 | 
				
			||||||
	'fr': loadLang('fr'),
 | 
					 | 
				
			||||||
	'ja': native,
 | 
					 | 
				
			||||||
	'pl': loadLang('pl'),
 | 
					 | 
				
			||||||
	'es': loadLang('es')
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Object.entries(langs).map(([, locale]) => {
 | 
					 | 
				
			||||||
	// Extend native language (Japanese)
 | 
					 | 
				
			||||||
	locale = Object.assign({}, native, locale);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function isAvailableLanguage(lang: string): lang is LangKey {
 | 
					 | 
				
			||||||
	return lang in langs;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default langs;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,14 @@ common:
 | 
				
			||||||
  about-title: "A ⭐ of fediverse."
 | 
					  about-title: "A ⭐ of fediverse."
 | 
				
			||||||
  about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
 | 
					  about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  customization-tips:
 | 
				
			||||||
 | 
					    title: "カスタマイズのヒント"
 | 
				
			||||||
 | 
					    paragraph1: "ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。"
 | 
				
			||||||
 | 
					    paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。"
 | 
				
			||||||
 | 
					    paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。"
 | 
				
			||||||
 | 
					    paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
 | 
				
			||||||
 | 
					    gotit: "Got it!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  time:
 | 
					  time:
 | 
				
			||||||
    unknown: "なぞのじかん"
 | 
					    unknown: "なぞのじかん"
 | 
				
			||||||
    future: "未来"
 | 
					    future: "未来"
 | 
				
			||||||
| 
						 | 
					@ -19,6 +27,8 @@ common:
 | 
				
			||||||
    months_ago: "{}ヶ月前"
 | 
					    months_ago: "{}ヶ月前"
 | 
				
			||||||
    years_ago: "{}年前"
 | 
					    years_ago: "{}年前"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  trash: "ゴミ箱"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  weekday-short:
 | 
					  weekday-short:
 | 
				
			||||||
    sunday: "日"
 | 
					    sunday: "日"
 | 
				
			||||||
    monday: "月"
 | 
					    monday: "月"
 | 
				
			||||||
| 
						 | 
					@ -56,6 +66,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
 | 
					  my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
 | 
				
			||||||
  i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
 | 
					  i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
 | 
				
			||||||
  show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
 | 
					  show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
 | 
				
			||||||
 | 
					  verified-user: "認証済みのユーザー"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  reversi:
 | 
					  reversi:
 | 
				
			||||||
    drawn: "引き分け"
 | 
					    drawn: "引き分け"
 | 
				
			||||||
| 
						 | 
					@ -63,6 +74,7 @@ common:
 | 
				
			||||||
    opponent-turn: "相手のターンです"
 | 
					    opponent-turn: "相手のターンです"
 | 
				
			||||||
    turn-of: "{}のターンです"
 | 
					    turn-of: "{}のターンです"
 | 
				
			||||||
    past-turn-of: "{}のターン"
 | 
					    past-turn-of: "{}のターン"
 | 
				
			||||||
 | 
					    won: "{}の勝ち"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  widgets:
 | 
					  widgets:
 | 
				
			||||||
    analog-clock: "アナログ時計"
 | 
					    analog-clock: "アナログ時計"
 | 
				
			||||||
| 
						 | 
					@ -93,6 +105,7 @@ common:
 | 
				
			||||||
    widgets: "ウィジェット"
 | 
					    widgets: "ウィジェット"
 | 
				
			||||||
    home: "ホーム"
 | 
					    home: "ホーム"
 | 
				
			||||||
    local: "ローカル"
 | 
					    local: "ローカル"
 | 
				
			||||||
 | 
					    hybrid: "ソーシャル"
 | 
				
			||||||
    global: "グローバル"
 | 
					    global: "グローバル"
 | 
				
			||||||
    notifications: "通知"
 | 
					    notifications: "通知"
 | 
				
			||||||
    list: "リスト"
 | 
					    list: "リスト"
 | 
				
			||||||
| 
						 | 
					@ -280,6 +293,11 @@ common/views/widgets/memo.vue:
 | 
				
			||||||
  memo: "ここに書いて!"
 | 
					  memo: "ここに書いて!"
 | 
				
			||||||
  save: "保存"
 | 
					  save: "保存"
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					common/views/widgets/slideshow.vue:
 | 
				
			||||||
 | 
					  folder-customize-mode: "フォルダを指定するには、カスタマイズモードを終了してください"
 | 
				
			||||||
 | 
					  folder: "クリックしてフォルダを指定してください"
 | 
				
			||||||
 | 
					  no-image: "このフォルダには画像がありません"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/pages/follow.vue:
 | 
					common/views/pages/follow.vue:
 | 
				
			||||||
  signed-in-as: "{}としてサインイン中"
 | 
					  signed-in-as: "{}としてサインイン中"
 | 
				
			||||||
  following: "フォロー中"
 | 
					  following: "フォロー中"
 | 
				
			||||||
| 
						 | 
					@ -329,6 +347,8 @@ desktop/views/components/drive.file.vue:
 | 
				
			||||||
  banner: "バナー"
 | 
					  banner: "バナー"
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    rename: "名前を変更"
 | 
					    rename: "名前を変更"
 | 
				
			||||||
 | 
					    mark-as-sensitive: "閲覧注意に設定"
 | 
				
			||||||
 | 
					    unmark-as-sensitive: "閲覧注意を解除"
 | 
				
			||||||
    copy-url: "URLをコピー"
 | 
					    copy-url: "URLをコピー"
 | 
				
			||||||
    download: "ダウンロード"
 | 
					    download: "ダウンロード"
 | 
				
			||||||
    else-files: "その他..."
 | 
					    else-files: "その他..."
 | 
				
			||||||
| 
						 | 
					@ -376,6 +396,14 @@ desktop/views/components/drive.vue:
 | 
				
			||||||
    upload: "ファイルをアップロード"
 | 
					    upload: "ファイルをアップロード"
 | 
				
			||||||
    url-upload: "URLからアップロード"
 | 
					    url-upload: "URLからアップロード"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					desktop/views/components/media-image.vue:
 | 
				
			||||||
 | 
					  sensitive: "閲覧注意"
 | 
				
			||||||
 | 
					  click-to-show: "クリックして表示"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					desktop/views/components/media-video.vue:
 | 
				
			||||||
 | 
					  sensitive: "閲覧注意"
 | 
				
			||||||
 | 
					  click-to-show: "クリックして表示"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/follow-button.vue:
 | 
					desktop/views/components/follow-button.vue:
 | 
				
			||||||
  following: "フォロー中"
 | 
					  following: "フォロー中"
 | 
				
			||||||
  follow: "フォロー"
 | 
					  follow: "フォロー"
 | 
				
			||||||
| 
						 | 
					@ -440,12 +468,16 @@ desktop/views/components/notes.note.vue:
 | 
				
			||||||
desktop/views/components/notes.vue:
 | 
					desktop/views/components/notes.vue:
 | 
				
			||||||
  error: "読み込みに失敗しました。"
 | 
					  error: "読み込みに失敗しました。"
 | 
				
			||||||
  retry: "リトライ"
 | 
					  retry: "リトライ"
 | 
				
			||||||
 | 
					  load-more: "もっと読み込む"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/notifications.vue:
 | 
					desktop/views/components/notifications.vue:
 | 
				
			||||||
  more: "もっと見る"
 | 
					  more: "もっと見る"
 | 
				
			||||||
  empty: "ありません!"
 | 
					  empty: "ありません!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/post-form.vue:
 | 
					desktop/views/components/post-form.vue:
 | 
				
			||||||
 | 
					  add-visible-user: "+ユーザーを追加"
 | 
				
			||||||
 | 
					  attach-location-information: "位置情報を添付する"
 | 
				
			||||||
 | 
					  hide-contents: "内容を隠す"
 | 
				
			||||||
  reply-placeholder: "この投稿への返信..."
 | 
					  reply-placeholder: "この投稿への返信..."
 | 
				
			||||||
  quote-placeholder: "この投稿を引用..."
 | 
					  quote-placeholder: "この投稿を引用..."
 | 
				
			||||||
  submit: "投稿"
 | 
					  submit: "投稿"
 | 
				
			||||||
| 
						 | 
					@ -464,6 +496,12 @@ desktop/views/components/post-form.vue:
 | 
				
			||||||
  insert-a-kao: "v('ω')v"
 | 
					  insert-a-kao: "v('ω')v"
 | 
				
			||||||
  create-poll: "アンケートを作成"
 | 
					  create-poll: "アンケートを作成"
 | 
				
			||||||
  text-remain: "残り{}文字"
 | 
					  text-remain: "残り{}文字"
 | 
				
			||||||
 | 
					  recent-tags: "最近"
 | 
				
			||||||
 | 
					  click-to-tagging: "クリックでタグ付け"
 | 
				
			||||||
 | 
					  visibility: "公開範囲"
 | 
				
			||||||
 | 
					  geolocation-alert: "お使いの端末は位置情報に対応していません"
 | 
				
			||||||
 | 
					  error: "エラー"
 | 
				
			||||||
 | 
					  enter-username: "ユーザー名を入力してください"
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
desktop/views/components/post-form-window.vue:
 | 
					desktop/views/components/post-form-window.vue:
 | 
				
			||||||
  note: "新規投稿"
 | 
					  note: "新規投稿"
 | 
				
			||||||
| 
						 | 
					@ -512,6 +550,8 @@ desktop/views/components/settings.vue:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  display: "デザインと表示"
 | 
					  display: "デザインと表示"
 | 
				
			||||||
  customize: "ホームをカスタマイズ"
 | 
					  customize: "ホームをカスタマイズ"
 | 
				
			||||||
 | 
					  choose-wallpaper: "壁紙を選択"
 | 
				
			||||||
 | 
					  delete-wallpaper: "壁紙を削除"
 | 
				
			||||||
  dark-mode: "ダークモード"
 | 
					  dark-mode: "ダークモード"
 | 
				
			||||||
  circle-icons: "円形のアイコンを使用"
 | 
					  circle-icons: "円形のアイコンを使用"
 | 
				
			||||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
					  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
				
			||||||
| 
						 | 
					@ -621,8 +661,12 @@ desktop/views/components/settings.profile.vue:
 | 
				
			||||||
  description: "自己紹介"
 | 
					  description: "自己紹介"
 | 
				
			||||||
  birthday: "誕生日"
 | 
					  birthday: "誕生日"
 | 
				
			||||||
  save: "保存"
 | 
					  save: "保存"
 | 
				
			||||||
 | 
					  locked-account: "アカウントの保護"
 | 
				
			||||||
 | 
					  is-locked: "投稿を非公開にする"
 | 
				
			||||||
 | 
					  other: "その他"
 | 
				
			||||||
  is-bot: "このアカウントはBotです"
 | 
					  is-bot: "このアカウントはBotです"
 | 
				
			||||||
  is-cat: "このアカウントはCatです"
 | 
					  is-cat: "このアカウントはCatです"
 | 
				
			||||||
 | 
					  profile-updated: "プロフィールを更新しました"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/sub-note-content.vue:
 | 
					desktop/views/components/sub-note-content.vue:
 | 
				
			||||||
  private: "この投稿は非公開です"
 | 
					  private: "この投稿は非公開です"
 | 
				
			||||||
| 
						 | 
					@ -636,6 +680,7 @@ desktop/views/components/taskmanager.vue:
 | 
				
			||||||
desktop/views/components/timeline.vue:
 | 
					desktop/views/components/timeline.vue:
 | 
				
			||||||
  home: "ホーム"
 | 
					  home: "ホーム"
 | 
				
			||||||
  local: "ローカル"
 | 
					  local: "ローカル"
 | 
				
			||||||
 | 
					  hybrid: "ソーシャル"
 | 
				
			||||||
  global: "グローバル"
 | 
					  global: "グローバル"
 | 
				
			||||||
  list: "リスト"
 | 
					  list: "リスト"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -648,7 +693,7 @@ desktop/views/components/ui.header.account.vue:
 | 
				
			||||||
  favorites: "お気に入り"
 | 
					  favorites: "お気に入り"
 | 
				
			||||||
  lists: "リスト"
 | 
					  lists: "リスト"
 | 
				
			||||||
  follow-requests: "フォロー申請"
 | 
					  follow-requests: "フォロー申請"
 | 
				
			||||||
  customize: "カスタマイズ"
 | 
					  customize: "ホームのカスタマイズ"
 | 
				
			||||||
  settings: "設定"
 | 
					  settings: "設定"
 | 
				
			||||||
  signout: "サインアウト"
 | 
					  signout: "サインアウト"
 | 
				
			||||||
  dark: "闇に飲まれる"
 | 
					  dark: "闇に飲まれる"
 | 
				
			||||||
| 
						 | 
					@ -698,6 +743,7 @@ desktop/views/components/window.vue:
 | 
				
			||||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
					desktop/views/pages/deck/deck.tl-column.vue:
 | 
				
			||||||
  is-media-only: "メディア投稿のみ"
 | 
					  is-media-only: "メディア投稿のみ"
 | 
				
			||||||
  is-media-view: "メディアビュー"
 | 
					  is-media-view: "メディアビュー"
 | 
				
			||||||
 | 
					  edit: "オプション"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/pages/deck/deck.note.vue:
 | 
					desktop/views/pages/deck/deck.note.vue:
 | 
				
			||||||
  reposted-by: "{}がRenote"
 | 
					  reposted-by: "{}がRenote"
 | 
				
			||||||
| 
						 | 
					@ -844,6 +890,14 @@ mobile/views/components/drive.file-detail.vue:
 | 
				
			||||||
  hash: "ハッシュ (md5)"
 | 
					  hash: "ハッシュ (md5)"
 | 
				
			||||||
  exif: "EXIF"
 | 
					  exif: "EXIF"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mobile/views/components/media-image.vue:
 | 
				
			||||||
 | 
					  sensitive: "閲覧注意"
 | 
				
			||||||
 | 
					  click-to-show: "クリックして表示"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mobile/views/components/media-video.vue:
 | 
				
			||||||
 | 
					  sensitive: "閲覧注意"
 | 
				
			||||||
 | 
					  click-to-show: "クリックして表示"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/components/follow-button.vue:
 | 
					mobile/views/components/follow-button.vue:
 | 
				
			||||||
  following: "フォロー中"
 | 
					  following: "フォロー中"
 | 
				
			||||||
  follow: "フォロー"
 | 
					  follow: "フォロー"
 | 
				
			||||||
| 
						 | 
					@ -958,6 +1012,7 @@ mobile/views/pages/following.vue:
 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
  home: "ホーム"
 | 
					  home: "ホーム"
 | 
				
			||||||
  local: "ローカル"
 | 
					  local: "ローカル"
 | 
				
			||||||
 | 
					  hybrid: "ソーシャル"
 | 
				
			||||||
  global: "グローバル"
 | 
					  global: "グローバル"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/pages/messaging.vue:
 | 
					mobile/views/pages/messaging.vue:
 | 
				
			||||||
| 
						 | 
					@ -1088,11 +1143,17 @@ docs:
 | 
				
			||||||
      properties: "プロパティ"
 | 
					      properties: "プロパティ"
 | 
				
			||||||
    endpoints:
 | 
					    endpoints:
 | 
				
			||||||
      params: "パラメータ"
 | 
					      params: "パラメータ"
 | 
				
			||||||
 | 
					      no-params: "パラメータはありません"
 | 
				
			||||||
      res: "レスポンス"
 | 
					      res: "レスポンス"
 | 
				
			||||||
 | 
					      require-credential: "このエンドポイントは認証情報が必須です。"
 | 
				
			||||||
 | 
					      require-permission: "このエンドポイントは{permission}の権限を必要とします。"
 | 
				
			||||||
 | 
					      has-limit: "レートリミットがあります。"
 | 
				
			||||||
 | 
					      duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
 | 
				
			||||||
 | 
					      min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
 | 
				
			||||||
 | 
					      show-src: "このエンドポイントのソースコードも閲覧できます。"
 | 
				
			||||||
 | 
					      show-src-link: "コードをGitHubで見る"
 | 
				
			||||||
 | 
					      generated: "このドキュメントはAPI定義に基づき自動生成されています。"
 | 
				
			||||||
    props:
 | 
					    props:
 | 
				
			||||||
      name: "名前"
 | 
					      name: "名前"
 | 
				
			||||||
      type: "型"
 | 
					      type: "型"
 | 
				
			||||||
      optional: "オプション"
 | 
					 | 
				
			||||||
      description: "説明"
 | 
					      description: "説明"
 | 
				
			||||||
      yes: "はい"
 | 
					 | 
				
			||||||
      no: "いいえ"
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
Misskeyの破壊的変更に対応するいくつかのスニペットがあります。
 | 
					 | 
				
			||||||
MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。
 | 
					 | 
				
			||||||
ファイル名が `shell.` から始まるものは前者、 `node.` から始まるものは後者です。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
nodeで実行するいくつかのスニペットは、並列処理させる数を引数で設定できるものがあります。
 | 
					 | 
				
			||||||
処理中にエラーで落ちる場合は、メモリが足りていない可能性があるので、少ない数に設定してみてください。
 | 
					 | 
				
			||||||
※デフォルトは`5`です。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ファイルを作成する際は `../init-migration-file.sh -t _type_ -n _name_` を実行すると _type_._unixtime_._name_.js が生成されます
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,37 +0,0 @@
 | 
				
			||||||
#!/bin/bash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
usage() {
 | 
					 | 
				
			||||||
		echo "$0 [-t type] [-n name]"
 | 
					 | 
				
			||||||
		echo "  type: [node | shell]"
 | 
					 | 
				
			||||||
		echo "  name: if no present, set untitled"
 | 
					 | 
				
			||||||
		exit 0
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
while getopts :t:n:h OPT
 | 
					 | 
				
			||||||
do
 | 
					 | 
				
			||||||
	case $OPT in
 | 
					 | 
				
			||||||
		t)	type=$OPTARG
 | 
					 | 
				
			||||||
				;;
 | 
					 | 
				
			||||||
		n)	name=$OPTARG
 | 
					 | 
				
			||||||
				;;
 | 
					 | 
				
			||||||
		h)	usage
 | 
					 | 
				
			||||||
				;;
 | 
					 | 
				
			||||||
		\?) usage
 | 
					 | 
				
			||||||
				;;
 | 
					 | 
				
			||||||
		:)	usage
 | 
					 | 
				
			||||||
				;;
 | 
					 | 
				
			||||||
	esac
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [ "$type" = "" ]
 | 
					 | 
				
			||||||
then
 | 
					 | 
				
			||||||
	echo "no type present!!!"
 | 
					 | 
				
			||||||
	usage
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [ "$name" = "" ]
 | 
					 | 
				
			||||||
then
 | 
					 | 
				
			||||||
	name="untitled"
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
touch "$(realpath $(dirname $BASH_SOURCE))/migration/$type.$(date +%s).$name.js"
 | 
					 | 
				
			||||||
							
								
								
									
										100
									
								
								package.json
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,21 +1,18 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "4.15.0",
 | 
						"version": "5.8.0",
 | 
				
			||||||
	"clientVersion": "1.0.6878",
 | 
						"clientVersion": "1.0.7664",
 | 
				
			||||||
	"codename": "nighthike",
 | 
						"codename": "nighthike",
 | 
				
			||||||
	"main": "./built/index.js",
 | 
						"main": "./built/index.js",
 | 
				
			||||||
	"private": true,
 | 
						"private": true,
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"config": "node ./cli/init.js",
 | 
					 | 
				
			||||||
		"start": "node ./built",
 | 
							"start": "node ./built",
 | 
				
			||||||
		"debug": "DEBUG=misskey:* node ./built",
 | 
							"debug": "DEBUG=misskey:* node ./built",
 | 
				
			||||||
		"swagger": "node ./swagger.js",
 | 
					 | 
				
			||||||
		"build": "webpack && gulp build",
 | 
							"build": "webpack && gulp build",
 | 
				
			||||||
		"webpack": "webpack",
 | 
							"webpack": "webpack",
 | 
				
			||||||
		"watch": "webpack --watch",
 | 
							"watch": "webpack --watch",
 | 
				
			||||||
		"gulp": "gulp build",
 | 
							"gulp": "gulp build",
 | 
				
			||||||
		"rebuild": "gulp rebuild",
 | 
					 | 
				
			||||||
		"clean": "gulp clean",
 | 
							"clean": "gulp clean",
 | 
				
			||||||
		"cleanall": "gulp cleanall",
 | 
							"cleanall": "gulp cleanall",
 | 
				
			||||||
		"lint": "gulp lint",
 | 
							"lint": "gulp lint",
 | 
				
			||||||
| 
						 | 
					@ -27,15 +24,15 @@
 | 
				
			||||||
		"@fortawesome/fontawesome-free-brands": "5.0.13",
 | 
							"@fortawesome/fontawesome-free-brands": "5.0.13",
 | 
				
			||||||
		"@fortawesome/fontawesome-free-regular": "5.0.13",
 | 
							"@fortawesome/fontawesome-free-regular": "5.0.13",
 | 
				
			||||||
		"@fortawesome/fontawesome-free-solid": "5.0.13",
 | 
							"@fortawesome/fontawesome-free-solid": "5.0.13",
 | 
				
			||||||
		"@koa/cors": "2.2.1",
 | 
							"@koa/cors": "2.2.2",
 | 
				
			||||||
		"@prezzemolo/rap": "0.1.2",
 | 
							"@prezzemolo/rap": "0.1.2",
 | 
				
			||||||
		"@prezzemolo/zip": "0.0.3",
 | 
							"@prezzemolo/zip": "0.0.3",
 | 
				
			||||||
		"@types/bcryptjs": "2.4.1",
 | 
							"@types/bcryptjs": "2.4.1",
 | 
				
			||||||
 | 
							"@types/dateformat": "1.0.1",
 | 
				
			||||||
		"@types/debug": "0.0.30",
 | 
							"@types/debug": "0.0.30",
 | 
				
			||||||
		"@types/deep-equal": "1.0.1",
 | 
							"@types/deep-equal": "1.0.1",
 | 
				
			||||||
		"@types/elasticsearch": "5.0.24",
 | 
							"@types/elasticsearch": "5.0.25",
 | 
				
			||||||
		"@types/file-type": "5.2.1",
 | 
							"@types/file-type": "5.2.1",
 | 
				
			||||||
		"@types/gm": "1.18.0",
 | 
					 | 
				
			||||||
		"@types/gulp": "3.8.36",
 | 
							"@types/gulp": "3.8.36",
 | 
				
			||||||
		"@types/gulp-htmlmin": "1.3.32",
 | 
							"@types/gulp-htmlmin": "1.3.32",
 | 
				
			||||||
		"@types/gulp-mocha": "0.0.32",
 | 
							"@types/gulp-mocha": "0.0.32",
 | 
				
			||||||
| 
						 | 
					@ -43,31 +40,29 @@
 | 
				
			||||||
		"@types/gulp-replace": "0.0.31",
 | 
							"@types/gulp-replace": "0.0.31",
 | 
				
			||||||
		"@types/gulp-uglify": "3.0.5",
 | 
							"@types/gulp-uglify": "3.0.5",
 | 
				
			||||||
		"@types/gulp-util": "3.0.34",
 | 
							"@types/gulp-util": "3.0.34",
 | 
				
			||||||
		"@types/inquirer": "0.0.42",
 | 
					 | 
				
			||||||
		"@types/is-root": "1.0.0",
 | 
							"@types/is-root": "1.0.0",
 | 
				
			||||||
		"@types/is-url": "1.2.28",
 | 
							"@types/is-url": "1.2.28",
 | 
				
			||||||
		"@types/js-yaml": "3.11.1",
 | 
							"@types/js-yaml": "3.11.2",
 | 
				
			||||||
		"@types/jsdom": "11.0.6",
 | 
							"@types/jsdom": "11.0.6",
 | 
				
			||||||
		"@types/koa": "2.0.46",
 | 
							"@types/koa": "2.0.46",
 | 
				
			||||||
		"@types/koa-bodyparser": "5.0.0",
 | 
							"@types/koa-bodyparser": "5.0.1",
 | 
				
			||||||
		"@types/koa-compress": "2.0.8",
 | 
							"@types/koa-compress": "2.0.8",
 | 
				
			||||||
		"@types/koa-favicon": "2.0.19",
 | 
							"@types/koa-favicon": "2.0.19",
 | 
				
			||||||
		"@types/koa-logger": "3.1.0",
 | 
							"@types/koa-logger": "3.1.0",
 | 
				
			||||||
		"@types/koa-mount": "3.0.1",
 | 
							"@types/koa-mount": "3.0.1",
 | 
				
			||||||
		"@types/koa-multer": "1.0.0",
 | 
							"@types/koa-multer": "1.0.0",
 | 
				
			||||||
		"@types/koa-router": "7.0.30",
 | 
							"@types/koa-router": "7.0.31",
 | 
				
			||||||
		"@types/koa-send": "4.1.1",
 | 
							"@types/koa-send": "4.1.1",
 | 
				
			||||||
		"@types/koa-views": "2.0.3",
 | 
							"@types/koa-views": "2.0.3",
 | 
				
			||||||
		"@types/koa__cors": "2.2.2",
 | 
							"@types/koa__cors": "2.2.3",
 | 
				
			||||||
		"@types/kue": "0.11.9",
 | 
							"@types/minio": "6.0.2",
 | 
				
			||||||
		"@types/license-checker": "15.0.0",
 | 
					 | 
				
			||||||
		"@types/mkdirp": "0.5.2",
 | 
							"@types/mkdirp": "0.5.2",
 | 
				
			||||||
		"@types/mocha": "5.2.3",
 | 
							"@types/mocha": "5.2.3",
 | 
				
			||||||
		"@types/mongodb": "3.1.0",
 | 
							"@types/mongodb": "3.1.2",
 | 
				
			||||||
		"@types/ms": "0.7.30",
 | 
							"@types/ms": "0.7.30",
 | 
				
			||||||
		"@types/node": "10.5.1",
 | 
							"@types/node": "10.5.4",
 | 
				
			||||||
		"@types/nopt": "3.0.29",
 | 
					 | 
				
			||||||
		"@types/parse5": "5.0.0",
 | 
							"@types/parse5": "5.0.0",
 | 
				
			||||||
 | 
							"@types/portscanner": "2.1.0",
 | 
				
			||||||
		"@types/pug": "2.0.4",
 | 
							"@types/pug": "2.0.4",
 | 
				
			||||||
		"@types/qrcode": "1.2.0",
 | 
							"@types/qrcode": "1.2.0",
 | 
				
			||||||
		"@types/ratelimiter": "2.1.28",
 | 
							"@types/ratelimiter": "2.1.28",
 | 
				
			||||||
| 
						 | 
					@ -76,11 +71,14 @@
 | 
				
			||||||
		"@types/request-promise-native": "1.0.15",
 | 
							"@types/request-promise-native": "1.0.15",
 | 
				
			||||||
		"@types/rimraf": "2.0.2",
 | 
							"@types/rimraf": "2.0.2",
 | 
				
			||||||
		"@types/seedrandom": "2.4.27",
 | 
							"@types/seedrandom": "2.4.27",
 | 
				
			||||||
 | 
							"@types/sharp": "0.17.9",
 | 
				
			||||||
 | 
							"@types/showdown": "1.7.5",
 | 
				
			||||||
		"@types/single-line-log": "1.1.0",
 | 
							"@types/single-line-log": "1.1.0",
 | 
				
			||||||
		"@types/speakeasy": "2.0.2",
 | 
							"@types/speakeasy": "2.0.2",
 | 
				
			||||||
 | 
							"@types/systeminformation": "3.23.0",
 | 
				
			||||||
		"@types/tmp": "0.0.33",
 | 
							"@types/tmp": "0.0.33",
 | 
				
			||||||
		"@types/uuid": "3.4.3",
 | 
							"@types/uuid": "3.4.3",
 | 
				
			||||||
		"@types/webpack": "4.4.4",
 | 
							"@types/webpack": "4.4.8",
 | 
				
			||||||
		"@types/webpack-stream": "3.2.10",
 | 
							"@types/webpack-stream": "3.2.10",
 | 
				
			||||||
		"@types/websocket": "0.0.39",
 | 
							"@types/websocket": "0.0.39",
 | 
				
			||||||
		"@types/ws": "5.1.2",
 | 
							"@types/ws": "5.1.2",
 | 
				
			||||||
| 
						 | 
					@ -88,51 +86,54 @@
 | 
				
			||||||
		"autosize": "4.0.2",
 | 
							"autosize": "4.0.2",
 | 
				
			||||||
		"autwh": "0.1.0",
 | 
							"autwh": "0.1.0",
 | 
				
			||||||
		"bcryptjs": "2.4.3",
 | 
							"bcryptjs": "2.4.3",
 | 
				
			||||||
 | 
							"bee-queue": "1.2.2",
 | 
				
			||||||
		"bootstrap-vue": "2.0.0-rc.11",
 | 
							"bootstrap-vue": "2.0.0-rc.11",
 | 
				
			||||||
		"cafy": "8.0.0",
 | 
							"cafy": "11.3.0",
 | 
				
			||||||
		"chalk": "2.4.1",
 | 
							"chalk": "2.4.1",
 | 
				
			||||||
 | 
							"commander": "2.16.0",
 | 
				
			||||||
		"crc-32": "1.2.0",
 | 
							"crc-32": "1.2.0",
 | 
				
			||||||
		"css-loader": "0.28.11",
 | 
							"css-loader": "1.0.0",
 | 
				
			||||||
 | 
							"dateformat": "3.0.3",
 | 
				
			||||||
		"debug": "3.1.0",
 | 
							"debug": "3.1.0",
 | 
				
			||||||
		"deep-equal": "1.0.1",
 | 
							"deep-equal": "1.0.1",
 | 
				
			||||||
		"deepcopy": "0.6.3",
 | 
							"deepcopy": "0.6.3",
 | 
				
			||||||
		"diskusage": "0.2.4",
 | 
							"diskusage": "0.2.4",
 | 
				
			||||||
		"dompurify": "1.0.5",
 | 
							"dompurify": "1.0.5",
 | 
				
			||||||
		"elasticsearch": "15.0.0",
 | 
							"elasticsearch": "15.1.1",
 | 
				
			||||||
		"element-ui": "2.4.2",
 | 
							"element-ui": "2.4.5",
 | 
				
			||||||
		"emojilib": "2.2.12",
 | 
							"emojilib": "2.3.0",
 | 
				
			||||||
		"escape-regexp": "0.0.1",
 | 
							"escape-regexp": "0.0.1",
 | 
				
			||||||
		"eslint": "5.0.1",
 | 
							"eslint": "5.0.1",
 | 
				
			||||||
		"eslint-plugin-vue": "4.5.0",
 | 
							"eslint-plugin-vue": "4.7.1",
 | 
				
			||||||
		"eventemitter3": "3.1.0",
 | 
							"eventemitter3": "3.1.0",
 | 
				
			||||||
		"exif-js": "2.3.0",
 | 
							"exif-js": "2.3.0",
 | 
				
			||||||
		"file-loader": "1.1.11",
 | 
							"file-loader": "1.1.11",
 | 
				
			||||||
		"file-type": "8.0.0",
 | 
							"file-type": "8.1.0",
 | 
				
			||||||
		"fuckadblock": "3.2.1",
 | 
							"fuckadblock": "3.2.1",
 | 
				
			||||||
		"gm": "1.23.1",
 | 
					 | 
				
			||||||
		"gulp": "3.9.1",
 | 
							"gulp": "3.9.1",
 | 
				
			||||||
		"gulp-cssnano": "2.1.3",
 | 
							"gulp-cssnano": "2.1.3",
 | 
				
			||||||
		"gulp-htmlmin": "4.0.0",
 | 
							"gulp-htmlmin": "4.0.0",
 | 
				
			||||||
		"gulp-imagemin": "4.1.0",
 | 
							"gulp-imagemin": "4.1.0",
 | 
				
			||||||
		"gulp-mocha": "6.0.0",
 | 
							"gulp-mocha": "6.0.0",
 | 
				
			||||||
		"gulp-pug": "4.0.1",
 | 
							"gulp-pug": "4.0.1",
 | 
				
			||||||
		"gulp-rename": "1.3.0",
 | 
							"gulp-rename": "1.4.0",
 | 
				
			||||||
		"gulp-replace": "1.0.0",
 | 
							"gulp-replace": "1.0.0",
 | 
				
			||||||
		"gulp-sourcemaps": "2.6.4",
 | 
							"gulp-sourcemaps": "2.6.4",
 | 
				
			||||||
		"gulp-stylus": "2.7.0",
 | 
							"gulp-stylus": "2.7.0",
 | 
				
			||||||
		"gulp-tslint": "8.1.3",
 | 
							"gulp-tslint": "8.1.3",
 | 
				
			||||||
		"gulp-typescript": "4.0.2",
 | 
							"gulp-typescript": "4.0.2",
 | 
				
			||||||
		"gulp-uglify": "3.0.0",
 | 
							"gulp-uglify": "3.0.1",
 | 
				
			||||||
		"gulp-util": "3.0.8",
 | 
							"gulp-util": "3.0.8",
 | 
				
			||||||
		"hard-source-webpack-plugin": "0.10.1",
 | 
							"hard-source-webpack-plugin": "0.12.0",
 | 
				
			||||||
		"highlight.js": "9.12.0",
 | 
							"highlight.js": "9.12.0",
 | 
				
			||||||
		"html-minifier": "3.5.17",
 | 
							"html-minifier": "3.5.19",
 | 
				
			||||||
		"http-signature": "1.2.0",
 | 
							"http-signature": "1.2.0",
 | 
				
			||||||
		"inquirer": "6.0.0",
 | 
							"insert-text-at-cursor": "0.1.1",
 | 
				
			||||||
		"is-root": "2.0.0",
 | 
							"is-root": "2.0.0",
 | 
				
			||||||
		"is-url": "1.2.4",
 | 
							"is-url": "1.2.4",
 | 
				
			||||||
 | 
							"jquery": "3.3.1",
 | 
				
			||||||
		"js-yaml": "3.12.0",
 | 
							"js-yaml": "3.12.0",
 | 
				
			||||||
		"jsdom": "11.11.0",
 | 
							"jsdom": "11.12.0",
 | 
				
			||||||
		"koa": "2.5.1",
 | 
							"koa": "2.5.1",
 | 
				
			||||||
		"koa-bodyparser": "4.2.1",
 | 
							"koa-bodyparser": "4.2.1",
 | 
				
			||||||
		"koa-compress": "3.0.0",
 | 
							"koa-compress": "3.0.0",
 | 
				
			||||||
| 
						 | 
					@ -145,32 +146,30 @@
 | 
				
			||||||
		"koa-send": "5.0.0",
 | 
							"koa-send": "5.0.0",
 | 
				
			||||||
		"koa-slow": "2.1.0",
 | 
							"koa-slow": "2.1.0",
 | 
				
			||||||
		"koa-views": "6.1.4",
 | 
							"koa-views": "6.1.4",
 | 
				
			||||||
		"kue": "0.11.6",
 | 
					 | 
				
			||||||
		"license-checker": "20.1.0",
 | 
					 | 
				
			||||||
		"loader-utils": "1.1.0",
 | 
							"loader-utils": "1.1.0",
 | 
				
			||||||
		"mecab-async": "0.1.2",
 | 
							"mecab-async": "0.1.2",
 | 
				
			||||||
 | 
							"minio": "6.0.0",
 | 
				
			||||||
		"mkdirp": "0.5.1",
 | 
							"mkdirp": "0.5.1",
 | 
				
			||||||
		"mocha": "5.2.0",
 | 
							"mocha": "5.2.0",
 | 
				
			||||||
		"moji": "0.5.1",
 | 
							"moji": "0.5.1",
 | 
				
			||||||
		"mongodb": "3.1.0",
 | 
							"mongodb": "3.1.1",
 | 
				
			||||||
		"monk": "6.0.6",
 | 
							"monk": "6.0.6",
 | 
				
			||||||
		"ms": "2.1.1",
 | 
							"ms": "2.1.1",
 | 
				
			||||||
		"nan": "2.10.0",
 | 
							"nan": "2.10.0",
 | 
				
			||||||
		"node-sass": "4.9.0",
 | 
							"node-sass": "4.9.2",
 | 
				
			||||||
		"node-sass-json-importer": "3.3.1",
 | 
							"node-sass-json-importer": "3.3.1",
 | 
				
			||||||
		"nopt": "4.0.1",
 | 
					 | 
				
			||||||
		"nprogress": "0.2.0",
 | 
							"nprogress": "0.2.0",
 | 
				
			||||||
		"object-assign-deep": "0.4.0",
 | 
							"object-assign-deep": "0.4.0",
 | 
				
			||||||
		"on-build-webpack": "0.1.0",
 | 
							"on-build-webpack": "0.1.0",
 | 
				
			||||||
		"os-utils": "0.0.14",
 | 
							"os-utils": "0.0.14",
 | 
				
			||||||
		"parse5": "5.0.0",
 | 
							"parse5": "5.0.0",
 | 
				
			||||||
 | 
							"portscanner": "2.2.0",
 | 
				
			||||||
		"progress-bar-webpack-plugin": "1.11.0",
 | 
							"progress-bar-webpack-plugin": "1.11.0",
 | 
				
			||||||
		"prominence": "0.2.0",
 | 
					 | 
				
			||||||
		"promise-sequential": "1.1.1",
 | 
							"promise-sequential": "1.1.1",
 | 
				
			||||||
		"pug": "2.0.3",
 | 
							"pug": "2.0.3",
 | 
				
			||||||
		"punycode": "2.1.1",
 | 
							"punycode": "2.1.1",
 | 
				
			||||||
		"qrcode": "1.2.0",
 | 
							"qrcode": "1.2.2",
 | 
				
			||||||
		"ratelimiter": "3.1.0",
 | 
							"ratelimiter": "3.2.0",
 | 
				
			||||||
		"recaptcha-promise": "0.1.3",
 | 
							"recaptcha-promise": "0.1.3",
 | 
				
			||||||
		"reconnecting-websocket": "3.2.2",
 | 
							"reconnecting-websocket": "3.2.2",
 | 
				
			||||||
		"redis": "2.8.0",
 | 
							"redis": "2.8.0",
 | 
				
			||||||
| 
						 | 
					@ -181,22 +180,24 @@
 | 
				
			||||||
		"s-age": "1.1.2",
 | 
							"s-age": "1.1.2",
 | 
				
			||||||
		"sass-loader": "7.0.3",
 | 
							"sass-loader": "7.0.3",
 | 
				
			||||||
		"seedrandom": "2.4.3",
 | 
							"seedrandom": "2.4.3",
 | 
				
			||||||
 | 
							"sharp": "0.20.5",
 | 
				
			||||||
 | 
							"showdown": "1.8.6",
 | 
				
			||||||
 | 
							"showdown-highlightjs-extension": "0.1.2",
 | 
				
			||||||
		"single-line-log": "1.1.2",
 | 
							"single-line-log": "1.1.2",
 | 
				
			||||||
		"speakeasy": "2.0.0",
 | 
							"speakeasy": "2.0.0",
 | 
				
			||||||
		"style-loader": "0.21.0",
 | 
							"style-loader": "0.21.0",
 | 
				
			||||||
		"stylus": "0.54.5",
 | 
							"stylus": "0.54.5",
 | 
				
			||||||
		"stylus-loader": "3.0.2",
 | 
							"stylus-loader": "3.0.2",
 | 
				
			||||||
		"summaly": "2.0.6",
 | 
							"summaly": "2.0.6",
 | 
				
			||||||
		"swagger-jsdoc": "1.9.7",
 | 
							"systeminformation": "3.42.4",
 | 
				
			||||||
		"syuilo-password-strength": "0.0.1",
 | 
							"syuilo-password-strength": "0.0.1",
 | 
				
			||||||
		"tcp-port-used": "0.1.2",
 | 
					 | 
				
			||||||
		"textarea-caret": "3.1.0",
 | 
							"textarea-caret": "3.1.0",
 | 
				
			||||||
		"tmp": "0.0.33",
 | 
							"tmp": "0.0.33",
 | 
				
			||||||
		"ts-loader": "4.4.1",
 | 
							"ts-loader": "4.4.1",
 | 
				
			||||||
		"ts-node": "7.0.0",
 | 
							"ts-node": "7.0.0",
 | 
				
			||||||
		"tslint": "5.10.0",
 | 
							"tslint": "5.10.0",
 | 
				
			||||||
		"typescript": "2.9.2",
 | 
							"typescript": "2.9.2",
 | 
				
			||||||
		"typescript-eslint-parser": "16.0.1",
 | 
							"typescript-eslint-parser": "17.0.1",
 | 
				
			||||||
		"uglify-es": "3.3.9",
 | 
							"uglify-es": "3.3.9",
 | 
				
			||||||
		"url-loader": "1.0.1",
 | 
							"url-loader": "1.0.1",
 | 
				
			||||||
		"uuid": "3.3.2",
 | 
							"uuid": "3.3.2",
 | 
				
			||||||
| 
						 | 
					@ -205,18 +206,19 @@
 | 
				
			||||||
		"vue-cropperjs": "2.2.1",
 | 
							"vue-cropperjs": "2.2.1",
 | 
				
			||||||
		"vue-js-modal": "1.3.16",
 | 
							"vue-js-modal": "1.3.16",
 | 
				
			||||||
		"vue-json-tree-view": "2.1.4",
 | 
							"vue-json-tree-view": "2.1.4",
 | 
				
			||||||
		"vue-loader": "15.2.4",
 | 
							"vue-loader": "15.2.6",
 | 
				
			||||||
		"vue-router": "3.0.1",
 | 
							"vue-router": "3.0.1",
 | 
				
			||||||
 | 
							"vue-style-loader": "4.1.1",
 | 
				
			||||||
		"vue-template-compiler": "2.5.16",
 | 
							"vue-template-compiler": "2.5.16",
 | 
				
			||||||
		"vuedraggable": "2.16.0",
 | 
							"vuedraggable": "2.16.0",
 | 
				
			||||||
		"vuex": "3.0.1",
 | 
							"vuex": "3.0.1",
 | 
				
			||||||
		"vuex-persistedstate": "^2.5.4",
 | 
							"vuex-persistedstate": "2.5.4",
 | 
				
			||||||
		"web-push": "3.3.2",
 | 
							"web-push": "3.3.2",
 | 
				
			||||||
		"webfinger.js": "2.6.6",
 | 
							"webfinger.js": "2.6.6",
 | 
				
			||||||
		"webpack": "4.14.0",
 | 
							"webpack": "4.16.3",
 | 
				
			||||||
		"webpack-cli": "3.0.8",
 | 
							"webpack-cli": "3.1.0",
 | 
				
			||||||
		"websocket": "1.0.26",
 | 
							"websocket": "1.0.26",
 | 
				
			||||||
		"ws": "5.2.1",
 | 
							"ws": "6.0.0",
 | 
				
			||||||
		"xev": "2.0.1"
 | 
							"xev": "2.0.1"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"greenkeeper": {
 | 
						"greenkeeper": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
import { IUser } from '../models/user';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default (user: IUser) => {
 | 
					 | 
				
			||||||
	return user.host === null ? user.username : `${user.username}@${user.host}`;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<div class="form">
 | 
					<div class="form">
 | 
				
			||||||
	<header>
 | 
						<header>
 | 
				
			||||||
		<h1><i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?</h1>
 | 
							<h1><i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?</h1>
 | 
				
			||||||
		<img :src="`${app.iconUrl}?thumbnail&size=64`"/>
 | 
							<img :src="app.iconUrl"/>
 | 
				
			||||||
	</header>
 | 
						</header>
 | 
				
			||||||
	<div class="app">
 | 
						<div class="app">
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import getNoteSummary from '../../../../renderers/get-note-summary';
 | 
					import getNoteSummary from '../../../../misc/get-note-summary';
 | 
				
			||||||
import getReactionEmoji from '../../../../renderers/get-reaction-emoji';
 | 
					import getReactionEmoji from '../../../../misc/get-reaction-emoji';
 | 
				
			||||||
import getUserName from '../../../../renderers/get-user-name';
 | 
					import getUserName from '../../../../misc/get-user-name';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Notification = {
 | 
					type Notification = {
 | 
				
			||||||
	title: string;
 | 
						title: string;
 | 
				
			||||||
| 
						 | 
					@ -17,21 +17,21 @@ export default function(type, data): Notification {
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				title: 'ファイルがアップロードされました',
 | 
									title: 'ファイルがアップロードされました',
 | 
				
			||||||
				body: data.name,
 | 
									body: data.name,
 | 
				
			||||||
				icon: data.url + '?thumbnail&size=64'
 | 
									icon: data.url
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 'unread_messaging_message':
 | 
							case 'unread_messaging_message':
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				title: `${getUserName(data.user)}さんからメッセージ:`,
 | 
									title: `${getUserName(data.user)}さんからメッセージ:`,
 | 
				
			||||||
				body: data.text, // TODO: getMessagingMessageSummary(data),
 | 
									body: data.text, // TODO: getMessagingMessageSummary(data),
 | 
				
			||||||
				icon: data.user.avatarUrl + '?thumbnail&size=64'
 | 
									icon: data.user.avatarUrl
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 'reversi_invited':
 | 
							case 'reversi_invited':
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				title: '対局への招待があります',
 | 
									title: '対局への招待があります',
 | 
				
			||||||
				body: `${getUserName(data.parent)}さんから`,
 | 
									body: `${getUserName(data.parent)}さんから`,
 | 
				
			||||||
				icon: data.parent.avatarUrl + '?thumbnail&size=64'
 | 
									icon: data.parent.avatarUrl
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 'notification':
 | 
							case 'notification':
 | 
				
			||||||
| 
						 | 
					@ -40,28 +40,28 @@ export default function(type, data): Notification {
 | 
				
			||||||
					return {
 | 
										return {
 | 
				
			||||||
						title: `${getUserName(data.user)}さんから:`,
 | 
											title: `${getUserName(data.user)}さんから:`,
 | 
				
			||||||
						body: getNoteSummary(data),
 | 
											body: getNoteSummary(data),
 | 
				
			||||||
						icon: data.user.avatarUrl + '?thumbnail&size=64'
 | 
											icon: data.user.avatarUrl
 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 'reply':
 | 
									case 'reply':
 | 
				
			||||||
					return {
 | 
										return {
 | 
				
			||||||
						title: `${getUserName(data.user)}さんから返信:`,
 | 
											title: `${getUserName(data.user)}さんから返信:`,
 | 
				
			||||||
						body: getNoteSummary(data),
 | 
											body: getNoteSummary(data),
 | 
				
			||||||
						icon: data.user.avatarUrl + '?thumbnail&size=64'
 | 
											icon: data.user.avatarUrl
 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 'quote':
 | 
									case 'quote':
 | 
				
			||||||
					return {
 | 
										return {
 | 
				
			||||||
						title: `${getUserName(data.user)}さんが引用:`,
 | 
											title: `${getUserName(data.user)}さんが引用:`,
 | 
				
			||||||
						body: getNoteSummary(data),
 | 
											body: getNoteSummary(data),
 | 
				
			||||||
						icon: data.user.avatarUrl + '?thumbnail&size=64'
 | 
											icon: data.user.avatarUrl
 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 'reaction':
 | 
									case 'reaction':
 | 
				
			||||||
					return {
 | 
										return {
 | 
				
			||||||
						title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
 | 
											title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
 | 
				
			||||||
						body: getNoteSummary(data.note),
 | 
											body: getNoteSummary(data.note),
 | 
				
			||||||
						icon: data.user.avatarUrl + '?thumbnail&size=64'
 | 
											icon: data.user.avatarUrl
 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import Stream from './stream';
 | 
					import Stream from '../../stream';
 | 
				
			||||||
import MiOS from '../../../mios';
 | 
					import MiOS from '../../../../../mios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ReversiGameStream extends Stream {
 | 
					export class ReversiGameStream extends Stream {
 | 
				
			||||||
	constructor(os: MiOS, me, game) {
 | 
						constructor(os: MiOS, me, game) {
 | 
				
			||||||
		super(os, 'reversi-game', {
 | 
							super(os, 'games/reversi-game', {
 | 
				
			||||||
			i: me ? me.token : null,
 | 
								i: me ? me.token : null,
 | 
				
			||||||
			game: game.id
 | 
								game: game.id
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
import StreamManager from './stream-manager';
 | 
					import StreamManager from '../../stream-manager';
 | 
				
			||||||
import Stream from './stream';
 | 
					import Stream from '../../stream';
 | 
				
			||||||
import MiOS from '../../../mios';
 | 
					import MiOS from '../../../../../mios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ReversiStream extends Stream {
 | 
					export class ReversiStream extends Stream {
 | 
				
			||||||
	constructor(os: MiOS, me) {
 | 
						constructor(os: MiOS, me) {
 | 
				
			||||||
		super(os, 'reversi', {
 | 
							super(os, 'games/reversi', {
 | 
				
			||||||
			i: me.token
 | 
								i: me.token
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/client/app/common/scripts/streaming/hybrid-timeline.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					import Stream from './stream';
 | 
				
			||||||
 | 
					import StreamManager from './stream-manager';
 | 
				
			||||||
 | 
					import MiOS from '../../../mios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Hybrid timeline stream connection
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class HybridTimelineStream extends Stream {
 | 
				
			||||||
 | 
						constructor(os: MiOS, me) {
 | 
				
			||||||
 | 
							super(os, 'hybrid-timeline', {
 | 
				
			||||||
 | 
								i: me.token
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class HybridTimelineStreamManager extends StreamManager<HybridTimelineStream> {
 | 
				
			||||||
 | 
						private me;
 | 
				
			||||||
 | 
						private os: MiOS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(os: MiOS, me) {
 | 
				
			||||||
 | 
							super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.me = me;
 | 
				
			||||||
 | 
							this.os = os;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public getConnection() {
 | 
				
			||||||
 | 
							if (this.connection == null) {
 | 
				
			||||||
 | 
								this.connection = new HybridTimelineStream(this.os, this.me);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return this.connection;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -39,13 +39,17 @@ export default Vue.extend({
 | 
				
			||||||
		dark: {
 | 
							dark: {
 | 
				
			||||||
			type: Boolean,
 | 
								type: Boolean,
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							smooth: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								default: false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			now: new Date(),
 | 
								now: new Date(),
 | 
				
			||||||
			clock: null,
 | 
								enabled: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			graduationsPadding: 0.5,
 | 
								graduationsPadding: 0.5,
 | 
				
			||||||
			handsPadding: 1,
 | 
								handsPadding: 1,
 | 
				
			||||||
| 
						 | 
					@ -74,6 +78,9 @@ export default Vue.extend({
 | 
				
			||||||
			return themeColor;
 | 
								return themeColor;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ms(): number {
 | 
				
			||||||
 | 
								return this.now.getMilliseconds() * this.smooth;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		s(): number {
 | 
							s(): number {
 | 
				
			||||||
			return this.now.getSeconds();
 | 
								return this.now.getSeconds();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					@ -85,13 +92,13 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		hAngle(): number {
 | 
							hAngle(): number {
 | 
				
			||||||
			return Math.PI * (this.h % 12 + this.m / 60) / 6;
 | 
								return Math.PI * (this.h % 12 + (this.m + (this.s + this.ms / 1000) / 60) / 60) / 6;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		mAngle(): number {
 | 
							mAngle(): number {
 | 
				
			||||||
			return Math.PI * (this.m + this.s / 60) / 30;
 | 
								return Math.PI * (this.m + (this.s + this.ms / 1000) / 60) / 30;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		sAngle(): number {
 | 
							sAngle(): number {
 | 
				
			||||||
			return Math.PI * this.s / 30;
 | 
								return Math.PI * (this.s + this.ms / 1000) / 30;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		graduations(): any {
 | 
							graduations(): any {
 | 
				
			||||||
| 
						 | 
					@ -106,11 +113,17 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.clock = setInterval(this.tick, 1000);
 | 
							const update = () => {
 | 
				
			||||||
 | 
								if (this.enabled) {
 | 
				
			||||||
 | 
									this.tick();
 | 
				
			||||||
 | 
									requestAnimationFrame(update);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							update();
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		clearInterval(this.clock);
 | 
							this.enabled = false;
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,16 @@
 | 
				
			||||||
<div class="mk-autocomplete" @contextmenu.prevent="() => {}">
 | 
					<div class="mk-autocomplete" @contextmenu.prevent="() => {}">
 | 
				
			||||||
	<ol class="users" ref="suggests" v-if="users.length > 0">
 | 
						<ol class="users" ref="suggests" v-if="users.length > 0">
 | 
				
			||||||
		<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
 | 
							<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
 | 
				
			||||||
			<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
 | 
								<img class="avatar" :src="user.avatarUrl" alt=""/>
 | 
				
			||||||
			<span class="name">{{ user | userName }}</span>
 | 
								<span class="name">{{ user | userName }}</span>
 | 
				
			||||||
			<span class="username">@{{ user | acct }}</span>
 | 
								<span class="username">@{{ user | acct }}</span>
 | 
				
			||||||
		</li>
 | 
							</li>
 | 
				
			||||||
	</ol>
 | 
						</ol>
 | 
				
			||||||
 | 
						<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0">
 | 
				
			||||||
 | 
							<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1">
 | 
				
			||||||
 | 
								<span class="name">{{ hashtag }}</span>
 | 
				
			||||||
 | 
							</li>
 | 
				
			||||||
 | 
						</ol>
 | 
				
			||||||
	<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
 | 
						<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
 | 
				
			||||||
		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
 | 
							<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
 | 
				
			||||||
			<span class="emoji">{{ emoji.emoji }}</span>
 | 
								<span class="emoji">{{ emoji.emoji }}</span>
 | 
				
			||||||
| 
						 | 
					@ -48,33 +53,33 @@ emjdb.sort((a, b) => a.name.length - b.name.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'],
 | 
						props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
			users: [],
 | 
								users: [],
 | 
				
			||||||
 | 
								hashtags: [],
 | 
				
			||||||
			emojis: [],
 | 
								emojis: [],
 | 
				
			||||||
			select: -1,
 | 
								select: -1,
 | 
				
			||||||
			emojilib
 | 
								emojilib
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		items(): HTMLCollection {
 | 
							items(): HTMLCollection {
 | 
				
			||||||
			return (this.$refs.suggests as Element).children;
 | 
								return (this.$refs.suggests as Element).children;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	updated() {
 | 
						updated() {
 | 
				
			||||||
		//#region 位置調整
 | 
							//#region 位置調整
 | 
				
			||||||
		const margin = 32;
 | 
							if (this.x + this.$el.offsetWidth > window.innerWidth) {
 | 
				
			||||||
 | 
								this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px';
 | 
				
			||||||
		if (this.x + this.$el.offsetWidth > window.innerWidth - margin) {
 | 
					 | 
				
			||||||
			this.$el.style.left = (this.x - this.$el.offsetWidth) + 'px';
 | 
					 | 
				
			||||||
			this.$el.style.marginLeft = '-16px';
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			this.$el.style.left = this.x + 'px';
 | 
								this.$el.style.left = this.x + 'px';
 | 
				
			||||||
			this.$el.style.marginLeft = '0';
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this.y + this.$el.offsetHeight > window.innerHeight - margin) {
 | 
							if (this.y + this.$el.offsetHeight > window.innerHeight) {
 | 
				
			||||||
			this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
 | 
								this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
 | 
				
			||||||
			this.$el.style.marginTop = '0';
 | 
								this.$el.style.marginTop = '0';
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
| 
						 | 
					@ -83,6 +88,7 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		//#endregion
 | 
							//#endregion
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.textarea.addEventListener('keydown', this.onKeydown);
 | 
							this.textarea.addEventListener('keydown', this.onKeydown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,6 +106,7 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		this.textarea.removeEventListener('keydown', this.onKeydown);
 | 
							this.textarea.removeEventListener('keydown', this.onKeydown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -107,6 +114,7 @@ export default Vue.extend({
 | 
				
			||||||
			el.removeEventListener('mousedown', this.onMousedown);
 | 
								el.removeEventListener('mousedown', this.onMousedown);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		exec() {
 | 
							exec() {
 | 
				
			||||||
			this.select = -1;
 | 
								this.select = -1;
 | 
				
			||||||
| 
						 | 
					@ -117,7 +125,8 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (this.type == 'user') {
 | 
								if (this.type == 'user') {
 | 
				
			||||||
				const cache = sessionStorage.getItem(this.q);
 | 
									const cacheKey = 'autocomplete:user:' + this.q;
 | 
				
			||||||
 | 
									const cache = sessionStorage.getItem(cacheKey);
 | 
				
			||||||
				if (cache) {
 | 
									if (cache) {
 | 
				
			||||||
					const users = JSON.parse(cache);
 | 
										const users = JSON.parse(cache);
 | 
				
			||||||
					this.users = users;
 | 
										this.users = users;
 | 
				
			||||||
| 
						 | 
					@ -131,9 +140,33 @@ export default Vue.extend({
 | 
				
			||||||
						this.fetching = false;
 | 
											this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						// キャッシュ
 | 
											// キャッシュ
 | 
				
			||||||
						sessionStorage.setItem(this.q, JSON.stringify(users));
 | 
											sessionStorage.setItem(cacheKey, JSON.stringify(users));
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else if (this.type == 'hashtag') {
 | 
				
			||||||
 | 
									if (this.q == null || this.q == '') {
 | 
				
			||||||
 | 
										this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
 | 
				
			||||||
 | 
										this.fetching = false;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										const cacheKey = 'autocomplete:hashtag:' + this.q;
 | 
				
			||||||
 | 
										const cache = sessionStorage.getItem(cacheKey);
 | 
				
			||||||
 | 
										if (cache) {
 | 
				
			||||||
 | 
											const hashtags = JSON.parse(cache);
 | 
				
			||||||
 | 
											this.hashtags = hashtags;
 | 
				
			||||||
 | 
											this.fetching = false;
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											(this as any).api('hashtags/search', {
 | 
				
			||||||
 | 
												query: this.q,
 | 
				
			||||||
 | 
												limit: 30
 | 
				
			||||||
 | 
											}).then(hashtags => {
 | 
				
			||||||
 | 
												this.hashtags = hashtags;
 | 
				
			||||||
 | 
												this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												// キャッシュ
 | 
				
			||||||
 | 
												sessionStorage.setItem(cacheKey, JSON.stringify(hashtags));
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			} else if (this.type == 'emoji') {
 | 
								} else if (this.type == 'emoji') {
 | 
				
			||||||
				const matched = [];
 | 
									const matched = [];
 | 
				
			||||||
				emjdb.some(x => {
 | 
									emjdb.some(x => {
 | 
				
			||||||
| 
						 | 
					@ -228,12 +261,13 @@ export default Vue.extend({
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
@import '~const.styl'
 | 
					@import '~const.styl'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mk-autocomplete
 | 
					root(isDark)
 | 
				
			||||||
	position fixed
 | 
						position fixed
 | 
				
			||||||
	z-index 65535
 | 
						z-index 65535
 | 
				
			||||||
 | 
						max-width 100%
 | 
				
			||||||
	margin-top calc(1em + 8px)
 | 
						margin-top calc(1em + 8px)
 | 
				
			||||||
	overflow hidden
 | 
						overflow hidden
 | 
				
			||||||
	background #fff
 | 
						background isDark ? #313543 : #fff
 | 
				
			||||||
	border solid 1px rgba(#000, 0.1)
 | 
						border solid 1px rgba(#000, 0.1)
 | 
				
			||||||
	border-radius 4px
 | 
						border-radius 4px
 | 
				
			||||||
	transition top 0.1s ease, left 0.1s ease
 | 
						transition top 0.1s ease, left 0.1s ease
 | 
				
			||||||
| 
						 | 
					@ -248,7 +282,8 @@ export default Vue.extend({
 | 
				
			||||||
		list-style none
 | 
							list-style none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> li
 | 
							> li
 | 
				
			||||||
			display block
 | 
								display flex
 | 
				
			||||||
 | 
								align-items center
 | 
				
			||||||
			padding 4px 12px
 | 
								padding 4px 12px
 | 
				
			||||||
			white-space nowrap
 | 
								white-space nowrap
 | 
				
			||||||
			overflow hidden
 | 
								overflow hidden
 | 
				
			||||||
| 
						 | 
					@ -259,7 +294,13 @@ export default Vue.extend({
 | 
				
			||||||
			&, *
 | 
								&, *
 | 
				
			||||||
				user-select none
 | 
									user-select none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								*
 | 
				
			||||||
 | 
									overflow hidden
 | 
				
			||||||
 | 
									text-overflow ellipsis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&:hover
 | 
								&:hover
 | 
				
			||||||
 | 
									background isDark ? rgba(#fff, 0.1) : rgba(#000, 0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&[data-selected='true']
 | 
								&[data-selected='true']
 | 
				
			||||||
				background $theme-color
 | 
									background $theme-color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -275,7 +316,6 @@ export default Vue.extend({
 | 
				
			||||||
	> .users > li
 | 
						> .users > li
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.avatar
 | 
							.avatar
 | 
				
			||||||
			vertical-align middle
 | 
					 | 
				
			||||||
			min-width 28px
 | 
								min-width 28px
 | 
				
			||||||
			min-height 28px
 | 
								min-height 28px
 | 
				
			||||||
			max-width 28px
 | 
								max-width 28px
 | 
				
			||||||
| 
						 | 
					@ -285,10 +325,15 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.name
 | 
							.name
 | 
				
			||||||
			margin 0 8px 0 0
 | 
								margin 0 8px 0 0
 | 
				
			||||||
			color rgba(#000, 0.8)
 | 
								color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.username
 | 
							.username
 | 
				
			||||||
			color rgba(#000, 0.3)
 | 
								color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .hashtags > li
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.name
 | 
				
			||||||
 | 
								color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .emojis > li
 | 
						> .emojis > li
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,10 +343,15 @@ export default Vue.extend({
 | 
				
			||||||
			width 24px
 | 
								width 24px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.name
 | 
							.name
 | 
				
			||||||
			color rgba(#000, 0.8)
 | 
								color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.alias
 | 
							.alias
 | 
				
			||||||
			margin 0 0 0 8px
 | 
								margin 0 0 0 8px
 | 
				
			||||||
			color rgba(#000, 0.3)
 | 
								color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mk-autocomplete[data-darkmode]
 | 
				
			||||||
 | 
						root(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mk-autocomplete:not([data-darkmode])
 | 
				
			||||||
 | 
						root(false)
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default Vue.extend({
 | 
				
			||||||
					: this.user.avatarColor && this.user.avatarColor.length == 3
 | 
										: this.user.avatarColor && this.user.avatarColor.length == 3
 | 
				
			||||||
						? `rgb(${ this.user.avatarColor.join(',') })`
 | 
											? `rgb(${ this.user.avatarColor.join(',') })`
 | 
				
			||||||
						: null,
 | 
											: null,
 | 
				
			||||||
				backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl }?thumbnail)`,
 | 
									backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`,
 | 
				
			||||||
				borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
 | 
									borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@
 | 
				
			||||||
		<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
 | 
							<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
 | 
				
			||||||
		<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
 | 
							<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
 | 
				
			||||||
		<p class="result" v-if="game.isEnded && logPos == logs.length">
 | 
							<p class="result" v-if="game.isEnded && logPos == logs.length">
 | 
				
			||||||
			<template v-if="game.winner"><b>{{ game.winner.name }}</b>の勝ち{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
 | 
								<template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', game.winner.name) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
 | 
				
			||||||
			<template v-else>%i18n:common.reversi.drawn%</template>
 | 
								<template v-else>%i18n:common.reversi.drawn%</template>
 | 
				
			||||||
		</p>
 | 
							</p>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
| 
						 | 
					@ -26,8 +26,8 @@
 | 
				
			||||||
						:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
 | 
											:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
 | 
				
			||||||
						@click="set(i)"
 | 
											@click="set(i)"
 | 
				
			||||||
						:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
 | 
											:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
 | 
				
			||||||
					<img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt="">
 | 
										<img v-if="stone === true" :src="blackUser.avatarUrl" alt="">
 | 
				
			||||||
					<img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt="">
 | 
										<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="">
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
 | 
								<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
 | 
				
			||||||
| 
						 | 
					@ -58,8 +58,8 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import * as CRC32 from 'crc-32';
 | 
					import * as CRC32 from 'crc-32';
 | 
				
			||||||
import Reversi, { Color } from '../../../../../reversi/core';
 | 
					import Reversi, { Color } from '../../../../../../../games/reversi/core';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['initGame', 'connection'],
 | 
						props: ['initGame', 'connection'],
 | 
				
			||||||
| 
						 | 
					@ -105,13 +105,14 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		isMyTurn(): boolean {
 | 
							isMyTurn(): boolean {
 | 
				
			||||||
			if (this.turnUser == null) return null;
 | 
								if (!this.iAmPlayer) return false;
 | 
				
			||||||
 | 
								if (this.turnUser == null) return false;
 | 
				
			||||||
			return this.turnUser.id == this.$store.state.i.id;
 | 
								return this.turnUser.id == this.$store.state.i.id;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		cellsStyle(): any {
 | 
							cellsStyle(): any {
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				'grid-template-rows': `repeat(${ this.game.settings.map.length }, 1fr)`,
 | 
									'grid-template-rows': `repeat(${this.game.settings.map.length}, 1fr)`,
 | 
				
			||||||
				'grid-template-columns': `repeat(${ this.game.settings.map[0].length }, 1fr)`
 | 
									'grid-template-columns': `repeat(${this.game.settings.map[0].length}, 1fr)`
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import XGame from './reversi.game.vue';
 | 
					import XGame from './reversi.game.vue';
 | 
				
			||||||
import XRoom from './reversi.room.vue';
 | 
					import XRoom from './reversi.room.vue';
 | 
				
			||||||
import { ReversiGameStream } from '../../scripts/streaming/reversi-game';
 | 
					import { ReversiGameStream } from '../../../../scripts/streaming/games/reversi/reversi-game';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import * as maps from '../../../../../reversi/maps';
 | 
					import * as maps from '../../../../../../../games/reversi/maps';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['game', 'connection'],
 | 
						props: ['game', 'connection'],
 | 
				
			||||||
| 
						 | 
					@ -112,7 +112,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		mapCategories(): string[] {
 | 
							mapCategories(): string[] {
 | 
				
			||||||
			const categories = Object.entries(maps).map(x => x[1].category);
 | 
								const categories = Object.values(maps).map(x => x.category);
 | 
				
			||||||
			return categories.filter((item, pos) => categories.indexOf(item) == pos);
 | 
								return categories.filter((item, pos) => categories.indexOf(item) == pos);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		isAccepted(): boolean {
 | 
							isAccepted(): boolean {
 | 
				
			||||||
| 
						 | 
					@ -179,8 +179,8 @@ export default Vue.extend({
 | 
				
			||||||
			if (this.game.settings.map == null) {
 | 
								if (this.game.settings.map == null) {
 | 
				
			||||||
				this.mapName = null;
 | 
									this.mapName = null;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				const foundMap = Object.entries(maps).find(x => x[1].data.join('') == this.game.settings.map.join(''));
 | 
									const found = Object.values(maps).find(x => x.data.join('') == this.game.settings.map.join(''));
 | 
				
			||||||
				this.mapName = foundMap ? foundMap[1].name : '-Custom-';
 | 
									this.mapName = found ? found.name : '-Custom-';
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -206,7 +206,7 @@ export default Vue.extend({
 | 
				
			||||||
			if (v == null) {
 | 
								if (v == null) {
 | 
				
			||||||
				this.game.settings.map = null;
 | 
									this.game.settings.map = null;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				this.game.settings.map = Object.entries(maps).find(x => x[1].name == v)[1].data;
 | 
									this.game.settings.map = Object.values(maps).find(x => x.name == v).data;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			this.$forceUpdate();
 | 
								this.$forceUpdate();
 | 
				
			||||||
			this.updateSettings();
 | 
								this.updateSettings();
 | 
				
			||||||
| 
						 | 
					@ -217,9 +217,9 @@ export default Vue.extend({
 | 
				
			||||||
			const y = Math.floor(pos / this.game.settings.map[0].length);
 | 
								const y = Math.floor(pos / this.game.settings.map[0].length);
 | 
				
			||||||
			const newPixel =
 | 
								const newPixel =
 | 
				
			||||||
				pixel == ' ' ? '-' :
 | 
									pixel == ' ' ? '-' :
 | 
				
			||||||
				pixel == '-' ? 'b' :
 | 
										pixel == '-' ? 'b' :
 | 
				
			||||||
				pixel == 'b' ? 'w' :
 | 
											pixel == 'b' ? 'w' :
 | 
				
			||||||
				' ';
 | 
												' ';
 | 
				
			||||||
			const line = this.game.settings.map[y].split('');
 | 
								const line = this.game.settings.map[y].split('');
 | 
				
			||||||
			line[x] = newPixel;
 | 
								line[x] = newPixel;
 | 
				
			||||||
			this.$set(this.game.settings.map, y, line.join(''));
 | 
								this.$set(this.game.settings.map, y, line.join(''));
 | 
				
			||||||
| 
						 | 
					@ -67,7 +67,9 @@ export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XGameroom
 | 
							XGameroom
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: ['initGame'],
 | 
						props: ['initGame'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			game: null,
 | 
								game: null,
 | 
				
			||||||
| 
						 | 
					@ -82,63 +84,73 @@ export default Vue.extend({
 | 
				
			||||||
			pingClock: null
 | 
								pingClock: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		game(g) {
 | 
							game(g) {
 | 
				
			||||||
			this.$emit('gamed', g);
 | 
								this.$emit('gamed', g);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		if (this.initGame) {
 | 
							if (this.initGame) {
 | 
				
			||||||
			this.game = this.initGame;
 | 
								this.game = this.initGame;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.connection = (this as any).os.streams.reversiStream.getConnection();
 | 
							if (this.$store.getters.isSignedIn) {
 | 
				
			||||||
		this.connectionId = (this as any).os.streams.reversiStream.use();
 | 
								this.connection = (this as any).os.streams.reversiStream.getConnection();
 | 
				
			||||||
 | 
								this.connectionId = (this as any).os.streams.reversiStream.use();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.connection.on('matched', this.onMatched);
 | 
								this.connection.on('matched', this.onMatched);
 | 
				
			||||||
		this.connection.on('invited', this.onInvited);
 | 
								this.connection.on('invited', this.onInvited);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		(this as any).api('reversi/games', {
 | 
								(this as any).api('games/reversi/games', {
 | 
				
			||||||
			my: true
 | 
									my: true
 | 
				
			||||||
		}).then(games => {
 | 
								}).then(games => {
 | 
				
			||||||
			this.myGames = games;
 | 
									this.myGames = games;
 | 
				
			||||||
		});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		(this as any).api('reversi/games').then(games => {
 | 
								(this as any).api('games/reversi/invitations').then(invitations => {
 | 
				
			||||||
 | 
									this.invitations = this.invitations.concat(invitations);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.pingClock = setInterval(() => {
 | 
				
			||||||
 | 
									if (this.matching) {
 | 
				
			||||||
 | 
										this.connection.send({
 | 
				
			||||||
 | 
											type: 'ping',
 | 
				
			||||||
 | 
											id: this.matching.id
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}, 3000);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							(this as any).api('games/reversi/games').then(games => {
 | 
				
			||||||
			this.games = games;
 | 
								this.games = games;
 | 
				
			||||||
			this.gamesFetching = false;
 | 
								this.gamesFetching = false;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					 | 
				
			||||||
		(this as any).api('reversi/invitations').then(invitations => {
 | 
					 | 
				
			||||||
			this.invitations = this.invitations.concat(invitations);
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.pingClock = setInterval(() => {
 | 
					 | 
				
			||||||
			if (this.matching) {
 | 
					 | 
				
			||||||
				this.connection.send({
 | 
					 | 
				
			||||||
					type: 'ping',
 | 
					 | 
				
			||||||
					id: this.matching.id
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}, 3000);
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		this.connection.off('matched', this.onMatched);
 | 
							if (this.connection) {
 | 
				
			||||||
		this.connection.off('invited', this.onInvited);
 | 
								this.connection.off('matched', this.onMatched);
 | 
				
			||||||
		(this as any).os.streams.reversiStream.dispose(this.connectionId);
 | 
								this.connection.off('invited', this.onInvited);
 | 
				
			||||||
 | 
								(this as any).os.streams.reversiStream.dispose(this.connectionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		clearInterval(this.pingClock);
 | 
								clearInterval(this.pingClock);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		go(game) {
 | 
							go(game) {
 | 
				
			||||||
			(this as any).api('reversi/games/show', {
 | 
								(this as any).api('games/reversi/games/show', {
 | 
				
			||||||
				gameId: game.id
 | 
									gameId: game.id
 | 
				
			||||||
			}).then(game => {
 | 
								}).then(game => {
 | 
				
			||||||
				this.matching = null;
 | 
									this.matching = null;
 | 
				
			||||||
				this.game = game;
 | 
									this.game = game;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		match() {
 | 
							match() {
 | 
				
			||||||
			(this as any).apis.input({
 | 
								(this as any).apis.input({
 | 
				
			||||||
				title: 'ユーザー名を入力してください'
 | 
									title: 'ユーザー名を入力してください'
 | 
				
			||||||
| 
						 | 
					@ -146,7 +158,7 @@ export default Vue.extend({
 | 
				
			||||||
				(this as any).api('users/show', {
 | 
									(this as any).api('users/show', {
 | 
				
			||||||
					username
 | 
										username
 | 
				
			||||||
				}).then(user => {
 | 
									}).then(user => {
 | 
				
			||||||
					(this as any).api('reversi/match', {
 | 
										(this as any).api('games/reversi/match', {
 | 
				
			||||||
						userId: user.id
 | 
											userId: user.id
 | 
				
			||||||
					}).then(res => {
 | 
										}).then(res => {
 | 
				
			||||||
						if (res == null) {
 | 
											if (res == null) {
 | 
				
			||||||
| 
						 | 
					@ -158,12 +170,14 @@ export default Vue.extend({
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cancel() {
 | 
							cancel() {
 | 
				
			||||||
			this.matching = null;
 | 
								this.matching = null;
 | 
				
			||||||
			(this as any).api('reversi/match/cancel');
 | 
								(this as any).api('games/reversi/match/cancel');
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		accept(invitation) {
 | 
							accept(invitation) {
 | 
				
			||||||
			(this as any).api('reversi/match', {
 | 
								(this as any).api('games/reversi/match', {
 | 
				
			||||||
				userId: invitation.parent.id
 | 
									userId: invitation.parent.id
 | 
				
			||||||
			}).then(game => {
 | 
								}).then(game => {
 | 
				
			||||||
				if (game) {
 | 
									if (game) {
 | 
				
			||||||
| 
						 | 
					@ -172,10 +186,12 @@ export default Vue.extend({
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onMatched(game) {
 | 
							onMatched(game) {
 | 
				
			||||||
			this.matching = null;
 | 
								this.matching = null;
 | 
				
			||||||
			this.game = game;
 | 
								this.game = game;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onInvited(invite) {
 | 
							onInvited(invite) {
 | 
				
			||||||
			this.invitations.unshift(invite);
 | 
								this.invitations.unshift(invite);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ import urlPreview from './url-preview.vue';
 | 
				
			||||||
import twitterSetting from './twitter-setting.vue';
 | 
					import twitterSetting from './twitter-setting.vue';
 | 
				
			||||||
import fileTypeIcon from './file-type-icon.vue';
 | 
					import fileTypeIcon from './file-type-icon.vue';
 | 
				
			||||||
import Switch from './switch.vue';
 | 
					import Switch from './switch.vue';
 | 
				
			||||||
import Reversi from './reversi.vue';
 | 
					import Reversi from './games/reversi/reversi.vue';
 | 
				
			||||||
import welcomeTimeline from './welcome-timeline.vue';
 | 
					import welcomeTimeline from './welcome-timeline.vue';
 | 
				
			||||||
import uiInput from './ui/input.vue';
 | 
					import uiInput from './ui/input.vue';
 | 
				
			||||||
import uiButton from './ui/button.vue';
 | 
					import uiButton from './ui/button.vue';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,33 +46,45 @@ export default Vue.extend({
 | 
				
			||||||
		display grid
 | 
							display grid
 | 
				
			||||||
		grid-gap 4px
 | 
							grid-gap 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> *
 | 
				
			||||||
 | 
								overflow hidden
 | 
				
			||||||
 | 
								border-radius 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&[data-count="1"]
 | 
							&[data-count="1"]
 | 
				
			||||||
			grid-template-rows 1fr
 | 
								grid-template-rows 1fr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&[data-count="2"]
 | 
							&[data-count="2"]
 | 
				
			||||||
			grid-template-columns 1fr 1fr
 | 
								grid-template-columns 1fr 1fr
 | 
				
			||||||
			grid-template-rows 1fr
 | 
								grid-template-rows 1fr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&[data-count="3"]
 | 
							&[data-count="3"]
 | 
				
			||||||
			grid-template-columns 1fr 0.5fr
 | 
								grid-template-columns 1fr 0.5fr
 | 
				
			||||||
			grid-template-rows 1fr 1fr
 | 
								grid-template-rows 1fr 1fr
 | 
				
			||||||
			:nth-child(1)
 | 
					
 | 
				
			||||||
 | 
								> *:nth-child(1)
 | 
				
			||||||
				grid-row 1 / 3
 | 
									grid-row 1 / 3
 | 
				
			||||||
			:nth-child(3)
 | 
					
 | 
				
			||||||
 | 
								> *:nth-child(3)
 | 
				
			||||||
				grid-column 2 / 3
 | 
									grid-column 2 / 3
 | 
				
			||||||
				grid-row 2 / 3
 | 
									grid-row 2 / 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&[data-count="4"]
 | 
							&[data-count="4"]
 | 
				
			||||||
			grid-template-columns 1fr 1fr
 | 
								grid-template-columns 1fr 1fr
 | 
				
			||||||
			grid-template-rows 1fr 1fr
 | 
								grid-template-rows 1fr 1fr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		:nth-child(1)
 | 
							> *:nth-child(1)
 | 
				
			||||||
			grid-column 1 / 2
 | 
								grid-column 1 / 2
 | 
				
			||||||
			grid-row 1 / 2
 | 
								grid-row 1 / 2
 | 
				
			||||||
		:nth-child(2)
 | 
					
 | 
				
			||||||
 | 
							> *:nth-child(2)
 | 
				
			||||||
			grid-column 2 / 3
 | 
								grid-column 2 / 3
 | 
				
			||||||
			grid-row 1 / 2
 | 
								grid-row 1 / 2
 | 
				
			||||||
		:nth-child(3)
 | 
					
 | 
				
			||||||
 | 
							> *:nth-child(3)
 | 
				
			||||||
			grid-column 1 / 2
 | 
								grid-column 1 / 2
 | 
				
			||||||
			grid-row 2 / 3
 | 
								grid-row 2 / 3
 | 
				
			||||||
		:nth-child(4)
 | 
					
 | 
				
			||||||
 | 
							> *:nth-child(4)
 | 
				
			||||||
			grid-column 2 / 3
 | 
								grid-column 2 / 3
 | 
				
			||||||
			grid-row 2 / 3
 | 
								grid-row 2 / 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -119,7 +119,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onKeypress(e) {
 | 
							onKeypress(e) {
 | 
				
			||||||
			if ((e.which == 10 || e.which == 13) && e.ctrlKey) {
 | 
								if ((e.which == 10 || e.which == 13) && e.ctrlKey && this.canSend) {
 | 
				
			||||||
				this.send();
 | 
									this.send();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,9 @@
 | 
				
			||||||
	<mk-avatar class="avatar" :user="message.user" target="_blank"/>
 | 
						<mk-avatar class="avatar" :user="message.user" target="_blank"/>
 | 
				
			||||||
	<div class="content">
 | 
						<div class="content">
 | 
				
			||||||
		<div class="balloon" :data-no-text="message.text == null">
 | 
							<div class="balloon" :data-no-text="message.text == null">
 | 
				
			||||||
			<p class="read" v-if="isMe && message.isRead">%i18n:@is-read%</p>
 | 
								<!-- <button class="delete-button" v-if="isMe" title="%i18n:common.delete%">
 | 
				
			||||||
			<button class="delete-button" v-if="isMe" title="%i18n:common.delete%">
 | 
					 | 
				
			||||||
				<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
 | 
									<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
 | 
				
			||||||
			</button>
 | 
								</button> -->
 | 
				
			||||||
			<div class="content" v-if="!message.isDeleted">
 | 
								<div class="content" v-if="!message.isDeleted">
 | 
				
			||||||
				<misskey-flavored-markdown class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
 | 
									<misskey-flavored-markdown class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
 | 
				
			||||||
				<div class="file" v-if="message.file">
 | 
									<div class="file" v-if="message.file">
 | 
				
			||||||
| 
						 | 
					@ -23,6 +22,7 @@
 | 
				
			||||||
		<div></div>
 | 
							<div></div>
 | 
				
			||||||
		<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
							<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
				
			||||||
		<footer>
 | 
							<footer>
 | 
				
			||||||
 | 
								<span class="read" v-if="isMe && message.isRead">%i18n:@is-read%</span>
 | 
				
			||||||
			<mk-time :time="message.createdAt"/>
 | 
								<mk-time :time="message.createdAt"/>
 | 
				
			||||||
			<template v-if="message.is_edited">%fa:pencil-alt%</template>
 | 
								<template v-if="message.is_edited">%fa:pencil-alt%</template>
 | 
				
			||||||
		</footer>
 | 
							</footer>
 | 
				
			||||||
| 
						 | 
					@ -120,17 +120,6 @@ root(isDark)
 | 
				
			||||||
					height 16px
 | 
										height 16px
 | 
				
			||||||
					cursor pointer
 | 
										cursor pointer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> .read
 | 
					 | 
				
			||||||
				user-select none
 | 
					 | 
				
			||||||
				display block
 | 
					 | 
				
			||||||
				position absolute
 | 
					 | 
				
			||||||
				z-index 1
 | 
					 | 
				
			||||||
				bottom -4px
 | 
					 | 
				
			||||||
				left -12px
 | 
					 | 
				
			||||||
				margin 0
 | 
					 | 
				
			||||||
				color isDark ? rgba(#fff, 0.5) : rgba(#000, 0.5)
 | 
					 | 
				
			||||||
				font-size 11px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> .content
 | 
								> .content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				> .is-deleted
 | 
									> .is-deleted
 | 
				
			||||||
| 
						 | 
					@ -258,6 +247,12 @@ root(isDark)
 | 
				
			||||||
			> footer
 | 
								> footer
 | 
				
			||||||
				text-align right
 | 
									text-align right
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									> .read
 | 
				
			||||||
 | 
										user-select none
 | 
				
			||||||
 | 
										margin 0 4px 0 0
 | 
				
			||||||
 | 
										color isDark ? rgba(#fff, 0.5) : rgba(#000, 0.5)
 | 
				
			||||||
 | 
										font-size 11px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&[data-is-deleted]
 | 
						&[data-is-deleted]
 | 
				
			||||||
		> .baloon
 | 
							> .baloon
 | 
				
			||||||
			opacity 0.5
 | 
								opacity 0.5
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import getAcct from '../../../../../acct/render';
 | 
					import getAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import * as emojilib from 'emojilib';
 | 
					import * as emojilib from 'emojilib';
 | 
				
			||||||
import parse from '../../../../../mfm/parse';
 | 
					import parse from '../../../../../mfm/parse';
 | 
				
			||||||
import getAcct from '../../../../../acct/render';
 | 
					import getAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
import MkUrl from './url.vue';
 | 
					import MkUrl from './url.vue';
 | 
				
			||||||
import MkGoogle from './google.vue';
 | 
					import MkGoogle from './google.vue';
 | 
				
			||||||
| 
						 | 
					@ -92,7 +92,7 @@ export default Vue.component('misskey-flavored-markdown', {
 | 
				
			||||||
				case 'hashtag':
 | 
									case 'hashtag':
 | 
				
			||||||
					return createElement('a', {
 | 
										return createElement('a', {
 | 
				
			||||||
						attrs: {
 | 
											attrs: {
 | 
				
			||||||
							href: `${url}/tags/${token.hashtag}`,
 | 
												href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
 | 
				
			||||||
							target: '_blank'
 | 
												target: '_blank'
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}, token.content);
 | 
										}, token.content);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,9 @@
 | 
				
			||||||
<span class="mk-nav">
 | 
					<span class="mk-nav">
 | 
				
			||||||
	<a :href="aboutUrl">%i18n:@about%</a>
 | 
						<a :href="aboutUrl">%i18n:@about%</a>
 | 
				
			||||||
	<i>・</i>
 | 
						<i>・</i>
 | 
				
			||||||
	<a href="https://github.com/syuilo/misskey">%i18n:@repository%</a>
 | 
						<a :href="repositoryUrl">%i18n:@repository%</a>
 | 
				
			||||||
	<i>・</i>
 | 
						<i>・</i>
 | 
				
			||||||
	<a href="https://github.com/syuilo/misskey/issues/new" target="_blank">%i18n:@feedback%</a>
 | 
						<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a>
 | 
				
			||||||
	<i>・</i>
 | 
						<i>・</i>
 | 
				
			||||||
	<a :href="devUrl">%i18n:@develop%</a>
 | 
						<a :href="devUrl">%i18n:@develop%</a>
 | 
				
			||||||
	<i>・</i>
 | 
						<i>・</i>
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { docsUrl, statsUrl, statusUrl, devUrl, lang } from '../../../config';
 | 
					import { docsUrl, statsUrl, statusUrl, devUrl, repositoryUrl, feedbackUrl, lang } from '../../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,9 @@ export default Vue.extend({
 | 
				
			||||||
			aboutUrl: `${docsUrl}/${lang}/about`,
 | 
								aboutUrl: `${docsUrl}/${lang}/about`,
 | 
				
			||||||
			statsUrl,
 | 
								statsUrl,
 | 
				
			||||||
			statusUrl,
 | 
								statusUrl,
 | 
				
			||||||
			devUrl
 | 
								devUrl,
 | 
				
			||||||
 | 
								repositoryUrl: repositoryUrl || `https://github.com/syuilo/misskey`,
 | 
				
			||||||
 | 
								feedbackUrl: feedbackUrl || `https://github.com/syuilo/misskey/issues/new`
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
 | 
					<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
 | 
				
			||||||
	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
 | 
						<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
 | 
				
			||||||
	<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
 | 
						<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
 | 
				
			||||||
 | 
						<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:bookmark%</span>
 | 
				
			||||||
	<span class="is-admin" v-if="note.user.isAdmin">admin</span>
 | 
						<span class="is-admin" v-if="note.user.isAdmin">admin</span>
 | 
				
			||||||
	<span class="is-bot" v-if="note.user.isBot">bot</span>
 | 
						<span class="is-bot" v-if="note.user.isBot">bot</span>
 | 
				
			||||||
	<span class="is-cat" v-if="note.user.isCat">cat</span>
 | 
						<span class="is-cat" v-if="note.user.isCat">cat</span>
 | 
				
			||||||
| 
						 | 
					@ -69,6 +70,10 @@ root(isDark)
 | 
				
			||||||
		&:hover
 | 
							&:hover
 | 
				
			||||||
			text-decoration underline
 | 
								text-decoration underline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .is-verified
 | 
				
			||||||
 | 
							margin-right 8px
 | 
				
			||||||
 | 
							color #4dabf7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .is-admin
 | 
						> .is-admin
 | 
				
			||||||
	> .is-bot
 | 
						> .is-bot
 | 
				
			||||||
	> .is-cat
 | 
						> .is-cat
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -183,7 +183,7 @@ root(isDark)
 | 
				
			||||||
				border-right solid $balloon-size transparent
 | 
									border-right solid $balloon-size transparent
 | 
				
			||||||
				border-bottom solid $balloon-size $bgcolor
 | 
									border-bottom solid $balloon-size $bgcolor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&.compact
 | 
							&.big
 | 
				
			||||||
			> div
 | 
								> div
 | 
				
			||||||
				width 280px
 | 
									width 280px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,11 +29,7 @@
 | 
				
			||||||
			<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
 | 
								<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-input>
 | 
						</ui-input>
 | 
				
			||||||
	<div class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
 | 
						<div v-if="recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
 | 
				
			||||||
	<label class="agree-tou" style="display: block; margin: 16px 0;">
 | 
					 | 
				
			||||||
		<input name="agree-tou" type="checkbox" required/>
 | 
					 | 
				
			||||||
		<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
 | 
					 | 
				
			||||||
	</label>
 | 
					 | 
				
			||||||
	<ui-button type="submit">%i18n:@create%</ui-button>
 | 
						<ui-button type="submit">%i18n:@create%</ui-button>
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -41,7 +37,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
const getPasswordStrength = require('syuilo-password-strength');
 | 
					const getPasswordStrength = require('syuilo-password-strength');
 | 
				
			||||||
import { host, url, docsUrl, lang, recaptchaSitekey } from '../../../config';
 | 
					import { host, url, recaptchaSitekey } from '../../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					@ -51,7 +47,6 @@ export default Vue.extend({
 | 
				
			||||||
			password: '',
 | 
								password: '',
 | 
				
			||||||
			retypedPassword: '',
 | 
								retypedPassword: '',
 | 
				
			||||||
			url,
 | 
								url,
 | 
				
			||||||
			touUrl: `${docsUrl}/${lang}/tou`,
 | 
					 | 
				
			||||||
			recaptchaSitekey,
 | 
								recaptchaSitekey,
 | 
				
			||||||
			usernameState: null,
 | 
								usernameState: null,
 | 
				
			||||||
			passwordStrength: '',
 | 
								passwordStrength: '',
 | 
				
			||||||
| 
						 | 
					@ -115,7 +110,7 @@ export default Vue.extend({
 | 
				
			||||||
			(this as any).api('signup', {
 | 
								(this as any).api('signup', {
 | 
				
			||||||
				username: this.username,
 | 
									username: this.username,
 | 
				
			||||||
				password: this.password,
 | 
									password: this.password,
 | 
				
			||||||
				'g-recaptcha-response': (window as any).grecaptcha.getResponse()
 | 
									'g-recaptcha-response': recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
				(this as any).api('signin', {
 | 
									(this as any).api('signin', {
 | 
				
			||||||
					username: this.username,
 | 
										username: this.username,
 | 
				
			||||||
| 
						 | 
					@ -126,15 +121,19 @@ export default Vue.extend({
 | 
				
			||||||
			}).catch(() => {
 | 
								}).catch(() => {
 | 
				
			||||||
				alert('%i18n:@some-error%');
 | 
									alert('%i18n:@some-error%');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				(window as any).grecaptcha.reset();
 | 
									if (recaptchaSitekey != null) {
 | 
				
			||||||
 | 
										(window as any).grecaptcha.reset();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		const head = document.getElementsByTagName('head')[0];
 | 
							if (recaptchaSitekey != null) {
 | 
				
			||||||
		const script = document.createElement('script');
 | 
								const head = document.getElementsByTagName('head')[0];
 | 
				
			||||||
		script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
 | 
								const script = document.createElement('script');
 | 
				
			||||||
		head.appendChild(script);
 | 
								script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
 | 
				
			||||||
 | 
								head.appendChild(script);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -144,22 +143,4 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mk-signup
 | 
					.mk-signup
 | 
				
			||||||
	min-width 302px
 | 
						min-width 302px
 | 
				
			||||||
 | 
					 | 
				
			||||||
	.agree-tou
 | 
					 | 
				
			||||||
		padding 4px
 | 
					 | 
				
			||||||
		border-radius 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
			background #f4f4f4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:active
 | 
					 | 
				
			||||||
			background #eee
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&, *
 | 
					 | 
				
			||||||
			cursor pointer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		p
 | 
					 | 
				
			||||||
			display inline
 | 
					 | 
				
			||||||
			color #555
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,11 @@
 | 
				
			||||||
<iframe v-if="youtubeId" type="text/html" height="250"
 | 
					<iframe v-if="youtubeId" type="text/html" height="250"
 | 
				
			||||||
	:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
 | 
						:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
 | 
				
			||||||
	frameborder="0"/>
 | 
						frameborder="0"/>
 | 
				
			||||||
 | 
					<div v-else-if="tweetUrl && detail" class="twitter">
 | 
				
			||||||
 | 
						<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
 | 
				
			||||||
 | 
							<a :href="url"></a>
 | 
				
			||||||
 | 
						</blockquote>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
<div v-else class="mk-url-preview">
 | 
					<div v-else class="mk-url-preview">
 | 
				
			||||||
	<a :href="url" target="_blank" :title="url" v-if="!fetching">
 | 
						<a :href="url" target="_blank" :title="url" v-if="!fetching">
 | 
				
			||||||
		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
 | 
							<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
 | 
				
			||||||
| 
						 | 
					@ -24,7 +29,17 @@ import Vue from 'vue';
 | 
				
			||||||
import { url as misskeyUrl } from '../../../config';
 | 
					import { url as misskeyUrl } from '../../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['url'],
 | 
						props: {
 | 
				
			||||||
 | 
							url: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								require: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							detail: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
| 
						 | 
					@ -34,6 +49,7 @@ export default Vue.extend({
 | 
				
			||||||
			icon: null,
 | 
								icon: null,
 | 
				
			||||||
			sitename: null,
 | 
								sitename: null,
 | 
				
			||||||
			youtubeId: null,
 | 
								youtubeId: null,
 | 
				
			||||||
 | 
								tweetUrl: null,
 | 
				
			||||||
			misskeyUrl
 | 
								misskeyUrl
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -44,6 +60,25 @@ export default Vue.extend({
 | 
				
			||||||
			this.youtubeId = url.searchParams.get('v');
 | 
								this.youtubeId = url.searchParams.get('v');
 | 
				
			||||||
		} else if (url.hostname == 'youtu.be') {
 | 
							} else if (url.hostname == 'youtu.be') {
 | 
				
			||||||
			this.youtubeId = url.pathname;
 | 
								this.youtubeId = url.pathname;
 | 
				
			||||||
 | 
							} else if (this.detail && url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
 | 
				
			||||||
 | 
								this.tweetUrl = url;
 | 
				
			||||||
 | 
								const twttr = (window as any).twttr || {};
 | 
				
			||||||
 | 
								const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (twttr.widgets) {
 | 
				
			||||||
 | 
									Vue.nextTick(loadTweet);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									const wjsId = 'twitter-wjs';
 | 
				
			||||||
 | 
									if (!document.getElementById(wjsId)) {
 | 
				
			||||||
 | 
										const head = document.getElementsByTagName('head')[0];
 | 
				
			||||||
 | 
										const script = document.createElement('script');
 | 
				
			||||||
 | 
										script.setAttribute('id', wjsId);
 | 
				
			||||||
 | 
										script.setAttribute('src', 'https://platform.twitter.com/widgets.js');
 | 
				
			||||||
 | 
										head.appendChild(script);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									twttr.ready = loadTweet;
 | 
				
			||||||
 | 
									(window as any).twttr = twttr;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
 | 
								fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
 | 
				
			||||||
				res.json().then(info => {
 | 
									res.json().then(info => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import * as getCaretCoordinates from 'textarea-caret';
 | 
					import * as getCaretCoordinates from 'textarea-caret';
 | 
				
			||||||
import MkAutocomplete from '../components/autocomplete.vue';
 | 
					import MkAutocomplete from '../components/autocomplete.vue';
 | 
				
			||||||
 | 
					import renderAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
	bind(el, binding, vn) {
 | 
						bind(el, binding, vn) {
 | 
				
			||||||
| 
						 | 
					@ -67,15 +68,30 @@ class Autocomplete {
 | 
				
			||||||
	 * テキスト入力時
 | 
						 * テキスト入力時
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private onInput() {
 | 
						private onInput() {
 | 
				
			||||||
		const caret = this.textarea.selectionStart;
 | 
							const caretPos = this.textarea.selectionStart;
 | 
				
			||||||
		const text = this.text.substr(0, caret);
 | 
							const text = this.text.substr(0, caretPos).split('\n').pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const mentionIndex = text.lastIndexOf('@');
 | 
							const mentionIndex = text.lastIndexOf('@');
 | 
				
			||||||
 | 
							const hashtagIndex = text.lastIndexOf('#');
 | 
				
			||||||
		const emojiIndex = text.lastIndexOf(':');
 | 
							const emojiIndex = text.lastIndexOf(':');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const max = Math.max(
 | 
				
			||||||
 | 
								mentionIndex,
 | 
				
			||||||
 | 
								hashtagIndex,
 | 
				
			||||||
 | 
								emojiIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (max == -1) {
 | 
				
			||||||
 | 
								this.close();
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const isMention = mentionIndex != -1;
 | 
				
			||||||
 | 
							const isHashtag = hashtagIndex != -1;
 | 
				
			||||||
 | 
							const isEmoji = emojiIndex != -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let opened = false;
 | 
							let opened = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (mentionIndex != -1 && mentionIndex > emojiIndex) {
 | 
							if (isMention) {
 | 
				
			||||||
			const username = text.substr(mentionIndex + 1);
 | 
								const username = text.substr(mentionIndex + 1);
 | 
				
			||||||
			if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) {
 | 
								if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) {
 | 
				
			||||||
				this.open('user', username);
 | 
									this.open('user', username);
 | 
				
			||||||
| 
						 | 
					@ -83,7 +99,15 @@ class Autocomplete {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (emojiIndex != -1 && emojiIndex > mentionIndex) {
 | 
							if (isHashtag && opened == false) {
 | 
				
			||||||
 | 
								const hashtag = text.substr(hashtagIndex + 1);
 | 
				
			||||||
 | 
								if (!hashtag.includes(' ')) {
 | 
				
			||||||
 | 
									this.open('hashtag', hashtag);
 | 
				
			||||||
 | 
									opened = true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (isEmoji && opened == false) {
 | 
				
			||||||
			const emoji = text.substr(emojiIndex + 1);
 | 
								const emoji = text.substr(emojiIndex + 1);
 | 
				
			||||||
			if (emoji != '' && emoji.match(/^[\+\-a-z0-9_]+$/)) {
 | 
								if (emoji != '' && emoji.match(/^[\+\-a-z0-9_]+$/)) {
 | 
				
			||||||
				this.open('emoji', emoji);
 | 
									this.open('emoji', emoji);
 | 
				
			||||||
| 
						 | 
					@ -164,13 +188,31 @@ class Autocomplete {
 | 
				
			||||||
			const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
 | 
								const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
 | 
				
			||||||
			const after = source.substr(caret);
 | 
								const after = source.substr(caret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const acct = renderAcct(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 挿入
 | 
								// 挿入
 | 
				
			||||||
			this.text = trimmedBefore + '@' + value.username + ' ' + after;
 | 
								this.text = trimmedBefore + '@' + acct + ' ' + after;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// キャレットを戻す
 | 
								// キャレットを戻す
 | 
				
			||||||
			this.vm.$nextTick(() => {
 | 
								this.vm.$nextTick(() => {
 | 
				
			||||||
				this.textarea.focus();
 | 
									this.textarea.focus();
 | 
				
			||||||
				const pos = trimmedBefore.length + (value.username.length + 2);
 | 
									const pos = trimmedBefore.length + (acct.length + 2);
 | 
				
			||||||
 | 
									this.textarea.setSelectionRange(pos, pos);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else if (type == 'hashtag') {
 | 
				
			||||||
 | 
								const source = this.text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const before = source.substr(0, caret);
 | 
				
			||||||
 | 
								const trimmedBefore = before.substring(0, before.lastIndexOf('#'));
 | 
				
			||||||
 | 
								const after = source.substr(caret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// 挿入
 | 
				
			||||||
 | 
								this.text = trimmedBefore + '#' + value + ' ' + after;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// キャレットを戻す
 | 
				
			||||||
 | 
								this.vm.$nextTick(() => {
 | 
				
			||||||
 | 
									this.textarea.focus();
 | 
				
			||||||
 | 
									const pos = trimmedBefore.length + (value.length + 2);
 | 
				
			||||||
				this.textarea.setSelectionRange(pos, pos);
 | 
									this.textarea.setSelectionRange(pos, pos);
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		} else if (type == 'emoji') {
 | 
							} else if (type == 'emoji') {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import getAcct from '../../../../../acct/render';
 | 
					import getAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
					import getUserName from '../../../../../misc/get-user-name';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.filter('acct', user => {
 | 
					Vue.filter('acct', user => {
 | 
				
			||||||
	return getAcct(user);
 | 
						return getAcct(user);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,8 +31,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import parseAcct from '../../../../../acct/parse';
 | 
					import parseAcct from '../../../../../misc/acct/parse';
 | 
				
			||||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
					import getUserName from '../../../../../misc/get-user-name';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mkw-analog-clock">
 | 
					<div class="mkw-analog-clock">
 | 
				
			||||||
	<mk-widget-container :naked="props.naked" :show-header="false">
 | 
						<mk-widget-container :naked="!(props.design % 2)" :show-header="false">
 | 
				
			||||||
		<div class="mkw-analog-clock--body">
 | 
							<div class="mkw-analog-clock--body">
 | 
				
			||||||
			<mk-analog-clock :dark="$store.state.device.darkmode"/>
 | 
								<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="!(props.design && ~props.design)"/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</mk-widget-container>
 | 
						</mk-widget-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -13,12 +13,13 @@ import define from '../../../common/define-widget';
 | 
				
			||||||
export default define({
 | 
					export default define({
 | 
				
			||||||
	name: 'analog-clock',
 | 
						name: 'analog-clock',
 | 
				
			||||||
	props: () => ({
 | 
						props: () => ({
 | 
				
			||||||
		naked: false
 | 
							design: -1
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}).extend({
 | 
					}).extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		func() {
 | 
							func() {
 | 
				
			||||||
			this.props.naked = !this.props.naked;
 | 
								if (++this.props.design > 2)
 | 
				
			||||||
 | 
									this.props.design = -1;
 | 
				
			||||||
			this.save();
 | 
								this.save();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,6 +175,7 @@ root(isDark)
 | 
				
			||||||
					> .val
 | 
										> .val
 | 
				
			||||||
						height 4px
 | 
											height 4px
 | 
				
			||||||
						background $theme-color
 | 
											background $theme-color
 | 
				
			||||||
 | 
											transition width .3s cubic-bezier(0.23, 1, 0.32, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				&:nth-child(1)
 | 
									&:nth-child(1)
 | 
				
			||||||
					> .meter > .val
 | 
										> .meter > .val
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				<div v-for="stat in stats" :key="stat.tag">
 | 
									<div v-for="stat in stats" :key="stat.tag">
 | 
				
			||||||
					<div class="tag">
 | 
										<div class="tag">
 | 
				
			||||||
						<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
 | 
											<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
 | 
				
			||||||
						<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
 | 
											<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
					<x-chart class="chart" :src="stat.chart"/>
 | 
										<x-chart class="chart" :src="stat.chart"/>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
							<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
				
			||||||
		<div :class="$style.stream" v-if="!fetching && images.length > 0">
 | 
							<div :class="$style.stream" v-if="!fetching && images.length > 0">
 | 
				
			||||||
			<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div>
 | 
								<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url})`"></div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
 | 
							<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
 | 
				
			||||||
	</mk-widget-container>
 | 
						</mk-widget-container>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,6 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		onStats(stats) {
 | 
							onStats(stats) {
 | 
				
			||||||
			stats.mem.used = stats.mem.total - stats.mem.free;
 | 
					 | 
				
			||||||
			this.stats.push(stats);
 | 
								this.stats.push(stats);
 | 
				
			||||||
			if (this.stats.length > 50) this.stats.shift();
 | 
								if (this.stats.length > 50) this.stats.shift();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,8 +110,8 @@ export default Vue.extend({
 | 
				
			||||||
			this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
								this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
				
			||||||
			this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
								this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
 | 
								this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
 | 
				
			||||||
			this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
 | 
								this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
 | 
								this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
 | 
				
			||||||
			this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
 | 
								this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		onStats(stats) {
 | 
							onStats(stats) {
 | 
				
			||||||
			stats.mem.used = stats.mem.total - stats.mem.free;
 | 
								stats.mem.free = stats.mem.total - stats.mem.used;
 | 
				
			||||||
			this.usage = stats.mem.used / stats.mem.total;
 | 
								this.usage = stats.mem.used / stats.mem.total;
 | 
				
			||||||
			this.total = stats.mem.total;
 | 
								this.total = stats.mem.total;
 | 
				
			||||||
			this.used = stats.mem.used;
 | 
								this.used = stats.mem.used;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,10 @@
 | 
				
			||||||
<div class="mkw-slideshow" :data-mobile="platform == 'mobile'">
 | 
					<div class="mkw-slideshow" :data-mobile="platform == 'mobile'">
 | 
				
			||||||
	<div @click="choose">
 | 
						<div @click="choose">
 | 
				
			||||||
		<p v-if="props.folder === undefined">
 | 
							<p v-if="props.folder === undefined">
 | 
				
			||||||
			<template v-if="isCustomizeMode">フォルダを指定するには、カスタマイズモードを終了してください</template>
 | 
								<template v-if="isCustomizeMode">%i18n:@folder-customize-mode%</template>
 | 
				
			||||||
			<template v-else>クリックしてフォルダを指定してください</template>
 | 
								<template v-else>%i18n:@folder%</template>
 | 
				
			||||||
		</p>
 | 
							</p>
 | 
				
			||||||
		<p v-if="props.folder !== undefined && images.length == 0 && !fetching">このフォルダには画像がありません</p>
 | 
							<p v-if="props.folder !== undefined && images.length == 0 && !fetching">%i18n:@no-image%</p>
 | 
				
			||||||
		<div ref="slideA" class="slide a"></div>
 | 
							<div ref="slideA" class="slide a"></div>
 | 
				
			||||||
		<div ref="slideB" class="slide b"></div>
 | 
							<div ref="slideB" class="slide b"></div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@ export default define({
 | 
				
			||||||
			if (this.images.length == 0) return;
 | 
								if (this.images.length == 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const index = Math.floor(Math.random() * this.images.length);
 | 
								const index = Math.floor(Math.random() * this.images.length);
 | 
				
			||||||
			const img = `url(${ this.images[index].url }?thumbnail&size=1024)`;
 | 
								const img = `url(${ this.images[index].url })`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			(this.$refs.slideB as any).style.backgroundImage = img;
 | 
								(this.$refs.slideB as any).style.backgroundImage = img;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,8 @@ declare const _DOCS_URL_: string;
 | 
				
			||||||
declare const _STATS_URL_: string;
 | 
					declare const _STATS_URL_: string;
 | 
				
			||||||
declare const _STATUS_URL_: string;
 | 
					declare const _STATUS_URL_: string;
 | 
				
			||||||
declare const _DEV_URL_: string;
 | 
					declare const _DEV_URL_: string;
 | 
				
			||||||
 | 
					declare const _REPOSITORY_URL_: string;
 | 
				
			||||||
 | 
					declare const _FEEDBACK_URL_: string;
 | 
				
			||||||
declare const _LANG_: string;
 | 
					declare const _LANG_: string;
 | 
				
			||||||
declare const _LANGS_: string;
 | 
					declare const _LANGS_: string;
 | 
				
			||||||
declare const _RECAPTCHA_SITEKEY_: string;
 | 
					declare const _RECAPTCHA_SITEKEY_: string;
 | 
				
			||||||
| 
						 | 
					@ -32,6 +34,8 @@ export const docsUrl = _DOCS_URL_;
 | 
				
			||||||
export const statsUrl = _STATS_URL_;
 | 
					export const statsUrl = _STATS_URL_;
 | 
				
			||||||
export const statusUrl = _STATUS_URL_;
 | 
					export const statusUrl = _STATUS_URL_;
 | 
				
			||||||
export const devUrl = _DEV_URL_;
 | 
					export const devUrl = _DEV_URL_;
 | 
				
			||||||
 | 
					export const repositoryUrl = _REPOSITORY_URL_;
 | 
				
			||||||
 | 
					export const feedbackUrl = _FEEDBACK_URL_;
 | 
				
			||||||
export const lang = _LANG_;
 | 
					export const lang = _LANG_;
 | 
				
			||||||
export const langs = _LANGS_;
 | 
					export const langs = _LANGS_;
 | 
				
			||||||
export const recaptchaSitekey = _RECAPTCHA_SITEKEY_;
 | 
					export const recaptchaSitekey = _RECAPTCHA_SITEKEY_;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 401 KiB After Width: | Height: | Size: 400 KiB  | 
| 
		 Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 424 B  | 
| 
						 | 
					@ -35,10 +35,7 @@ import Vue from 'vue';
 | 
				
			||||||
const eachMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
 | 
					const eachMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isLeapYear(year) {
 | 
					function isLeapYear(year) {
 | 
				
			||||||
	return (year % 400 == 0) ? true :
 | 
						return !(year & (year % 25 ? 3 : 15));
 | 
				
			||||||
		(year % 100 == 0) ? false :
 | 
					 | 
				
			||||||
			(year % 4 == 0) ? true :
 | 
					 | 
				
			||||||
				false;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ export default Vue.extend({
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		title: {
 | 
							title: {
 | 
				
			||||||
			default: '%fa:R file%%i18n:@choose-prompt%s'
 | 
								default: '%fa:R file%%i18n:@choose-prompt%'
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="root file"
 | 
					<div class="gvfdktuvdgwhmztnuekzkswkjygptfcv"
 | 
				
			||||||
	:data-is-selected="isSelected"
 | 
						:data-is-selected="isSelected"
 | 
				
			||||||
	:data-is-contextmenu-showing="isContextmenuShowing"
 | 
						:data-is-contextmenu-showing="isContextmenuShowing"
 | 
				
			||||||
	@click="onClick"
 | 
						@click="onClick"
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@
 | 
				
			||||||
		<p>%i18n:@banner%</p>
 | 
							<p>%i18n:@banner%</p>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
 | 
						<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
 | 
				
			||||||
		<img :src="`${file.url}?thumbnail&size=128`" alt="" @load="onThumbnailLoaded"/>
 | 
							<img :src="file.url" alt="" @load="onThumbnailLoaded"/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<p class="name">
 | 
						<p class="name">
 | 
				
			||||||
		<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
 | 
							<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,11 @@ export default Vue.extend({
 | 
				
			||||||
				icon: '%fa:i-cursor%',
 | 
									icon: '%fa:i-cursor%',
 | 
				
			||||||
				action: this.rename
 | 
									action: this.rename
 | 
				
			||||||
			}, {
 | 
								}, {
 | 
				
			||||||
 | 
									type: 'item',
 | 
				
			||||||
 | 
									text: this.file.isSensitive ? '%i18n:@contextmenu.unmark-as-sensitive%' : '%i18n:@contextmenu.mark-as-sensitive%',
 | 
				
			||||||
 | 
									icon: this.file.isSensitive ? '%fa:R eye%' : '%fa:R eye-slash%',
 | 
				
			||||||
 | 
									action: this.toggleSensitive
 | 
				
			||||||
 | 
								}, null, {
 | 
				
			||||||
				type: 'item',
 | 
									type: 'item',
 | 
				
			||||||
				text: '%i18n:@contextmenu.copy-url%',
 | 
									text: '%i18n:@contextmenu.copy-url%',
 | 
				
			||||||
				icon: '%fa:link%',
 | 
									icon: '%fa:link%',
 | 
				
			||||||
| 
						 | 
					@ -149,6 +154,13 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							toggleSensitive() {
 | 
				
			||||||
 | 
								(this as any).api('drive/files/update', {
 | 
				
			||||||
 | 
									fileId: this.file.id,
 | 
				
			||||||
 | 
									isSensitive: !this.file.isSensitive
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		copyUrl() {
 | 
							copyUrl() {
 | 
				
			||||||
			copyToClipboard(this.file.url);
 | 
								copyToClipboard(this.file.url);
 | 
				
			||||||
			(this as any).apis.dialog({
 | 
								(this as any).apis.dialog({
 | 
				
			||||||
| 
						 | 
					@ -312,10 +324,10 @@ root(isDark)
 | 
				
			||||||
		> .ext
 | 
							> .ext
 | 
				
			||||||
			opacity 0.5
 | 
								opacity 0.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.root.file[data-darkmode]
 | 
					.gvfdktuvdgwhmztnuekzkswkjygptfcv[data-darkmode]
 | 
				
			||||||
	root(true)
 | 
						root(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.root.file:not([data-darkmode])
 | 
					.gvfdktuvdgwhmztnuekzkswkjygptfcv:not([data-darkmode])
 | 
				
			||||||
	root(false)
 | 
						root(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="root folder"
 | 
					<div class="ynntpczxvnusfwdyxsfuhvcmuypqopdd"
 | 
				
			||||||
	:data-is-contextmenu-showing="isContextmenuShowing"
 | 
						:data-is-contextmenu-showing="isContextmenuShowing"
 | 
				
			||||||
	:data-draghover="draghover"
 | 
						:data-draghover="draghover"
 | 
				
			||||||
	@click="onClick"
 | 
						@click="onClick"
 | 
				
			||||||
| 
						 | 
					@ -216,10 +216,10 @@ export default Vue.extend({
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
@import '~const.styl'
 | 
					@import '~const.styl'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.root.folder
 | 
					root(isDark)
 | 
				
			||||||
	padding 8px
 | 
						padding 8px
 | 
				
			||||||
	height 64px
 | 
						height 64px
 | 
				
			||||||
	background lighten($theme-color, 95%)
 | 
						background isDark ? rgba($theme-color, 0.2) : lighten($theme-color, 95%)
 | 
				
			||||||
	border-radius 4px
 | 
						border-radius 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&, *
 | 
						&, *
 | 
				
			||||||
| 
						 | 
					@ -229,10 +229,10 @@ export default Vue.extend({
 | 
				
			||||||
		pointer-events none
 | 
							pointer-events none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:hover
 | 
						&:hover
 | 
				
			||||||
		background lighten($theme-color, 90%)
 | 
							background isDark ? rgba(lighten($theme-color, 10%), 0.2) : lighten($theme-color, 90%)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:active
 | 
						&:active
 | 
				
			||||||
		background lighten($theme-color, 85%)
 | 
							background isDark ? rgba(darken($theme-color, 10%), 0.2) : lighten($theme-color, 85%)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&[data-is-contextmenu-showing]
 | 
						&[data-is-contextmenu-showing]
 | 
				
			||||||
	&[data-draghover]
 | 
						&[data-draghover]
 | 
				
			||||||
| 
						 | 
					@ -248,16 +248,22 @@ export default Vue.extend({
 | 
				
			||||||
			border-radius 4px
 | 
								border-radius 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&[data-draghover]
 | 
						&[data-draghover]
 | 
				
			||||||
		background lighten($theme-color, 90%)
 | 
							background isDark ? rgba(darken($theme-color, 10%), 0.2) : lighten($theme-color, 90%)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .name
 | 
						> .name
 | 
				
			||||||
		margin 0
 | 
							margin 0
 | 
				
			||||||
		font-size 0.9em
 | 
							font-size 0.9em
 | 
				
			||||||
		color darken($theme-color, 30%)
 | 
							color isDark ? #fff : darken($theme-color, 30%)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> [data-fa]
 | 
							> [data-fa]
 | 
				
			||||||
			margin-right 4px
 | 
								margin-right 4px
 | 
				
			||||||
			margin-left 2px
 | 
								margin-left 2px
 | 
				
			||||||
			text-align left
 | 
								text-align left
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ynntpczxvnusfwdyxsfuhvcmuypqopdd[data-darkmode]
 | 
				
			||||||
 | 
						root(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ynntpczxvnusfwdyxsfuhvcmuypqopdd:not([data-darkmode])
 | 
				
			||||||
 | 
						root(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,10 @@
 | 
				
			||||||
			<span class="separator" v-if="folder != null">%fa:angle-right%</span>
 | 
								<span class="separator" v-if="folder != null">%fa:angle-right%</span>
 | 
				
			||||||
			<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
 | 
								<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<input class="search" type="search" placeholder=" %i18n:@search%"/>
 | 
							<!--
 | 
				
			||||||
 | 
								TODO: #343
 | 
				
			||||||
 | 
								<input class="search" type="search" placeholder=" %i18n:@search%"/>
 | 
				
			||||||
 | 
							-->
 | 
				
			||||||
	</nav>
 | 
						</nav>
 | 
				
			||||||
	<div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
 | 
						<div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
 | 
				
			||||||
		ref="main"
 | 
							ref="main"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-window width="400px" height="550px" @closed="$destroy">
 | 
					<mk-window width="400px" height="550px" @closed="$destroy">
 | 
				
			||||||
	<span slot="header" :class="$style.header">
 | 
						<span slot="header" :class="$style.header">
 | 
				
			||||||
		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
 | 
							<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
 | 
				
			||||||
	</span>
 | 
						</span>
 | 
				
			||||||
	<mk-followers :user="user"/>
 | 
						<mk-followers :user="user"/>
 | 
				
			||||||
</mk-window>
 | 
					</mk-window>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-window width="400px" height="550px" @closed="$destroy">
 | 
					<mk-window width="400px" height="550px" @closed="$destroy">
 | 
				
			||||||
	<span slot="header" :class="$style.header">
 | 
						<span slot="header" :class="$style.header">
 | 
				
			||||||
		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
 | 
							<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
 | 
				
			||||||
	</span>
 | 
						</span>
 | 
				
			||||||
	<mk-following :user="user"/>
 | 
						<mk-following :user="user"/>
 | 
				
			||||||
</mk-window>
 | 
					</mk-window>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="trash">
 | 
								<div class="trash">
 | 
				
			||||||
				<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
 | 
									<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
 | 
				
			||||||
				<p>ゴミ箱</p>
 | 
									<p>%i18n:common.trash%</p>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</x-draggable>
 | 
								</x-draggable>
 | 
				
			||||||
			<div class="main">
 | 
								<div class="main">
 | 
				
			||||||
				<a @click="hint">カスタマイズのヒント</a>
 | 
									<a @click="hint">%i18n:common.customization-tips.title%</a>
 | 
				
			||||||
				<div>
 | 
									<div>
 | 
				
			||||||
					<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
 | 
										<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
 | 
				
			||||||
					<mk-timeline ref="tl" @loaded="onTlLoaded"/>
 | 
										<mk-timeline ref="tl" @loaded="onTlLoaded"/>
 | 
				
			||||||
| 
						 | 
					@ -187,13 +187,13 @@ export default Vue.extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		hint() {
 | 
							hint() {
 | 
				
			||||||
			(this as any).apis.dialog({
 | 
								(this as any).apis.dialog({
 | 
				
			||||||
				title: '%fa:info-circle%カスタマイズのヒント',
 | 
									title: '%fa:info-circle%%i18n:common.customization-tips.title%',
 | 
				
			||||||
				text: '<p>ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。</p>' +
 | 
									text: '<p>%i18n:common.customization-tips.paragraph1%</p>' +
 | 
				
			||||||
					'<p>一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。</p>' +
 | 
										'<p>%i18n:common.customization-tips.paragraph2%</p>' +
 | 
				
			||||||
					'<p>ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。</p>' +
 | 
										'<p>%i18n:common.customization-tips.paragraph3%</p>' +
 | 
				
			||||||
					'<p>カスタマイズを終了するには、右上の「完了」をクリックします。</p>',
 | 
										'<p>%i18n:common.customization-tips.paragraph4%</p>',
 | 
				
			||||||
				actions: [{
 | 
									actions: [{
 | 
				
			||||||
					text: 'Got it!'
 | 
										text: '%i18n:common.customization-tips.gotit%'
 | 
				
			||||||
				}]
 | 
									}]
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,11 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<a class="mk-media-image"
 | 
					<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide" @click="hide = false">
 | 
				
			||||||
 | 
						<div>
 | 
				
			||||||
 | 
							<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
 | 
				
			||||||
 | 
							<span>%i18n:@click-to-show%</span>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<a class="lcjomzwbohoelkxsnuqjiaccdbdfiazy" v-else
 | 
				
			||||||
	:href="image.url"
 | 
						:href="image.url"
 | 
				
			||||||
	@mousemove="onMousemove"
 | 
						@mousemove="onMousemove"
 | 
				
			||||||
	@mouseleave="onMouseleave"
 | 
						@mouseleave="onMouseleave"
 | 
				
			||||||
| 
						 | 
					@ -21,13 +27,17 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		raw: {
 | 
							raw: {
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							hide: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								default: true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		style(): any {
 | 
							style(): any {
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
 | 
									'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
 | 
				
			||||||
				'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)`
 | 
									'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})`
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -56,16 +66,30 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.mk-media-image
 | 
					.lcjomzwbohoelkxsnuqjiaccdbdfiazy
 | 
				
			||||||
	display block
 | 
						display block
 | 
				
			||||||
	cursor zoom-in
 | 
						cursor zoom-in
 | 
				
			||||||
	overflow hidden
 | 
						overflow hidden
 | 
				
			||||||
	width 100%
 | 
						width 100%
 | 
				
			||||||
	height 100%
 | 
						height 100%
 | 
				
			||||||
	background-position center
 | 
						background-position center
 | 
				
			||||||
	border-radius 4px
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:not(:hover)
 | 
						&:not(:hover)
 | 
				
			||||||
		background-size cover
 | 
							background-size cover
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ldwbgwstjsdgcjruamauqdrffetqudry
 | 
				
			||||||
 | 
						display flex
 | 
				
			||||||
 | 
						justify-content center
 | 
				
			||||||
 | 
						align-items center
 | 
				
			||||||
 | 
						background #111
 | 
				
			||||||
 | 
						color #fff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> div
 | 
				
			||||||
 | 
							display table-cell
 | 
				
			||||||
 | 
							text-align center
 | 
				
			||||||
 | 
							font-size 12px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> b
 | 
				
			||||||
 | 
								display block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,19 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
	<video class="mk-media-video"
 | 
					<div class="uofhebxjdgksfmltszlxurtjnjjsvioh" v-if="video.isSensitive && hide" @click="hide = false">
 | 
				
			||||||
 | 
						<div>
 | 
				
			||||||
 | 
							<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
 | 
				
			||||||
 | 
							<span>%i18n:@click-to-show%</span>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="vwxdhznewyashiknzolsoihtlpicqepe" v-else>
 | 
				
			||||||
 | 
						<video class="video"
 | 
				
			||||||
		:src="video.url"
 | 
							:src="video.url"
 | 
				
			||||||
		:title="video.name"
 | 
							:title="video.name"
 | 
				
			||||||
		controls
 | 
							controls
 | 
				
			||||||
		@dblclick.prevent="onClick"
 | 
							@dblclick.prevent="onClick"
 | 
				
			||||||
		ref="video"
 | 
							ref="video"
 | 
				
			||||||
		v-if="inlinePlayable" />
 | 
							v-if="inlinePlayable" />
 | 
				
			||||||
	<a class="mk-media-video-thumbnail"
 | 
						<a class="thumbnail"
 | 
				
			||||||
		:href="video.url"
 | 
							:href="video.url"
 | 
				
			||||||
		:style="imageStyle"
 | 
							:style="imageStyle"
 | 
				
			||||||
		@click.prevent="onClick"
 | 
							@click.prevent="onClick"
 | 
				
			||||||
| 
						 | 
					@ -14,6 +21,7 @@
 | 
				
			||||||
		v-else>
 | 
							v-else>
 | 
				
			||||||
		%fa:R play-circle%
 | 
							%fa:R play-circle%
 | 
				
			||||||
	</a>
 | 
						</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					@ -21,11 +29,23 @@ import Vue from 'vue';
 | 
				
			||||||
import MkMediaVideoDialog from './media-video-dialog.vue';
 | 
					import MkMediaVideoDialog from './media-video-dialog.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['video', 'inlinePlayable'],
 | 
						props: {
 | 
				
			||||||
 | 
							video: {
 | 
				
			||||||
 | 
								type: Object,
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							inlinePlayable: {
 | 
				
			||||||
 | 
								default: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							hide: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								default: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		imageStyle(): any {
 | 
							imageStyle(): any {
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				'background-image': `url(${this.video.url}?thumbnail&size=512)`
 | 
									'background-image': `url(${this.video.url})`
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -47,22 +67,39 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.mk-media-video
 | 
					.vwxdhznewyashiknzolsoihtlpicqepe
 | 
				
			||||||
	display block
 | 
						.video
 | 
				
			||||||
	width 100%
 | 
							display block
 | 
				
			||||||
	height 100%
 | 
							width 100%
 | 
				
			||||||
	border-radius 4px
 | 
							height 100%
 | 
				
			||||||
 | 
							border-radius 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mk-media-video-thumbnail
 | 
						.thumbnail
 | 
				
			||||||
 | 
							display flex
 | 
				
			||||||
 | 
							justify-content center
 | 
				
			||||||
 | 
							align-items center
 | 
				
			||||||
 | 
							font-size 3.5em
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							cursor zoom-in
 | 
				
			||||||
 | 
							overflow hidden
 | 
				
			||||||
 | 
							background-position center
 | 
				
			||||||
 | 
							background-size cover
 | 
				
			||||||
 | 
							width 100%
 | 
				
			||||||
 | 
							height 100%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.uofhebxjdgksfmltszlxurtjnjjsvioh
 | 
				
			||||||
	display flex
 | 
						display flex
 | 
				
			||||||
	justify-content center
 | 
						justify-content center
 | 
				
			||||||
	align-items center
 | 
						align-items center
 | 
				
			||||||
	font-size 3.5em
 | 
						background #111
 | 
				
			||||||
 | 
						color #fff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> div
 | 
				
			||||||
 | 
							display table-cell
 | 
				
			||||||
 | 
							text-align center
 | 
				
			||||||
 | 
							font-size 12px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> b
 | 
				
			||||||
 | 
								display block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cursor zoom-in
 | 
					 | 
				
			||||||
	overflow hidden
 | 
					 | 
				
			||||||
	background-position center
 | 
					 | 
				
			||||||
	background-size cover
 | 
					 | 
				
			||||||
	width 100%
 | 
					 | 
				
			||||||
	height 100%
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
import getAcct from '../../../../../acct/render';
 | 
					import getAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['user'],
 | 
						props: ['user'],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@
 | 
				
			||||||
				<mk-media-list :media-list="p.media" :raw="true"/>
 | 
									<mk-media-list :media-list="p.media" :raw="true"/>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<mk-poll v-if="p.poll" :note="p"/>
 | 
								<mk-poll v-if="p.poll" :note="p"/>
 | 
				
			||||||
			<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
								<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
 | 
				
			||||||
			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
 | 
								<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
 | 
				
			||||||
			<div class="map" v-if="p.geo" ref="map"></div>
 | 
								<div class="map" v-if="p.geo" ref="map"></div>
 | 
				
			||||||
			<div class="renote" v-if="p.renote">
 | 
								<div class="renote" v-if="p.renote">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,10 +56,10 @@
 | 
				
			||||||
				<button @click="menu" ref="menuButton">
 | 
									<button @click="menu" ref="menuButton">
 | 
				
			||||||
					%fa:ellipsis-h%
 | 
										%fa:ellipsis-h%
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
				<button title="%i18n:@detail">
 | 
									<!-- <button title="%i18n:@detail">
 | 
				
			||||||
					<template v-if="!isDetailOpened">%fa:caret-down%</template>
 | 
										<template v-if="!isDetailOpened">%fa:caret-down%</template>
 | 
				
			||||||
					<template v-if="isDetailOpened">%fa:caret-up%</template>
 | 
										<template v-if="isDetailOpened">%fa:caret-up%</template>
 | 
				
			||||||
				</button>
 | 
									</button> -->
 | 
				
			||||||
			</footer>
 | 
								</footer>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</article>
 | 
						</article>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
					import getNoteSummary from '../../../../../misc/get-note-summary';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import XNote from './notes.note.vue';
 | 
					import XNote from './notes.note.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,7 +110,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
					import getNoteSummary from '../../../../../misc/get-note-summary';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,11 @@
 | 
				
			||||||
	<div class="content">
 | 
						<div class="content">
 | 
				
			||||||
		<div v-if="visibility == 'specified'" class="visibleUsers">
 | 
							<div v-if="visibility == 'specified'" class="visibleUsers">
 | 
				
			||||||
			<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span>
 | 
								<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span>
 | 
				
			||||||
			<a @click="addVisibleUser">+ユーザーを追加</a>
 | 
								<a @click="addVisibleUser">%i18n:@add-visible-user%</a>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="hashtags" v-if="recentHashtags.length > 0">
 | 
				
			||||||
 | 
								<b>%i18n:@recent-tags%:</b>
 | 
				
			||||||
 | 
								<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" title="%@click-to-tagging%">#{{ tag }}</a>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<input v-show="useCw" v-model="cw" placeholder="内容への注釈 (オプション)">
 | 
							<input v-show="useCw" v-model="cw" placeholder="内容への注釈 (オプション)">
 | 
				
			||||||
		<textarea :class="{ with: (files.length != 0 || poll) }"
 | 
							<textarea :class="{ with: (files.length != 0 || poll) }"
 | 
				
			||||||
| 
						 | 
					@ -19,7 +23,7 @@
 | 
				
			||||||
		<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
 | 
							<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
 | 
				
			||||||
			<x-draggable :list="files" :options="{ animation: 150 }">
 | 
								<x-draggable :list="files" :options="{ animation: 150 }">
 | 
				
			||||||
				<div v-for="file in files" :key="file.id">
 | 
									<div v-for="file in files" :key="file.id">
 | 
				
			||||||
					<div class="img" :style="{ backgroundImage: `url(${file.url}?thumbnail&size=64)` }" :title="file.name"></div>
 | 
										<div class="img" :style="{ backgroundImage: `url(${file.url})` }" :title="file.name"></div>
 | 
				
			||||||
					<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" title="%i18n:@attach-cancel%" alt=""/>
 | 
										<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" title="%i18n:@attach-cancel%" alt=""/>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</x-draggable>
 | 
								</x-draggable>
 | 
				
			||||||
| 
						 | 
					@ -32,9 +36,15 @@
 | 
				
			||||||
	<button class="drive" title="%i18n:@attach-media-from-drive%" @click="chooseFileFromDrive">%fa:cloud%</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="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 = true">%fa:chart-pie%</button>
 | 
				
			||||||
	<button class="poll" title="内容を隠す" @click="useCw = !useCw">%fa:eye-slash%</button>
 | 
						<button class="poll" title="%i18n:@hide-contents%" @click="useCw = !useCw">%fa:eye-slash%</button>
 | 
				
			||||||
	<button class="geo" title="位置情報を添付する" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
 | 
						<button class="geo" title="%i18n:@attach-location-information%" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
 | 
				
			||||||
	<button class="visibility" title="公開範囲" @click="setVisibility" ref="visibilityButton">%fa:lock%</button>
 | 
						<button class="visibility" title="%i18n:@visibility%" @click="setVisibility" ref="visibilityButton">
 | 
				
			||||||
 | 
							<span v-if="visibility === 'public'">%fa:globe%</span>
 | 
				
			||||||
 | 
							<span v-if="visibility === 'home'">%fa:home%</span>
 | 
				
			||||||
 | 
							<span v-if="visibility === 'followers'">%fa:unlock%</span>
 | 
				
			||||||
 | 
							<span v-if="visibility === 'specified'">%fa:envelope%</span>
 | 
				
			||||||
 | 
							<span v-if="visibility === 'private'">%fa:lock%</span>
 | 
				
			||||||
 | 
						</button>
 | 
				
			||||||
	<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
 | 
						<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
 | 
				
			||||||
	<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
 | 
						<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
 | 
				
			||||||
		{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
 | 
							{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
 | 
				
			||||||
| 
						 | 
					@ -46,6 +56,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import insertTextAtCursor from 'insert-text-at-cursor';
 | 
				
			||||||
import * as XDraggable from 'vuedraggable';
 | 
					import * as XDraggable from 'vuedraggable';
 | 
				
			||||||
import getKao from '../../../common/scripts/get-kao';
 | 
					import getKao from '../../../common/scripts/get-kao';
 | 
				
			||||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
 | 
					import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
 | 
				
			||||||
| 
						 | 
					@ -91,7 +102,8 @@ export default Vue.extend({
 | 
				
			||||||
			visibility: 'public',
 | 
								visibility: 'public',
 | 
				
			||||||
			visibleUsers: [],
 | 
								visibleUsers: [],
 | 
				
			||||||
			autocomplete: null,
 | 
								autocomplete: null,
 | 
				
			||||||
			draghover: false
 | 
								draghover: false,
 | 
				
			||||||
 | 
								recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]')
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,7 +143,9 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		canPost(): boolean {
 | 
							canPost(): boolean {
 | 
				
			||||||
			return !this.posting && (this.text.length != 0 || this.files.length != 0 || this.poll || this.renote);
 | 
								return !this.posting &&
 | 
				
			||||||
 | 
									(1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) &&
 | 
				
			||||||
 | 
									(this.text.trim().length <= 1000);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -183,6 +197,10 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
 | 
							addTag(tag: string) {
 | 
				
			||||||
 | 
								insertTextAtCursor(this.$refs.text, ` #${tag} `);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		watch() {
 | 
							watch() {
 | 
				
			||||||
			this.$watch('text', () => this.saveDraft());
 | 
								this.$watch('text', () => this.saveDraft());
 | 
				
			||||||
			this.$watch('poll', () => this.saveDraft());
 | 
								this.$watch('poll', () => this.saveDraft());
 | 
				
			||||||
| 
						 | 
					@ -235,7 +253,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onKeydown(e) {
 | 
							onKeydown(e) {
 | 
				
			||||||
			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
 | 
								if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onPaste(e) {
 | 
							onPaste(e) {
 | 
				
			||||||
| 
						 | 
					@ -287,7 +305,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		setGeo() {
 | 
							setGeo() {
 | 
				
			||||||
			if (navigator.geolocation == null) {
 | 
								if (navigator.geolocation == null) {
 | 
				
			||||||
				alert('お使いの端末は位置情報に対応していません');
 | 
									alert('%i18n:@geolocation-alert%');
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,10 +313,10 @@ export default Vue.extend({
 | 
				
			||||||
				this.geo = pos.coords;
 | 
									this.geo = pos.coords;
 | 
				
			||||||
				this.$emit('geo-attached', this.geo);
 | 
									this.$emit('geo-attached', this.geo);
 | 
				
			||||||
			}, err => {
 | 
								}, err => {
 | 
				
			||||||
				alert('エラー: ' + err.message);
 | 
									alert('%i18n:@error%: ' + err.message);
 | 
				
			||||||
			}, {
 | 
								}, {
 | 
				
			||||||
				enableHighAccuracy: true
 | 
										enableHighAccuracy: true
 | 
				
			||||||
			});
 | 
									});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		removeGeo() {
 | 
							removeGeo() {
 | 
				
			||||||
| 
						 | 
					@ -318,7 +336,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		addVisibleUser() {
 | 
							addVisibleUser() {
 | 
				
			||||||
			(this as any).apis.input({
 | 
								(this as any).apis.input({
 | 
				
			||||||
				title: 'ユーザー名を入力してください'
 | 
									title: '%i18n:@enter-username%'
 | 
				
			||||||
			}).then(username => {
 | 
								}).then(username => {
 | 
				
			||||||
				(this as any).api('users/show', {
 | 
									(this as any).api('users/show', {
 | 
				
			||||||
					username
 | 
										username
 | 
				
			||||||
| 
						 | 
					@ -370,6 +388,12 @@ export default Vue.extend({
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
				this.posting = false;
 | 
									this.posting = false;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.text && this.text != '') {
 | 
				
			||||||
 | 
									const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
 | 
				
			||||||
 | 
									const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
 | 
				
			||||||
 | 
									localStorage.setItem('hashtags', JSON.stringify(hashtags.concat(history).reduce((a, c) => a.includes(c) ? a : [...a, c], [])));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		saveDraft() {
 | 
							saveDraft() {
 | 
				
			||||||
| 
						 | 
					@ -452,7 +476,7 @@ root(isDark)
 | 
				
			||||||
			margin 0
 | 
								margin 0
 | 
				
			||||||
			max-width 100%
 | 
								max-width 100%
 | 
				
			||||||
			min-width 100%
 | 
								min-width 100%
 | 
				
			||||||
			min-height 64px
 | 
								min-height 84px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&:hover
 | 
								&:hover
 | 
				
			||||||
				& + *
 | 
									& + *
 | 
				
			||||||
| 
						 | 
					@ -478,6 +502,19 @@ root(isDark)
 | 
				
			||||||
				margin-right 16px
 | 
									margin-right 16px
 | 
				
			||||||
				color isDark ? #fff : #666
 | 
									color isDark ? #fff : #666
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> .hashtags
 | 
				
			||||||
 | 
								margin 0 0 8px 0
 | 
				
			||||||
 | 
								overflow hidden
 | 
				
			||||||
 | 
								white-space nowrap
 | 
				
			||||||
 | 
								font-size 14px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> b
 | 
				
			||||||
 | 
									color isDark ? #9baec8 : darken($theme-color, 20%)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> *
 | 
				
			||||||
 | 
									margin-right 8px
 | 
				
			||||||
 | 
									white-space nowrap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .medias
 | 
							> .medias
 | 
				
			||||||
			margin 0
 | 
								margin 0
 | 
				
			||||||
			padding 0
 | 
								padding 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<div class="profile">
 | 
					<div class="profile">
 | 
				
			||||||
	<label class="avatar ui from group">
 | 
						<label class="avatar ui from group">
 | 
				
			||||||
		<p>%i18n:@avatar%</p>
 | 
							<p>%i18n:@avatar%</p>
 | 
				
			||||||
		<img class="avatar" :src="`${$store.state.i.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
							<img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/>
 | 
				
			||||||
		<button class="ui" @click="updateAvatar">%i18n:@choice-avatar%</button>
 | 
							<button class="ui" @click="updateAvatar">%i18n:@choice-avatar%</button>
 | 
				
			||||||
	</label>
 | 
						</label>
 | 
				
			||||||
	<label class="ui from group">
 | 
						<label class="ui from group">
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,7 @@ export default Vue.extend({
 | 
				
			||||||
				description: this.description || null,
 | 
									description: this.description || null,
 | 
				
			||||||
				birthday: this.birthday || null
 | 
									birthday: this.birthday || null
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
				(this as any).apis.notify('プロフィールを更新しました');
 | 
									(this as any).apis.notify('%i18n:@profile-updated%');
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		onChangeIsLocked() {
 | 
							onChangeIsLocked() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -410,7 +410,7 @@ export default Vue.extend({
 | 
				
			||||||
			localStorage.clear();
 | 
								localStorage.clear();
 | 
				
			||||||
			(this as any).apis.dialog({
 | 
								(this as any).apis.dialog({
 | 
				
			||||||
				title: '%i18n:@cache-cleared%',
 | 
									title: '%i18n:@cache-cleared%',
 | 
				
			||||||
				text: '%i18n:@caache-cleared-desc%'
 | 
									text: '%i18n:@cache-cleared-desc%'
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		soundTest() {
 | 
							soundTest() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||