Compare commits
	
		
			53 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0fb528ddf8 | ||
| 
						 | 
					14c03f226d | ||
| 
						 | 
					4f0d844b43 | ||
| 
						 | 
					b93395fc4c | ||
| 
						 | 
					34eacb7e2d | ||
| 
						 | 
					0177023ead | ||
| 
						 | 
					a4678e45de | ||
| 
						 | 
					f24869625e | ||
| 
						 | 
					01beb705a2 | ||
| 
						 | 
					ce28c70c35 | ||
| 
						 | 
					5e0f5c31e7 | ||
| 
						 | 
					b28dd4be52 | ||
| 
						 | 
					291beb45fc | ||
| 
						 | 
					ffb345ccb5 | ||
| 
						 | 
					d2abe2cd81 | ||
| 
						 | 
					acffc3e522 | ||
| 
						 | 
					0962e62b8c | ||
| 
						 | 
					91ebd310b7 | ||
| 
						 | 
					2974c74b4e | ||
| 
						 | 
					3d24112d2d | ||
| 
						 | 
					4a977cd523 | ||
| 
						 | 
					4b1886990f | ||
| 
						 | 
					f3499b787c | ||
| 
						 | 
					5209a584a2 | ||
| 
						 | 
					57a63d38aa | ||
| 
						 | 
					3efffbcf22 | ||
| 
						 | 
					15eaebe522 | ||
| 
						 | 
					eee98358ac | ||
| 
						 | 
					795fc5e7bc | ||
| 
						 | 
					70ac668474 | ||
| 
						 | 
					1004e0d6e8 | ||
| 
						 | 
					52aa64fcb6 | ||
| 
						 | 
					7860d97a10 | ||
| 
						 | 
					409b37b271 | ||
| 
						 | 
					ad9b9964fa | ||
| 
						 | 
					d2b5276f43 | ||
| 
						 | 
					7204e2a84c | ||
| 
						 | 
					1377fa3332 | ||
| 
						 | 
					bf087bfccf | ||
| 
						 | 
					e846e3d571 | ||
| 
						 | 
					d646e62888 | ||
| 
						 | 
					c008154d18 | ||
| 
						 | 
					29bd4de26a | ||
| 
						 | 
					7559b8da6c | ||
| 
						 | 
					35218e84fc | ||
| 
						 | 
					2c135fa2f6 | ||
| 
						 | 
					21da6bd047 | ||
| 
						 | 
					f1d65a66b4 | ||
| 
						 | 
					63e2dbbb0d | ||
| 
						 | 
					a228c522f1 | ||
| 
						 | 
					8ee771ca77 | ||
| 
						 | 
					488bbc9651 | ||
| 
						 | 
					29b2bdf613 | 
@@ -1,18 +1,19 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
# BEARER_TOKEN=
 | 
			
		||||
# CAMPAIGN_ID=
 | 
			
		||||
# GITHUB_TOKEN=
 | 
			
		||||
# HEAD='acid-chicken:patch-autogen'
 | 
			
		||||
# REPO='syuilo/misskey'
 | 
			
		||||
test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN" | jq -r '.[].head.label' | grep $HEAD)" && exit 1
 | 
			
		||||
# __MISSKEY_BEARER_TOKEN=
 | 
			
		||||
# __MISSKEY_CAMPAIGN_ID=
 | 
			
		||||
# __MISSKEY_GITHUB_TOKEN=
 | 
			
		||||
# __MISSKEY_HEAD=acid-chicken:patch-autogen
 | 
			
		||||
# __MISSKEY_REPO=syuilo/misskey
 | 
			
		||||
# __MISSKEY_BRANCH=develop
 | 
			
		||||
test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN" | jq -r '.[].head.label' | grep $__MISSKEY_HEAD)" && exit 1
 | 
			
		||||
cd "$(dirname $0)/.." && \
 | 
			
		||||
touch null.cache && \
 | 
			
		||||
rm *.cache && \
 | 
			
		||||
git checkout master && \
 | 
			
		||||
git pull origin master && \
 | 
			
		||||
git pull upstream master && \
 | 
			
		||||
git checkout $__MISSKEY_BRANCH && \
 | 
			
		||||
git pull origin $__MISSKEY_BRANCH && \
 | 
			
		||||
git pull upstream $__MISSKEY_BRANCH && \
 | 
			
		||||
git stash && \
 | 
			
		||||
git rebase -f upstream/master && \
 | 
			
		||||
git rebase -f upstream/$__MISSKEY_BRANCH && \
 | 
			
		||||
git branch patch-autogen && \
 | 
			
		||||
git checkout patch-autogen && \
 | 
			
		||||
git reset --hard HEAD || \
 | 
			
		||||
@@ -20,12 +21,12 @@ exit 1
 | 
			
		||||
touch patreon.md.cache && \
 | 
			
		||||
rm patreon.md.cache && \
 | 
			
		||||
echo '<!-- PATREON_START -->' > patreon.md.cache && \
 | 
			
		||||
URL="https://www.patreon.com/api/oauth2/v2/campaigns/$CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
 | 
			
		||||
url="https://www.patreon.com/api/oauth2/v2/campaigns/$__MISSKEY_CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
 | 
			
		||||
while :
 | 
			
		||||
 do
 | 
			
		||||
  touch patreon.raw.cache && \
 | 
			
		||||
  rm patreon.raw.cache && \
 | 
			
		||||
  curl -LSs -w '\n' -H "Authorization: Bearer $BEARER_TOKEN" -- $URL > patreon.raw.cache && \
 | 
			
		||||
  curl -LSs -w '\n' -H "Authorization: Bearer $__MISSKEY_BEARER_TOKEN" -- $url > patreon.raw.cache && \
 | 
			
		||||
  touch patreon.cache && \
 | 
			
		||||
  rm patreon.cache && \
 | 
			
		||||
  cat patreon.raw.cache | \
 | 
			
		||||
@@ -42,31 +43,31 @@ while :
 | 
			
		||||
  xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
 | 
			
		||||
  echo '</tr></table>' >> patreon.md.cache || \
 | 
			
		||||
  exit 1
 | 
			
		||||
  NEW_URL="$(cat patreon.raw.cache | jq -r '.links.next')"
 | 
			
		||||
  test "$NEW_URL" = 'null' && \
 | 
			
		||||
  new_url="$(cat patreon.raw.cache | jq -r '.links.next')"
 | 
			
		||||
  test "$new_url" = 'null' && \
 | 
			
		||||
  break || \
 | 
			
		||||
  URL="$NEW_URL"
 | 
			
		||||
  URL="$url"
 | 
			
		||||
done
 | 
			
		||||
IGNORE= && \
 | 
			
		||||
ignore= && \
 | 
			
		||||
echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
 | 
			
		||||
touch README.md && \
 | 
			
		||||
touch .autogen/README.md && \
 | 
			
		||||
rm .autogen/README.md && \
 | 
			
		||||
mv README.md .autogen/README.md && \
 | 
			
		||||
cat .autogen/README.md | while IFS= read LINE;
 | 
			
		||||
cat .autogen/README.md | while IFS= read line;
 | 
			
		||||
 do
 | 
			
		||||
  if [[ -z "$IGNORE" ]]
 | 
			
		||||
  if [[ -z "$ignore" ]]
 | 
			
		||||
   then
 | 
			
		||||
    if [[ "$LINE" = '<!-- PATREON_START -->' ]]
 | 
			
		||||
    if [[ "$line" = '<!-- PATREON_START -->' ]]
 | 
			
		||||
     then
 | 
			
		||||
      IGNORE='PATREON_INSIDE'
 | 
			
		||||
      ignore='PATREON_INSIDE'
 | 
			
		||||
     else
 | 
			
		||||
      echo "$LINE" >> README.md
 | 
			
		||||
      echo "$line" >> README.md
 | 
			
		||||
    fi
 | 
			
		||||
   else
 | 
			
		||||
    if [[ "$LINE" = '<!-- PATREON_END -->' ]]
 | 
			
		||||
     then
 | 
			
		||||
      IGNORE=
 | 
			
		||||
      ignore=
 | 
			
		||||
      cat patreon.md.cache >> README.md
 | 
			
		||||
    fi
 | 
			
		||||
  fi
 | 
			
		||||
@@ -80,7 +81,7 @@ test 4 -lt $(cat diff.cache | wc -l) && \
 | 
			
		||||
git add README.md && \
 | 
			
		||||
git commit -m 'Update README.md [AUTOGEN]' && \
 | 
			
		||||
git push -f origin patch-autogen && \
 | 
			
		||||
curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$HEAD'","base":"master"}' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN"
 | 
			
		||||
curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$__MISSKEY_HEAD'","base":"'$__MISSKEY_BRANCH'"}' -- "https://api.github.com/repos/$__MISSKEY_REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN"
 | 
			
		||||
git stash
 | 
			
		||||
git checkout master
 | 
			
		||||
git checkout $__MISSKEY_BRANCH
 | 
			
		||||
git branch -D patch-autogen
 | 
			
		||||
 
 | 
			
		||||
@@ -47,13 +47,13 @@ Please run `node cli/migration/5.0.0` before launch.
 | 
			
		||||
 | 
			
		||||
オセロがリバーシに変更されました。
 | 
			
		||||
 | 
			
		||||
Othello is now Reversi.
 | 
			
		||||
Othello is rename to Reversi.
 | 
			
		||||
 | 
			
		||||
### Migration
 | 
			
		||||
 | 
			
		||||
MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。
 | 
			
		||||
 | 
			
		||||
You need to rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings`.
 | 
			
		||||
Please rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings` respectively.
 | 
			
		||||
 | 
			
		||||
3.0.0
 | 
			
		||||
-----
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,27 @@
 | 
			
		||||
# Contribution guide
 | 
			
		||||
:v: Misskeyへの貢献ありがとうございます。 :v:
 | 
			
		||||
:v: Thanks for your contributions :v:
 | 
			
		||||
 | 
			
		||||
## Issueの報告
 | 
			
		||||
新機能の提案や不具合の報告は https://github.com/syuilo/misskey/issues で管理しています。
 | 
			
		||||
Issueを作成する前に、既に同じIssueが作成されていないかご確認ください。
 | 
			
		||||
もし既にIssueが作成されている場合は、既存のIssueにコメントをしたりリアクションをするようお願いします。
 | 
			
		||||
## Issues
 | 
			
		||||
Feature suggestions and bug reports are filed in https://github.com/syuilo/misskey/issues .
 | 
			
		||||
Before creating a new issue, please search existing issues to avoid duplication.
 | 
			
		||||
If you find the existing issue, please add your reaction or comment to the issue.
 | 
			
		||||
 | 
			
		||||
## Issueの解決
 | 
			
		||||
[pr-welcomeのラベルがついているIssue](https://github.com/syuilo/misskey/labels/pr-welcome)
 | 
			
		||||
の解決を目的としたPull Requestを作成してくださると非常にありがたいです。
 | 
			
		||||
## Internationalization (i18n)
 | 
			
		||||
Please see [Translation guide](./docs/translate.en.md).
 | 
			
		||||
 | 
			
		||||
## 翻訳の改善
 | 
			
		||||
ソースコード中の `%i18n:id%` という形の文字列は、言語ファイルの対応するテキストに置換されます。
 | 
			
		||||
言語ファイルは /locales ディレクトリに存在します。
 | 
			
		||||
## Localization (l10n)
 | 
			
		||||
Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
 | 
			
		||||
 | 
			
		||||
## ドキュメントの編集
 | 
			
		||||
現在Misskeyはドキュメントが大きく不足しています。
 | 
			
		||||
ドキュメントは /docs ディレクトリに存在します。
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## テストの追加
 | 
			
		||||
現在Misskeyはテストが大きく不足しています。
 | 
			
		||||
テストコードは /test ディレクトリに存在します。
 | 
			
		||||
## Documentation
 | 
			
		||||
* Documents for contributors are located in `/docs`.
 | 
			
		||||
* Documents for instance admins are located in `/docs`.
 | 
			
		||||
* Documents for end users are located in `src/docs`.
 | 
			
		||||
 | 
			
		||||
## 自動テスト及び自動リリース
 | 
			
		||||
Travis CIで行っています。
 | 
			
		||||
設定ファイルは /.travis に存在します。
 | 
			
		||||
## Test
 | 
			
		||||
* Test codes are located in `/test`.
 | 
			
		||||
 | 
			
		||||
## Continuous integration
 | 
			
		||||
Misskey uses Travis for automated test.
 | 
			
		||||
Configuration files are located in `/.travis`.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							@@ -24,7 +24,7 @@ Why don't you take a short break from the hustle and bustle of the city, and div
 | 
			
		||||
* Reactions
 | 
			
		||||
* User lists
 | 
			
		||||
* Customizable column view (called MisskeyDeck)
 | 
			
		||||
  * and widgets!
 | 
			
		||||
* Customizable widgets
 | 
			
		||||
* Private messages
 | 
			
		||||
* ActivityPub support
 | 
			
		||||
 | 
			
		||||
@@ -32,28 +32,16 @@ and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz
 | 
			
		||||
 | 
			
		||||
:package: Create your own instance
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
If you want to run your own instance of Misskey,
 | 
			
		||||
please see [Setup and installation guide](./docs/setup.en.md).
 | 
			
		||||
Please see [Setup and installation guide](./docs/setup.en.md).
 | 
			
		||||
 | 
			
		||||
:wrench: Contribute
 | 
			
		||||
:wrench: Contribution
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
**[PR](https://github.com/syuilo/misskey/pulls)s welcome!**
 | 
			
		||||
 | 
			
		||||
### i18n
 | 
			
		||||
 | 
			
		||||
Please see [Translation guide](./docs/translate.en.md).
 | 
			
		||||
 | 
			
		||||
### l10n
 | 
			
		||||
 | 
			
		||||
Misskey is using Crowdin for l10n.
 | 
			
		||||
 | 
			
		||||
[](https://crowdin.com/project/misskey)
 | 
			
		||||
Please see [Contribution guide](./CONTRIBUTING.md).
 | 
			
		||||
 | 
			
		||||
:heart: Backers & Sponsors
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
<!-- PATREON_START -->
 | 
			
		||||
<table><tr>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
 | 
			
		||||
@@ -63,7 +51,6 @@ Misskey is using Crowdin for l10n.
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
 | 
			
		||||
@@ -80,7 +67,6 @@ Misskey is using Crowdin for l10n.
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
 | 
			
		||||
@@ -88,10 +74,9 @@ Misskey is using Crowdin for l10n.
 | 
			
		||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Sun, 26 Aug 2018 08:55:06 UTC
 | 
			
		||||
**Last updated:** Sun, 02 Sep 2018 00:05:05 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
@@ -131,6 +131,7 @@ You can check if the service is running with `systemctl status misskey`.
 | 
			
		||||
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
 | 
			
		||||
3. `npm install`
 | 
			
		||||
4. `npm run build`
 | 
			
		||||
5. Check [ChangeLog](../CHANGELOG.md) for migration information
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,7 @@ WantedBy=multi-user.target
 | 
			
		||||
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
 | 
			
		||||
3. `npm install`
 | 
			
		||||
4. `npm run build`
 | 
			
		||||
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
# **Please DO NOT edit these files** except `ja-JP.yml`.
 | 
			
		||||
# **DO NOT edit locale files** except `ja-JP.yml`.
 | 
			
		||||
 | 
			
		||||
If you want to...
 | 
			
		||||
* i18n ... please see [Translation guide](../docs/translate.en.md).
 | 
			
		||||
* l10n ... please visit https://crowdin.com/project/misskey
 | 
			
		||||
Please see [Contribution guide](../CONTRIBUTING.md) for more information.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,24 +5,9 @@
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const yaml = require('js-yaml');
 | 
			
		||||
 | 
			
		||||
const loadLang = lang => yaml.safeLoad(
 | 
			
		||||
	fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
 | 
			
		||||
const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES'];
 | 
			
		||||
 | 
			
		||||
const native = loadLang('ja-JP');
 | 
			
		||||
const loadLocale = lang => yaml.safeLoad(fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
 | 
			
		||||
const locales = langs.map(lang => ({ [lang]: loadLocale(lang) }));
 | 
			
		||||
 | 
			
		||||
const langs = {
 | 
			
		||||
	'de-DE': loadLang('de-DE'),
 | 
			
		||||
	'en-US': loadLang('en-US'),
 | 
			
		||||
	'fr-FR': loadLang('fr-FR'),
 | 
			
		||||
	'ja-JP': native,
 | 
			
		||||
	'ja-KS': loadLang('ja-KS'),
 | 
			
		||||
	'pl-PL': loadLang('pl-PL'),
 | 
			
		||||
	'es-ES': loadLang('es-ES')
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Object.values(langs).forEach(locale => {
 | 
			
		||||
	// Extend native language (Japanese)
 | 
			
		||||
	locale = Object.assign({}, native, locale);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = langs;
 | 
			
		||||
module.exports = locales.reduce((a, b) => ({ ...a, ...b }));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "8.18.0",
 | 
			
		||||
	"clientVersion": "1.0.9214",
 | 
			
		||||
	"version": "8.21.1",
 | 
			
		||||
	"clientVersion": "1.0.9264",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
@@ -210,13 +210,12 @@
 | 
			
		||||
		"vue": "2.5.17",
 | 
			
		||||
		"vue-chartjs": "3.4.0",
 | 
			
		||||
		"vue-cropperjs": "2.2.1",
 | 
			
		||||
		"vue-js-modal": "1.3.24",
 | 
			
		||||
		"vue-js-modal": "1.3.25",
 | 
			
		||||
		"vue-json-tree-view": "2.1.4",
 | 
			
		||||
		"vue-loader": "15.4.1",
 | 
			
		||||
		"vue-router": "3.0.1",
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-template-compiler": "2.5.17",
 | 
			
		||||
		"vue-thin-modal": "1.1.1",
 | 
			
		||||
		"vuedraggable": "2.16.0",
 | 
			
		||||
		"vuex": "3.0.1",
 | 
			
		||||
		"vuex-persistedstate": "2.5.4",
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@ html
 | 
			
		||||
		&, *
 | 
			
		||||
			cursor progress !important
 | 
			
		||||
 | 
			
		||||
html
 | 
			
		||||
	// iOSのため
 | 
			
		||||
	overflow auto
 | 
			
		||||
 | 
			
		||||
body
 | 
			
		||||
	overflow-wrap break-word
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,3 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<router-view id="app"></router-view>
 | 
			
		||||
	<modal-portal/>
 | 
			
		||||
</div>
 | 
			
		||||
<router-view id="app"></router-view>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ export default Vue.extend({
 | 
			
		||||
		accepted() {
 | 
			
		||||
			this.state = 'accepted';
 | 
			
		||||
			if (this.session.app.callbackUrl) {
 | 
			
		||||
				location.href = this.session.app.callbackUrl + '?token=' + this.session.token;
 | 
			
		||||
				location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@
 | 
			
		||||
 | 
			
		||||
	// Get salt query
 | 
			
		||||
	const salt = localStorage.getItem('salt')
 | 
			
		||||
		? '?salt=' + localStorage.getItem('salt')
 | 
			
		||||
		? `?salt=${localStorage.getItem('salt')}`
 | 
			
		||||
		: '';
 | 
			
		||||
 | 
			
		||||
	// Load an app script
 | 
			
		||||
 
 | 
			
		||||
@@ -44,11 +44,11 @@ export default class Connection extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
		const query = params
 | 
			
		||||
			? Object.keys(params)
 | 
			
		||||
				.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
 | 
			
		||||
				.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
 | 
			
		||||
				.join('&')
 | 
			
		||||
			: null;
 | 
			
		||||
 | 
			
		||||
		this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? '?' + query : ''}`);
 | 
			
		||||
		this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? `?${query}` : ''}`);
 | 
			
		||||
		this.socket.addEventListener('open', this.onOpen);
 | 
			
		||||
		this.socket.addEventListener('close', this.onClose);
 | 
			
		||||
		this.socket.addEventListener('message', this.onMessage);
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ export default Vue.extend({
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (this.type == 'user') {
 | 
			
		||||
				const cacheKey = 'autocomplete:user:' + this.q;
 | 
			
		||||
				const cacheKey = `autocomplete:user:${this.q}`;
 | 
			
		||||
				const cache = sessionStorage.getItem(cacheKey);
 | 
			
		||||
				if (cache) {
 | 
			
		||||
					const users = JSON.parse(cache);
 | 
			
		||||
@@ -148,7 +148,7 @@ export default Vue.extend({
 | 
			
		||||
					this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
 | 
			
		||||
					this.fetching = false;
 | 
			
		||||
				} else {
 | 
			
		||||
					const cacheKey = 'autocomplete:hashtag:' + this.q;
 | 
			
		||||
					const cacheKey = `autocomplete:hashtag:${this.q}`;
 | 
			
		||||
					const cache = sessionStorage.getItem(cacheKey);
 | 
			
		||||
					if (cache) {
 | 
			
		||||
						const hashtags = JSON.parse(cache);
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check internet connection
 | 
			
		||||
		fetch('https://google.com?rand=' + Math.random(), {
 | 
			
		||||
		fetch(`https://google.com?rand=${Math.random()}`, {
 | 
			
		||||
			mode: 'no-cors'
 | 
			
		||||
		}).then(() => {
 | 
			
		||||
			this.internet = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -159,11 +159,9 @@ export default Vue.extend({
 | 
			
		||||
				canPutEverywhere: this.game.settings.canPutEverywhere,
 | 
			
		||||
				loopedBoard: this.game.settings.loopedBoard
 | 
			
		||||
			});
 | 
			
		||||
			this.logs.forEach((log, i) => {
 | 
			
		||||
				if (i < v) {
 | 
			
		||||
					this.o.put(log.color, log.pos);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			for (const log of this.logs.slice(0, v)) {
 | 
			
		||||
				this.o.put(log.color, log.pos);
 | 
			
		||||
			}
 | 
			
		||||
			this.$forceUpdate();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -205,17 +205,8 @@ export default Vue.component('misskey-flavored-markdown', {
 | 
			
		||||
			}
 | 
			
		||||
		}));
 | 
			
		||||
 | 
			
		||||
		const _els = [];
 | 
			
		||||
		els.forEach((el, i) => {
 | 
			
		||||
			if (el.tag == 'br') {
 | 
			
		||||
				if (!['div', 'pre'].includes(els[i - 1].tag)) {
 | 
			
		||||
					_els.push(el);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				_els.push(el);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// el.tag === 'br' のとき i !== 0 が保証されるため、短絡評価により els[i - 1] は配列外参照しない
 | 
			
		||||
		const _els = els.filter((el, i) => !(el.tag === 'br' && ['div', 'pre'].includes(els[i - 1].tag)));
 | 
			
		||||
		return createElement('span', _els);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
	</blockquote>
 | 
			
		||||
</div>
 | 
			
		||||
<div v-else class="mk-url-preview">
 | 
			
		||||
	<a :href="url" target="_blank" :title="url" v-if="!fetching">
 | 
			
		||||
	<a :class="{ mini }" :href="url" target="_blank" :title="url" v-if="!fetching">
 | 
			
		||||
		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
 | 
			
		||||
		<article>
 | 
			
		||||
			<header>
 | 
			
		||||
@@ -118,6 +118,12 @@ export default Vue.extend({
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		mini: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -164,7 +170,7 @@ export default Vue.extend({
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
 | 
			
		||||
		fetch(`/url?url=${encodeURIComponent(this.url)}`).then(res => {
 | 
			
		||||
			res.json().then(info => {
 | 
			
		||||
				if (info.url == null) return;
 | 
			
		||||
				this.title = info.title;
 | 
			
		||||
@@ -293,6 +299,29 @@ root(isDark)
 | 
			
		||||
						width 12px
 | 
			
		||||
						height 12px
 | 
			
		||||
 | 
			
		||||
		&.mini
 | 
			
		||||
			font-size 10px
 | 
			
		||||
 | 
			
		||||
			> .thumbnail
 | 
			
		||||
				position relative
 | 
			
		||||
				width 100%
 | 
			
		||||
				height 60px
 | 
			
		||||
 | 
			
		||||
			> article
 | 
			
		||||
				left 0
 | 
			
		||||
				width 100%
 | 
			
		||||
				padding 8px
 | 
			
		||||
 | 
			
		||||
				> header
 | 
			
		||||
					margin-bottom 4px
 | 
			
		||||
 | 
			
		||||
				> footer
 | 
			
		||||
					margin-top 4px
 | 
			
		||||
 | 
			
		||||
					> img
 | 
			
		||||
						width 12px
 | 
			
		||||
						height 12px
 | 
			
		||||
 | 
			
		||||
.mk-url-preview[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -191,7 +191,7 @@ class Autocomplete {
 | 
			
		||||
			const acct = renderAcct(value);
 | 
			
		||||
 | 
			
		||||
			// 挿入
 | 
			
		||||
			this.text = trimmedBefore + '@' + acct + ' ' + after;
 | 
			
		||||
			this.text = `${trimmedBefore}@${acct} ${after}`;
 | 
			
		||||
 | 
			
		||||
			// キャレットを戻す
 | 
			
		||||
			this.vm.$nextTick(() => {
 | 
			
		||||
@@ -207,7 +207,7 @@ class Autocomplete {
 | 
			
		||||
			const after = source.substr(caret);
 | 
			
		||||
 | 
			
		||||
			// 挿入
 | 
			
		||||
			this.text = trimmedBefore + '#' + value + ' ' + after;
 | 
			
		||||
			this.text = `${trimmedBefore}#${value} ${after}`;
 | 
			
		||||
 | 
			
		||||
			// キャレットを戻す
 | 
			
		||||
			this.vm.$nextTick(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
Vue.filter('notePage', note => {
 | 
			
		||||
	return '/notes/' + note.id;
 | 
			
		||||
	return `/notes/${note.id}`;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -11,5 +11,5 @@ Vue.filter('userName', user => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Vue.filter('userPage', (user, path?) => {
 | 
			
		||||
	return '/@' + Vue.filter('acct')(user) + (path ? '/' + path : '');
 | 
			
		||||
	return `/@${Vue.filter('acct')(user)}${(path ? `/${path}` : '')}`;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="syxhndwprovvuqhmyvveewmbqayniwkv" v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
	<div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + myName + '</b>')"></div>
 | 
			
		||||
	<div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', `<b>${myName}`)"></div>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
		<div class="banner" :style="bannerStyle"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@ export default Vue.extend({
 | 
			
		||||
							});
 | 
			
		||||
							break;
 | 
			
		||||
						default:
 | 
			
		||||
							alert('%i18n:@unhandled-error% ' + err);
 | 
			
		||||
							alert(`%i18n:@unhandled-error% ${err}`);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -323,7 +323,7 @@ export default Vue.extend({
 | 
			
		||||
							});
 | 
			
		||||
							break;
 | 
			
		||||
						default:
 | 
			
		||||
							alert('%i18n:@unhandled-error% ' + err);
 | 
			
		||||
							alert(`%i18n:@unhandled-error% ${err}`);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
@@ -404,7 +404,7 @@ export default Vue.extend({
 | 
			
		||||
					folder: folder
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				window.open(url + '/i/drive/folder/' + folder.id,
 | 
			
		||||
				window.open(`${url}/i/drive/folder/${folder.id}`,
 | 
			
		||||
					'drive_window',
 | 
			
		||||
					'height=500, width=800');
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ export default Vue.extend({
 | 
			
		||||
			const mouseY = e.clientY - rect.top;
 | 
			
		||||
			const xp = mouseX / this.$el.offsetWidth * 100;
 | 
			
		||||
			const yp = mouseY / this.$el.offsetHeight * 100;
 | 
			
		||||
			this.$el.style.backgroundPosition = xp + '% ' + yp + '%';
 | 
			
		||||
			this.$el.style.backgroundPosition = `${xp}% ${yp}%`;
 | 
			
		||||
			this.$el.style.backgroundImage = `url("${this.image.url}")`;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -110,9 +110,9 @@ export default Vue.extend({
 | 
			
		||||
	computed: {
 | 
			
		||||
		draftId(): string {
 | 
			
		||||
			return this.renote
 | 
			
		||||
				? 'renote:' + this.renote.id
 | 
			
		||||
				? `renote:${this.renote.id}`
 | 
			
		||||
				: this.reply
 | 
			
		||||
					? 'reply:' + this.reply.id
 | 
			
		||||
					? `reply:${this.reply.id}`
 | 
			
		||||
					: 'note';
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -313,7 +313,7 @@ export default Vue.extend({
 | 
			
		||||
				this.geo = pos.coords;
 | 
			
		||||
				this.$emit('geo-attached', this.geo);
 | 
			
		||||
			}, err => {
 | 
			
		||||
				alert('%i18n:@error%: ' + err.message);
 | 
			
		||||
				alert(`%i18n:@error%: ${err.message}`);
 | 
			
		||||
			}, {
 | 
			
		||||
					enableHighAccuracy: true
 | 
			
		||||
				});
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@
 | 
			
		||||
					<div class="renote" v-if="p.renote">
 | 
			
		||||
						<mk-note-preview :note="p.renote" :mini="true"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="false" :mini="true"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ export default Vue.extend({
 | 
			
		||||
			const title = folder.name + ' | %i18n:@title%';
 | 
			
		||||
 | 
			
		||||
			// Rewrite URL
 | 
			
		||||
			history.pushState(null, title, '/i/drive/folder/' + folder.id);
 | 
			
		||||
			history.pushState(null, title, `/i/drive/folder/${folder.id}`);
 | 
			
		||||
 | 
			
		||||
			document.title = title;
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,10 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(game, actualNav) {
 | 
			
		||||
			if (actualNav) {
 | 
			
		||||
				this.$router.push('/reversi/' + game.id);
 | 
			
		||||
				this.$router.push(`/reversi/${game.id}`);
 | 
			
		||||
			} else {
 | 
			
		||||
				// TODO: https://github.com/vuejs/vue-router/issues/703
 | 
			
		||||
				this.$router.push('/reversi/' + game.id);
 | 
			
		||||
				this.$router.push(`/reversi/${game.id}`);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ export default Vue.extend({
 | 
			
		||||
				this.user = user;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				document.title = 'メッセージ: ' + getUserName(this.user);
 | 
			
		||||
				document.title = `メッセージ: ${getUserName(this.user)}`;
 | 
			
		||||
 | 
			
		||||
				Progress.done();
 | 
			
		||||
			});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="followers-you-know">
 | 
			
		||||
<div class="vahgrswmbzfdlmomxnqftuueyvwaafth">
 | 
			
		||||
	<p class="title">%fa:users%%i18n:@title%</p>
 | 
			
		||||
	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
 | 
			
		||||
	<div v-if="!fetching && users.length > 0">
 | 
			
		||||
@@ -37,7 +37,6 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
.followers-you-know
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
@@ -78,4 +77,10 @@ root(isDark)
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
.vahgrswmbzfdlmomxnqftuueyvwaafth[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.vahgrswmbzfdlmomxnqftuueyvwaafth:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="friends">
 | 
			
		||||
<div class="hozptpaliadatkehcmcayizwzwwctpbc">
 | 
			
		||||
	<p class="title">%fa:users%%i18n:@title%</p>
 | 
			
		||||
	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
 | 
			
		||||
	<template v-if="!fetching && users.length != 0">
 | 
			
		||||
@@ -41,7 +41,6 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
.friends
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
@@ -113,10 +112,10 @@ root(isDark)
 | 
			
		||||
			top 16px
 | 
			
		||||
			right 16px
 | 
			
		||||
 | 
			
		||||
.friends[data-darkmode]
 | 
			
		||||
.hozptpaliadatkehcmcayizwzwwctpbc[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.friends:not([data-darkmode])
 | 
			
		||||
.hozptpaliadatkehcmcayizwzwwctpbc:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="photos">
 | 
			
		||||
<div class="dzsuvbsrrrwobdxifudxuefculdfiaxd">
 | 
			
		||||
	<p class="title">%fa:camera%%i18n:@title%</p>
 | 
			
		||||
	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
 | 
			
		||||
	<div class="stream" v-if="!fetching && images.length > 0">
 | 
			
		||||
@@ -40,7 +40,6 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
.photos
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
@@ -88,10 +87,10 @@ root(isDark)
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
.photos[data-darkmode]
 | 
			
		||||
.dzsuvbsrrrwobdxifudxuefculdfiaxd[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.photos:not([data-darkmode])
 | 
			
		||||
.dzsuvbsrrrwobdxifudxuefculdfiaxd:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@
 | 
			
		||||
	<div class="tl">
 | 
			
		||||
		<mk-welcome-timeline :max="20"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<modal name="signup" width="500px" height="auto" scrollable>
 | 
			
		||||
		<header :class="$style.signupFormHeader">%i18n:@signup%</header>
 | 
			
		||||
		<mk-signup :class="$style.signupForm"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,12 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Vuex from 'vuex';
 | 
			
		||||
import VueRouter from 'vue-router';
 | 
			
		||||
import VModal from 'vue-js-modal';
 | 
			
		||||
import * as TreeView from 'vue-json-tree-view';
 | 
			
		||||
import VAnimateCss from 'v-animate-css';
 | 
			
		||||
import Element from 'element-ui';
 | 
			
		||||
import ElementLocaleEn from 'element-ui/lib/locale/lang/en';
 | 
			
		||||
import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
 | 
			
		||||
import VueThinModal from 'vue-thin-modal';
 | 
			
		||||
import 'vue-thin-modal/dist/vue-thin-modal.css';
 | 
			
		||||
import VModal from 'vue-js-modal';
 | 
			
		||||
 | 
			
		||||
import App from './app.vue';
 | 
			
		||||
import checkForUpdate from './common/scripts/check-for-update';
 | 
			
		||||
@@ -28,13 +26,10 @@ switch (lang) {
 | 
			
		||||
 | 
			
		||||
Vue.use(Vuex);
 | 
			
		||||
Vue.use(VueRouter);
 | 
			
		||||
Vue.use(VModal);
 | 
			
		||||
Vue.use(TreeView);
 | 
			
		||||
Vue.use(VAnimateCss);
 | 
			
		||||
Vue.use(Element, { locale: elementLocale });
 | 
			
		||||
Vue.use(VueThinModal, {
 | 
			
		||||
	autoMountPortal: false
 | 
			
		||||
});
 | 
			
		||||
Vue.use(VModal);
 | 
			
		||||
 | 
			
		||||
// Register global directives
 | 
			
		||||
require('./common/views/directives');
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/client/app/mobile/api/post.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/client/app/mobile/api/post.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import PostForm from '../views/components/post-form-dialog.vue';
 | 
			
		||||
 | 
			
		||||
export default (os) => (opts) => {
 | 
			
		||||
	const o = opts || {};
 | 
			
		||||
 | 
			
		||||
	document.documentElement.style.overflow = 'hidden';
 | 
			
		||||
 | 
			
		||||
	function recover() {
 | 
			
		||||
		document.documentElement.style.overflow = 'auto';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const vm = new PostForm({
 | 
			
		||||
		parent: os.app,
 | 
			
		||||
		propsData: {
 | 
			
		||||
			reply: o.reply,
 | 
			
		||||
			renote: o.renote
 | 
			
		||||
		}
 | 
			
		||||
	}).$mount();
 | 
			
		||||
	vm.$once('cancel', recover);
 | 
			
		||||
	vm.$once('posted', recover);
 | 
			
		||||
	document.body.appendChild(vm.$el);
 | 
			
		||||
	(vm as any).focus();
 | 
			
		||||
};
 | 
			
		||||
@@ -14,6 +14,7 @@ import chooseDriveFolder from './api/choose-drive-folder';
 | 
			
		||||
import chooseDriveFile from './api/choose-drive-file';
 | 
			
		||||
import dialog from './api/dialog';
 | 
			
		||||
import input from './api/input';
 | 
			
		||||
import post from './api/post';
 | 
			
		||||
import notify from './api/notify';
 | 
			
		||||
 | 
			
		||||
import MkIndex from './views/pages/index.vue';
 | 
			
		||||
@@ -90,7 +91,7 @@ init((launch) => {
 | 
			
		||||
		chooseDriveFile,
 | 
			
		||||
		dialog: dialog(os),
 | 
			
		||||
		input,
 | 
			
		||||
		post: () => alert('deprecated'),
 | 
			
		||||
		post: post(os),
 | 
			
		||||
		notify
 | 
			
		||||
	}));
 | 
			
		||||
}, true);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,3 @@ body
 | 
			
		||||
	display flex
 | 
			
		||||
	flex-direction column
 | 
			
		||||
	min-height 100%
 | 
			
		||||
 | 
			
		||||
.modal-backdrop
 | 
			
		||||
	z-index 10000 !important
 | 
			
		||||
 | 
			
		||||
.modal-content-wrapper
 | 
			
		||||
	z-index 10001 !important
 | 
			
		||||
 | 
			
		||||
.modal-content
 | 
			
		||||
	padding 0 !important
 | 
			
		||||
	background-color transparent !important
 | 
			
		||||
 
 | 
			
		||||
@@ -75,13 +75,6 @@
 | 
			
		||||
	<div class="replies" v-if="!compact">
 | 
			
		||||
		<x-sub v-for="note in replies" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<modal name="replyForm">
 | 
			
		||||
		<mk-post-form @posted="replyFormClosed" @cancel="replyFormClosed" :reply="p"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
	<modal name="renoteForm">
 | 
			
		||||
		<mk-post-form @posted="renoteFormClosed" @cancel="renoteFormClosed" :renote="p"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -192,19 +185,15 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		reply() {
 | 
			
		||||
			this.$modal.push('replyForm');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		replyFormClosed() {
 | 
			
		||||
			this.$modal.pop();
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renote() {
 | 
			
		||||
			this.$modal.push('renoteForm');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renoteFormClosed() {
 | 
			
		||||
			this.$modal.pop();
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		react() {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,13 +60,6 @@
 | 
			
		||||
			</footer>
 | 
			
		||||
		</div>
 | 
			
		||||
	</article>
 | 
			
		||||
 | 
			
		||||
	<modal name="replyForm">
 | 
			
		||||
		<mk-post-form @posted="replyFormClosed" @cancel="replyFormClosed" :reply="p"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
	<modal name="renoteForm">
 | 
			
		||||
		<mk-post-form @posted="renoteFormClosed" @cancel="renoteFormClosed" :renote="p"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -202,19 +195,15 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		reply() {
 | 
			
		||||
			this.$modal.push('replyForm');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		replyFormClosed() {
 | 
			
		||||
			this.$modal.pop();
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renote() {
 | 
			
		||||
			this.$modal.push('renoteForm');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renoteFormClosed() {
 | 
			
		||||
			this.$modal.pop();
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		react() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										131
									
								
								src/client/app/mobile/views/components/post-form-dialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/client/app/mobile/views/components/post-form-dialog.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
 | 
			
		||||
	<div class="bg" ref="bg" @click="onBgClick"></div>
 | 
			
		||||
	<div class="main" ref="main" @click.self="onBgClick">
 | 
			
		||||
		<mk-post-form ref="form"
 | 
			
		||||
			:reply="reply"
 | 
			
		||||
			:renote="renote"
 | 
			
		||||
			:initial-text="initialText"
 | 
			
		||||
			:instant="instant"
 | 
			
		||||
			@posted="onPosted"
 | 
			
		||||
			@cancel="onCanceled"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as anime from 'animejs';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		reply: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		renote: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		initialText: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		instant: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			(this.$refs.bg as any).style.pointerEvents = 'auto';
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.bg,
 | 
			
		||||
				opacity: 1,
 | 
			
		||||
				duration: 100,
 | 
			
		||||
				easing: 'linear'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.main,
 | 
			
		||||
				opacity: 1,
 | 
			
		||||
				translateY: [-16, 0],
 | 
			
		||||
				duration: 300,
 | 
			
		||||
				easing: 'easeOutQuad'
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$refs.form.focus();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		close() {
 | 
			
		||||
			(this.$refs.bg as any).style.pointerEvents = 'none';
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.bg,
 | 
			
		||||
				opacity: 0,
 | 
			
		||||
				duration: 300,
 | 
			
		||||
				easing: 'linear'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			(this.$refs.main as any).style.pointerEvents = 'none';
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.main,
 | 
			
		||||
				opacity: 0,
 | 
			
		||||
				translateY: 16,
 | 
			
		||||
				duration: 300,
 | 
			
		||||
				easing: 'easeOutQuad',
 | 
			
		||||
				complete: () => this.$destroy()
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onBgClick() {
 | 
			
		||||
			this.$emit('cancel');
 | 
			
		||||
			this.close();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onPosted() {
 | 
			
		||||
			this.$emit('posted');
 | 
			
		||||
			this.close();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onCanceled() {
 | 
			
		||||
			this.$emit('cancel');
 | 
			
		||||
			this.close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.ulveipglmagnxfgvitaxyszerjwiqmwl
 | 
			
		||||
	> .bg
 | 
			
		||||
		display block
 | 
			
		||||
		position fixed
 | 
			
		||||
		z-index 10000
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100%
 | 
			
		||||
		background rgba(#000, 0.7)
 | 
			
		||||
		opacity 0
 | 
			
		||||
		pointer-events none
 | 
			
		||||
 | 
			
		||||
	> .main
 | 
			
		||||
		display block
 | 
			
		||||
		position fixed
 | 
			
		||||
		z-index 10000
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		right 0
 | 
			
		||||
		height 100%
 | 
			
		||||
		overflow auto
 | 
			
		||||
		margin 0 auto 0 auto
 | 
			
		||||
		opacity 0
 | 
			
		||||
		transform translateY(-16px)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -105,9 +105,9 @@ export default Vue.extend({
 | 
			
		||||
	computed: {
 | 
			
		||||
		draftId(): string {
 | 
			
		||||
			return this.renote
 | 
			
		||||
				? 'renote:' + this.renote.id
 | 
			
		||||
				? `renote:${this.renote.id}`
 | 
			
		||||
				: this.reply
 | 
			
		||||
					? 'reply:' + this.reply.id
 | 
			
		||||
					? `reply:${this.reply.id}`
 | 
			
		||||
					: 'note';
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -170,6 +170,8 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.focus();
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			this.focus();
 | 
			
		||||
		});
 | 
			
		||||
@@ -227,7 +229,7 @@ export default Vue.extend({
 | 
			
		||||
			navigator.geolocation.getCurrentPosition(pos => {
 | 
			
		||||
				this.geo = pos.coords;
 | 
			
		||||
			}, err => {
 | 
			
		||||
				alert('%i18n:@error%: ' + err.message);
 | 
			
		||||
				alert(`%i18n:@error%: ${err.message}`);
 | 
			
		||||
			}, {
 | 
			
		||||
					enableHighAccuracy: true
 | 
			
		||||
				});
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ export default Vue.extend({
 | 
			
		||||
		search() {
 | 
			
		||||
			const query = window.prompt('%i18n:@search%');
 | 
			
		||||
			if (query == null || query == '') return;
 | 
			
		||||
			this.$router.push('/search?q=' + encodeURIComponent(query));
 | 
			
		||||
			this.$router.push(`/search?q=${encodeURIComponent(query)}`);
 | 
			
		||||
		},
 | 
			
		||||
		onReversiInvited() {
 | 
			
		||||
			this.hasGameInvitation = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			if (!silent) {
 | 
			
		||||
				// Rewrite URL
 | 
			
		||||
				history.pushState(null, title, '/i/drive/folder/' + folder.id);
 | 
			
		||||
				history.pushState(null, title, `/i/drive/folder/${folder.id}`);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			document.title = title;
 | 
			
		||||
@@ -93,7 +93,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			if (!silent) {
 | 
			
		||||
				// Rewrite URL
 | 
			
		||||
				history.pushState(null, title, '/i/drive/file/' + file.id);
 | 
			
		||||
				history.pushState(null, title, `/i/drive/file/${file.id}`);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			document.title = title;
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ export default Vue.extend({
 | 
			
		||||
				this.user = user;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName;
 | 
			
		||||
				document.title = `${'%i18n:@followers-of%'.replace('{}', this.name)} | ${(this as any).os.instanceName}`;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onLoaded() {
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ export default Vue.extend({
 | 
			
		||||
				this.user = user;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName;
 | 
			
		||||
				document.title = `${'%i18n:@followers-of%'.replace('{}', this.name)} | ${(this as any).os.instanceName}`;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onLoaded() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,10 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(game, actualNav) {
 | 
			
		||||
			if (actualNav) {
 | 
			
		||||
				this.$router.push('/reversi/' + game.id);
 | 
			
		||||
				this.$router.push(`/reversi/${game.id}`);
 | 
			
		||||
			} else {
 | 
			
		||||
				// TODO: https://github.com/vuejs/vue-router/issues/703
 | 
			
		||||
				this.$router.push('/reversi/' + game.id);
 | 
			
		||||
				this.$router.push(`/reversi/${game.id}`);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,10 +42,6 @@
 | 
			
		||||
			<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
 | 
			
		||||
	<modal name="postForm">
 | 
			
		||||
		<mk-post-form @posted="postFormClosed" @cancel="postFormClosed"/>
 | 
			
		||||
	</modal>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -111,11 +107,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fn() {
 | 
			
		||||
			this.$modal.push('postForm');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		postFormClosed() {
 | 
			
		||||
			this.$modal.pop();
 | 
			
		||||
			(this as any).apis.post();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		saveSrc() {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:cog%%i18n:@settings%</span>
 | 
			
		||||
	<main :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
		<div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></div>
 | 
			
		||||
		<div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', `<b>${name}</b>`)"></div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<x-profile/>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ export default Vue.extend({
 | 
			
		||||
					title
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				this.$router.push('/i/lists/' + list.id);
 | 
			
		||||
				this.$router.push(`/i/lists/${list.id}`);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,7 @@ export default Vue.extend({
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				Progress.done();
 | 
			
		||||
				document.title = Vue.filter('userName')(this.user) + ' | ' + (this as any).os.instanceName;
 | 
			
		||||
				document.title = `${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export default function() {
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	ev.on('requestNotesStatsLog', id => {
 | 
			
		||||
		ev.emit('notesStatsLog:' + id, log.toArray());
 | 
			
		||||
		ev.emit(`notesStatsLog:${id}`, log.toArray());
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	process.on('exit', code => {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export default function() {
 | 
			
		||||
	const log = new Deque<any>();
 | 
			
		||||
 | 
			
		||||
	ev.on('requestServerStatsLog', x => {
 | 
			
		||||
		ev.emit('serverStatsLog:' + x.id, log.toArray().slice(0, x.length || 50));
 | 
			
		||||
		ev.emit(`serverStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	async function tick() {
 | 
			
		||||
 
 | 
			
		||||
@@ -205,13 +205,7 @@ export default class Reversi {
 | 
			
		||||
	 * 打つことができる場所を取得します
 | 
			
		||||
	 */
 | 
			
		||||
	public canPutSomewhere(color: Color): number[] {
 | 
			
		||||
		const result: number[] = [];
 | 
			
		||||
 | 
			
		||||
		this.board.forEach((x, i) => {
 | 
			
		||||
			if (this.canPut(color, i)) result.push(i);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
		return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import Logger from './misc/logger';
 | 
			
		||||
import ProgressBar from './misc/cli/progressbar';
 | 
			
		||||
import EnvironmentInfo from './misc/environmentInfo';
 | 
			
		||||
import MachineInfo from './misc/machineInfo';
 | 
			
		||||
import DependencyInfo from './misc/dependencyInfo';
 | 
			
		||||
import serverStats from './daemons/server-stats';
 | 
			
		||||
import notesStats from './daemons/notes-stats';
 | 
			
		||||
import loadConfig from './config/load';
 | 
			
		||||
@@ -116,7 +115,6 @@ async function init(): Promise<Config> {
 | 
			
		||||
	new Logger('Deps').info(`Node.js ${process.version}`);
 | 
			
		||||
	MachineInfo.show();
 | 
			
		||||
	EnvironmentInfo.show();
 | 
			
		||||
	new DependencyInfo().showAll();
 | 
			
		||||
 | 
			
		||||
	const configLogger = new Logger('Config');
 | 
			
		||||
	let config;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,26 +33,27 @@ export default function(html: string): string {
 | 
			
		||||
 | 
			
		||||
			case 'a':
 | 
			
		||||
				const txt = getText(node);
 | 
			
		||||
				const rel = node.attrs.find((x: any) => x.name == 'rel');
 | 
			
		||||
				const href = node.attrs.find((x: any) => x.name == 'href');
 | 
			
		||||
 | 
			
		||||
				// ハッシュタグ / hrefがない / txtがURL
 | 
			
		||||
				if ((rel && rel.value.match('tag') !== null) || !href || href.value == txt) {
 | 
			
		||||
					text += txt;
 | 
			
		||||
				// メンション
 | 
			
		||||
				if (txt.startsWith('@')) {
 | 
			
		||||
				} else if (txt.startsWith('@')) {
 | 
			
		||||
					const part = txt.split('@');
 | 
			
		||||
 | 
			
		||||
					if (part.length == 2) {
 | 
			
		||||
						//#region ホスト名部分が省略されているので復元する
 | 
			
		||||
						const href = new URL(node.attrs.find((x: any) => x.name == 'href').value);
 | 
			
		||||
						const acct = txt + '@' + href.hostname;
 | 
			
		||||
						const acct = `${txt}@${(new URL(href.value)).hostname}`;
 | 
			
		||||
						text += acct;
 | 
			
		||||
						break;
 | 
			
		||||
						//#endregion
 | 
			
		||||
					} else if (part.length == 3) {
 | 
			
		||||
						text += txt;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (node.childNodes) {
 | 
			
		||||
					node.childNodes.forEach((n: any) => analyze(n));
 | 
			
		||||
				// その他
 | 
			
		||||
				} else {
 | 
			
		||||
					text += `[${txt}](${href.value})`;
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,8 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
 | 
			
		||||
 | 
			
		||||
	hashtag({ document }, { hashtag }) {
 | 
			
		||||
		const a = document.createElement('a');
 | 
			
		||||
		a.href = config.url + '/tags/' + hashtag;
 | 
			
		||||
		a.textContent = '#' + hashtag;
 | 
			
		||||
		a.href = `${config.url}/tags/${hashtag}`;
 | 
			
		||||
		a.textContent = `#${hashtag}`;
 | 
			
		||||
		a.setAttribute('rel', 'tag');
 | 
			
		||||
		document.body.appendChild(a);
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
import Logger from './logger';
 | 
			
		||||
import { execSync } from 'child_process';
 | 
			
		||||
 | 
			
		||||
export default class {
 | 
			
		||||
	private logger: Logger;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.logger = new Logger('Deps');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public showAll(): void {
 | 
			
		||||
		this.show('MongoDB', 'mongo --version', x => x.match(/^MongoDB shell version:? v(.*)\r?\n/));
 | 
			
		||||
		this.show('Redis', 'redis-server --version', x => x.match(/v=([0-9\.]*)/));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public show(serviceName: string, command: string, transform: (x: string) => RegExpMatchArray): void {
 | 
			
		||||
		try {
 | 
			
		||||
			// ステータス0以外のときにexecSyncはstderrをコンソール上に出力してしまうので
 | 
			
		||||
			// プロセスからのstderrをすべて無視するように stdio オプションをセット
 | 
			
		||||
			const x = execSync(command, { stdio: ['pipe', 'pipe', 'ignore'] });
 | 
			
		||||
			const ver = transform(x.toString());
 | 
			
		||||
			if (ver != null) {
 | 
			
		||||
				this.logger.succ(`${serviceName} ${ver[1]} found`);
 | 
			
		||||
			} else {
 | 
			
		||||
				this.logger.warn(`${serviceName} not found`);
 | 
			
		||||
				this.logger.warn(`Regexp used for version check of ${serviceName} is probably messed up`);
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			this.logger.warn(`${serviceName} not found`);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -26,7 +26,7 @@ export const replacement = (match: string, key: string) => {
 | 
			
		||||
				arg == 'B' ? 'fab' :
 | 
			
		||||
				'';
 | 
			
		||||
		} else if (arg.startsWith('.')) {
 | 
			
		||||
			classes.push('fa-' + arg.substr(1));
 | 
			
		||||
			classes.push(`fa-${arg.substr(1)}`);
 | 
			
		||||
		} else if (arg.startsWith('-')) {
 | 
			
		||||
			transform = arg.substr(1).split('|').join(' ');
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -193,5 +193,10 @@ export const pack = (
 | 
			
		||||
		*/
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete _target.withoutChunks;
 | 
			
		||||
	delete _target.storage;
 | 
			
		||||
	delete _target.storageProps;
 | 
			
		||||
	delete _target.isRemote;
 | 
			
		||||
 | 
			
		||||
	resolve(_target);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,12 @@ import * as mongo from 'mongodb';
 | 
			
		||||
import db from '../db/mongodb';
 | 
			
		||||
 | 
			
		||||
const Stats = db.get<IStats>('stats');
 | 
			
		||||
Stats.dropIndex({ date: -1 }); // 後方互換性のため
 | 
			
		||||
 | 
			
		||||
// 後方互換性のため
 | 
			
		||||
Stats.dropIndex({ date: -1 } as any).catch((e: mongo.MongoError) => {
 | 
			
		||||
	if (e.code !== 27) throw e;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Stats.createIndex({ span: -1, date: -1 }, { unique: true });
 | 
			
		||||
export default Stats;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ const httpSignature = require('http-signature');
 | 
			
		||||
import parseAcct from '../../../misc/acct/parse';
 | 
			
		||||
import User, { IRemoteUser } from '../../../models/user';
 | 
			
		||||
import perform from '../../../remote/activitypub/perform';
 | 
			
		||||
import { resolvePerson } from '../../../remote/activitypub/models/person';
 | 
			
		||||
import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
 | 
			
		||||
@@ -44,11 +44,6 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
 | 
			
		||||
 | 
			
		||||
		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
 | 
			
		||||
		if (user === null) {
 | 
			
		||||
			user = await resolvePerson(activity.actor) as IRemoteUser;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// アクティビティ内のホストの検証
 | 
			
		||||
		const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase());
 | 
			
		||||
@@ -64,11 +59,26 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
			host: { $ne: null },
 | 
			
		||||
			'publicKey.id': signature.keyId
 | 
			
		||||
		}) as IRemoteUser;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
 | 
			
		||||
		if (user === null) {
 | 
			
		||||
			user = await resolvePerson(activity.actor) as IRemoteUser;
 | 
			
		||||
	// Update activityの場合は、ここで署名検証/更新処理まで実施して終了
 | 
			
		||||
	if (activity.type === 'Update') {
 | 
			
		||||
		if (activity.object && activity.object.type === 'Person') {
 | 
			
		||||
			if (user == null) {
 | 
			
		||||
				console.warn('Update activity received, but user not registed.');
 | 
			
		||||
			} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) {
 | 
			
		||||
				console.warn('Update activity received, but signature verification failed.');
 | 
			
		||||
			} else {
 | 
			
		||||
				updatePerson(activity.actor, null, activity.object);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		done();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
 | 
			
		||||
	if (user === null) {
 | 
			
		||||
		user = await resolvePerson(activity.actor) as IRemoteUser;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (user === null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -139,6 +139,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
			
		||||
			avatarId: null,
 | 
			
		||||
			bannerId: null,
 | 
			
		||||
			createdAt: Date.parse(person.published) || null,
 | 
			
		||||
			updatedAt: new Date(),
 | 
			
		||||
			description: htmlToMFM(person.summary),
 | 
			
		||||
			followersCount,
 | 
			
		||||
			followingCount,
 | 
			
		||||
@@ -215,10 +216,12 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Personの情報を更新します。
 | 
			
		||||
 *
 | 
			
		||||
 * Misskeyに対象のPersonが登録されていなければ無視します。
 | 
			
		||||
 * @param uri URI of Person
 | 
			
		||||
 * @param resolver Resolver
 | 
			
		||||
 * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します)
 | 
			
		||||
 */
 | 
			
		||||
export async function updatePerson(uri: string, resolver?: Resolver): Promise<void> {
 | 
			
		||||
export async function updatePerson(uri: string, resolver?: Resolver, hint?: object): Promise<void> {
 | 
			
		||||
	if (typeof uri !== 'string') throw 'uri is not string';
 | 
			
		||||
 | 
			
		||||
	// URIがこのサーバーを指しているならスキップ
 | 
			
		||||
@@ -236,7 +239,7 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
 | 
			
		||||
 | 
			
		||||
	if (resolver == null) resolver = new Resolver();
 | 
			
		||||
 | 
			
		||||
	const object = await resolver.resolve(uri) as any;
 | 
			
		||||
	const object = hint || await resolver.resolve(uri) as any;
 | 
			
		||||
 | 
			
		||||
	const err = validatePerson(object, uri);
 | 
			
		||||
 | 
			
		||||
@@ -290,7 +293,14 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
 | 
			
		||||
			name: person.name,
 | 
			
		||||
			url: person.url,
 | 
			
		||||
			endpoints: person.endpoints,
 | 
			
		||||
			isCat: (person as any).isCat === true ? true : false
 | 
			
		||||
			isBot: object.type == 'Service',
 | 
			
		||||
			isCat: (person as any).isCat === true ? true : false,
 | 
			
		||||
			isLocked: person.manuallyApprovesFollowers,
 | 
			
		||||
			createdAt: Date.parse(person.published) || null,
 | 
			
		||||
			publicKey: {
 | 
			
		||||
				id: person.publicKey.id,
 | 
			
		||||
				publicKeyPem: person.publicKey.publicKeyPem
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,5 +3,5 @@ import config from '../../../config';
 | 
			
		||||
export default (tag: string) => ({
 | 
			
		||||
	type: 'Hashtag',
 | 
			
		||||
	href: `${config.url}/tags/${encodeURIComponent(tag)}`,
 | 
			
		||||
	name: '#' + tag
 | 
			
		||||
	name: `#${tag}`
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								src/remote/activitypub/renderer/tombstone.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/remote/activitypub/renderer/tombstone.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
export default (id: string) => ({
 | 
			
		||||
	id,
 | 
			
		||||
	type: 'Tombstone'
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										14
									
								
								src/remote/activitypub/renderer/update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/remote/activitypub/renderer/update.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import config from '../../../config';
 | 
			
		||||
import { ILocalUser } from '../../../models/user';
 | 
			
		||||
 | 
			
		||||
export default (object: any, user: ILocalUser) => {
 | 
			
		||||
	const activity = {
 | 
			
		||||
		id: `${config.url}/users/${user._id}#updates/${new Date().getTime()}`,
 | 
			
		||||
		actor: `${config.url}/users/${user._id}`,
 | 
			
		||||
		type: 'Update',
 | 
			
		||||
		to: [ 'https://www.w3.org/ns/activitystreams#Public' ],
 | 
			
		||||
		object
 | 
			
		||||
	} as any;
 | 
			
		||||
 | 
			
		||||
	return activity;
 | 
			
		||||
};
 | 
			
		||||
@@ -22,7 +22,7 @@ const router = new Router();
 | 
			
		||||
function inbox(ctx: Router.IRouterContext) {
 | 
			
		||||
	let signature;
 | 
			
		||||
 | 
			
		||||
	ctx.req.headers.authorization = 'Signature ' + ctx.req.headers.signature;
 | 
			
		||||
	ctx.req.headers.authorization = `Signature ${ctx.req.headers.signature}`;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		signature = httpSignature.parseRequest(ctx.req, { 'headers': [] });
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ const files = glob.sync('**/*.js', {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const endpoints: IEndpoint[] = files.map(f => {
 | 
			
		||||
	const ep = require('./endpoints/' + f);
 | 
			
		||||
	const ep = require(`./endpoints/${f}`);
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		name: f.replace('.js', ''),
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ export default (params: any) => new Promise(async (res, rej) => {
 | 
			
		||||
 | 
			
		||||
	const set = {} as any;
 | 
			
		||||
 | 
			
		||||
	if (ps.disableRegistration === true || ps.disableRegistration === false) {
 | 
			
		||||
	if (typeof ps.disableRegistration === 'boolean') {
 | 
			
		||||
		set.disableRegistration = ps.disableRegistration;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import DriveFile from '../../../../models/drive-file';
 | 
			
		||||
import acceptAllFollowRequests from '../../../../services/following/requests/accept-all';
 | 
			
		||||
import { IApp } from '../../../../models/app';
 | 
			
		||||
import config from '../../../../config';
 | 
			
		||||
import { publishToFollowers } from '../../../../services/i/update';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	desc: {
 | 
			
		||||
@@ -144,4 +145,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
 | 
			
		||||
	if (user.isLocked && isLocked === false) {
 | 
			
		||||
		acceptAllFollowRequests(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// フォロワーにUpdateを配信
 | 
			
		||||
	publishToFollowers(user._id);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export default function(request: websocket.request, connection: websocket.connec
 | 
			
		||||
 | 
			
		||||
		switch (msg.type) {
 | 
			
		||||
			case 'requestLog':
 | 
			
		||||
				ev.once('notesStatsLog:' + msg.id, statsLog => {
 | 
			
		||||
				ev.once(`notesStatsLog:${msg.id}`, statsLog => {
 | 
			
		||||
					connection.send(JSON.stringify({
 | 
			
		||||
						type: 'statsLog',
 | 
			
		||||
						body: statsLog
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export default function(request: websocket.request, connection: websocket.connec
 | 
			
		||||
 | 
			
		||||
		switch (msg.type) {
 | 
			
		||||
			case 'requestLog':
 | 
			
		||||
				ev.once('serverStatsLog:' + msg.id, statsLog => {
 | 
			
		||||
				ev.once(`serverStatsLog:${msg.id}`, statsLog => {
 | 
			
		||||
					connection.send(JSON.stringify({
 | 
			
		||||
						type: 'statsLog',
 | 
			
		||||
						body: statsLog
 | 
			
		||||
 
 | 
			
		||||
@@ -196,7 +196,7 @@ router.get('/*/api/entities/*', async ctx => {
 | 
			
		||||
	const lang = ctx.params[0];
 | 
			
		||||
	const entity = ctx.params[1];
 | 
			
		||||
 | 
			
		||||
	const x = yaml.safeLoad(fs.readFileSync(path.resolve(__dirname + '/../../../src/docs/api/entities/' + entity + '.yaml'), 'utf-8')) as any;
 | 
			
		||||
	const x = yaml.safeLoad(fs.readFileSync(path.resolve(`${__dirname}/../../../src/docs/api/entities/${entity}.yaml`), 'utf-8')) as any;
 | 
			
		||||
 | 
			
		||||
	await ctx.render('../../../../src/docs/api/entities/view', Object.assign(await genVars(lang), {
 | 
			
		||||
		id: `api/entities/${entity}`,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ extends ../../../../src/client/app/base
 | 
			
		||||
 | 
			
		||||
block vars
 | 
			
		||||
	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
 | 
			
		||||
	- const url = config.url + '/@' + (user.host ? `${user.username}@${user.host}` : user.username);
 | 
			
		||||
	- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
 | 
			
		||||
	- const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
 | 
			
		||||
 | 
			
		||||
block title
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ async function save(path: string, name: string, type: string, hash: string, size
 | 
			
		||||
		const thumbnailKey = `${config.drive.prefix}/${uuid.v4()}/${name}.thumbnail.jpg`;
 | 
			
		||||
 | 
			
		||||
		const baseUrl = config.drive.baseUrl
 | 
			
		||||
			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`;
 | 
			
		||||
			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`;
 | 
			
		||||
 | 
			
		||||
		await minio.putObject(config.drive.bucket, key, fs.createReadStream(path), size, {
 | 
			
		||||
			'Content-Type': type,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								src/services/i/update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/services/i/update.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import User, { isLocalUser, isRemoteUser } from '../../models/user';
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import renderPerson from '../../remote/activitypub/renderer/person';
 | 
			
		||||
import renderUpdate from '../../remote/activitypub/renderer/update';
 | 
			
		||||
import packAp from '../../remote/activitypub/renderer';
 | 
			
		||||
import { deliver } from '../../queue';
 | 
			
		||||
 | 
			
		||||
export async function publishToFollowers(userId: mongo.ObjectID) {
 | 
			
		||||
	const user = await User.findOne({
 | 
			
		||||
		_id: userId
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const followers = await Following.find({
 | 
			
		||||
		followeeId: user._id
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const queue: string[] = [];
 | 
			
		||||
 | 
			
		||||
	// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
 | 
			
		||||
	if (isLocalUser(user)) {
 | 
			
		||||
		followers.map(following => {
 | 
			
		||||
			const follower = following._follower;
 | 
			
		||||
 | 
			
		||||
			if (isRemoteUser(follower)) {
 | 
			
		||||
				const inbox = follower.sharedInbox || follower.inbox;
 | 
			
		||||
				if (!queue.includes(inbox)) queue.push(inbox);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (queue.length > 0) {
 | 
			
		||||
			const content = packAp(renderUpdate(await renderPerson(user), user));
 | 
			
		||||
			queue.forEach(inbox => {
 | 
			
		||||
				deliver(user, content, inbox);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -5,8 +5,9 @@ import renderDelete from '../../remote/activitypub/renderer/delete';
 | 
			
		||||
import pack from '../../remote/activitypub/renderer';
 | 
			
		||||
import { deliver } from '../../queue';
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import renderNote from '../../remote/activitypub/renderer/note';
 | 
			
		||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
 | 
			
		||||
import { updateNoteStats } from '../update-chart';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 投稿を削除します。
 | 
			
		||||
@@ -32,7 +33,7 @@ export default async function(user: IUser, note: INote) {
 | 
			
		||||
 | 
			
		||||
	//#region ローカルの投稿なら削除アクティビティを配送
 | 
			
		||||
	if (isLocalUser(user)) {
 | 
			
		||||
		const content = pack(renderDelete(await renderNote(note), user));
 | 
			
		||||
		const content = pack(renderDelete(renderTombstone(`${config.url}/notes/${note._id}`), user));
 | 
			
		||||
 | 
			
		||||
		const followings = await Following.find({
 | 
			
		||||
			followeeId: user._id,
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ const constants = require('./src/const.json');
 | 
			
		||||
 | 
			
		||||
const locales = require('./locales');
 | 
			
		||||
const meta = require('./package.json');
 | 
			
		||||
const version = meta.clientVersion + '-' + rndstr({ length: 8, chars: '0-9a-z' });
 | 
			
		||||
const version = `${meta.clientVersion}-${rndstr({ length: 8, chars: '0-9a-z' })}`;
 | 
			
		||||
const codename = meta.codename;
 | 
			
		||||
 | 
			
		||||
declare var global: {
 | 
			
		||||
@@ -42,7 +42,7 @@ global['collapseSpacesReplacement'] = (html: string) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
global['base64replacement'] = (_: any, key: string) => {
 | 
			
		||||
	return fs.readFileSync(__dirname + '/src/client/' + key, 'base64');
 | 
			
		||||
	return fs.readFileSync(`${__dirname}/src/client/${key}`, 'base64');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
global['i18nReplacement'] = i18nReplacement;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user