Compare commits

...

62 Commits

Author SHA1 Message Date
syuilo
ea9b48db3c Merge branch 'develop' 2018-09-02 19:27:33 +09:00
syuilo
c145c994a9 8.22.0 2018-09-02 19:27:09 +09:00
Acid Chicken (硫酸鶏)
d033998b56 Update README.md [AUTOGEN] (#2586) 2018-09-02 19:10:12 +09:00
Aya Morisawa
3136c714bf Fix users/list/update API (#2590) 2018-09-02 19:03:00 +09:00
Aya Morisawa
c0ee134f19 Add users/lists/delete API (#2589) 2018-09-02 18:59:42 +09:00
syuilo
d15ebe5732 Improve usability 2018-09-02 18:27:43 +09:00
syuilo
ef630195fa モバイルのドライブダイアログがおかしいのを修正 & ダークモード対応 2018-09-02 18:23:47 +09:00
syuilo
e31921151e Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-02 17:56:39 +09:00
syuilo
f94992abbe 🎨 2018-09-02 17:56:20 +09:00
Aya Morisawa
b00060c09c Clean up 2018-09-02 17:44:49 +09:00
Marihachi
f6217d96d2 add an endpoint users/lists/update (#2585)
* add an endpoint users/lists/update

* add meta params

* fix packing
2018-09-02 11:25:32 +09:00
Acid Chicken (硫酸鶏)
3a6947c7ed Update README.md [AUTOGEN] 2018-09-02 09:45:28 +09:00
syuilo
0fb528ddf8 Merge branch 'develop' 2018-09-02 09:34:26 +09:00
syuilo
14c03f226d 8.21.1 2018-09-02 09:33:55 +09:00
syuilo
4f0d844b43 Remove needless properties 2018-09-02 09:31:11 +09:00
Acid Chicken (硫酸鶏)
b93395fc4c Update README.md [AUTOGEN] 2018-09-02 09:05:34 +09:00
Acid Chicken (硫酸鶏)
34eacb7e2d Update README.md [AUTOGEN] 2018-09-02 08:03:27 +09:00
mei23
0177023ead Use Tombstone for Delete 2018-09-02 08:02:43 +09:00
Aya Morisawa
a4678e45de Not check dependencies 2018-09-02 08:02:10 +09:00
xps2
f24869625e fix 2018-09-02 01:10:55 +09:00
syuilo
01beb705a2 8.21.0 2018-09-02 00:07:23 +09:00
syuilo
ce28c70c35 Clean up 2018-09-01 23:57:05 +09:00
Aya Morisawa
5e0f5c31e7 Merge branch 'string-interpolation' into develop 2018-09-01 23:30:16 +09:00
Aya Morisawa
b28dd4be52 Use typeof 2018-09-01 23:29:22 +09:00
Aya Morisawa
291beb45fc Use string interpolation 2018-09-01 23:12:51 +09:00
tamaina
ffb345ccb5 fix #2315 (#2339)
* improve MFM to html

* improve html to MFM

* missing semicolon

* missing semicolon

* fix html to MFM

タグのリンクは解除するように

* fix bug

* misssing semicolon

* Update html-to-mfm.ts

* Update html-to-mfm.ts
2018-09-01 22:45:27 +09:00
syuilo
d2abe2cd81 Merge pull request #2573 from syuilo/refactor-mfm
Refactor mfm component
2018-09-01 22:39:30 +09:00
Aya Morisawa
acffc3e522 Refactor mfm component 2018-09-01 22:38:50 +09:00
syuilo
0962e62b8c Merge pull request #2572 from syuilo/refactor-reversi-game
Refactor reversi game
2018-09-01 22:20:32 +09:00
Aya Morisawa
91ebd310b7 Refactor reversi game 2018-09-01 22:18:07 +09:00
syuilo
2974c74b4e Merge pull request #2571 from syuilo/refactor-reversi-engine
Refactor reversi engine
2018-09-01 22:11:33 +09:00
Aya Morisawa
3d24112d2d Refactor reversi engine 2018-09-01 22:09:54 +09:00
Aya Morisawa
4a977cd523 Update CHANGELOG.md 2018-09-01 21:42:07 +09:00
syuilo
4b1886990f Merge pull request #2570 from mei23/mei-0901-update2b
Implement ActivityPub Update(Person)
2018-09-01 21:38:49 +09:00
syuilo
f3499b787c 8.20.0 2018-09-01 20:49:06 +09:00
syuilo
5209a584a2 Better post form 2018-09-01 20:47:49 +09:00
mei23
57a63d38aa Send Update activity 2018-09-01 20:23:01 +09:00
mei23
3efffbcf22 Receive Update activity 2018-09-01 20:23:01 +09:00
mei23
15eaebe522 updatePersonで再割り当てを考慮する 2018-09-01 20:23:01 +09:00
mei23
eee98358ac Add missing updatePerson properties 2018-09-01 20:23:01 +09:00
mei23
795fc5e7bc Set Person.updatedAt at first 2018-09-01 20:23:01 +09:00
syuilo
70ac668474 Merge pull request #2568 from syuilo/fix-drop-index
Fix drop index
2018-09-01 19:13:49 +09:00
syuilo
1004e0d6e8 Merge pull request #2567 from syuilo/update-setup-docs
Update setup docs
2018-09-01 19:13:22 +09:00
Aya Morisawa
52aa64fcb6 Fix drop index 2018-09-01 19:12:07 +09:00
Aya Morisawa
7860d97a10 Add type annotation 2018-09-01 18:47:02 +09:00
Aya Morisawa
409b37b271 Update setup docs 2018-09-01 18:42:46 +09:00
Aya Morisawa
ad9b9964fa Update contribution guide 2018-09-01 18:15:25 +09:00
syuilo
d2b5276f43 Merge pull request #2565 from syuilo/contribution-guide
Update contribution guide
2018-09-01 18:06:19 +09:00
Aya Morisawa
7204e2a84c Update contribution guide 2018-09-01 18:05:13 +09:00
syuilo
1377fa3332 Merge pull request #2564 from syuilo/readme
Update README.md
2018-09-01 17:28:13 +09:00
Aya Morisawa
bf087bfccf Update README.md 2018-09-01 17:27:33 +09:00
Aya Morisawa
e846e3d571 Update .npmrc 2018-09-01 16:48:22 +09:00
syuilo
d646e62888 Merge pull request #2559 from acid-chicken/patch-4
Update autogen.sh
2018-09-01 16:34:57 +09:00
syuilo
c008154d18 8.19.1 2018-09-01 16:26:31 +09:00
syuilo
29bd4de26a Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-01 16:23:40 +09:00
syuilo
7559b8da6c ✌️ 2018-09-01 16:23:23 +09:00
syuilo
35218e84fc Merge pull request #2561 from syuilo/refactor-lang-loader
Refactor languages loader
2018-09-01 16:13:50 +09:00
Aya Morisawa
2c135fa2f6 Not fallback to native locale 2018-09-01 16:12:50 +09:00
Aya Morisawa
21da6bd047 Refactor languages loader 2018-09-01 16:05:10 +09:00
syuilo
f1d65a66b4 Fix bug 2018-09-01 15:38:03 +09:00
syuilo
63e2dbbb0d Fix bug 2018-09-01 15:22:28 +09:00
Acid Chicken (硫酸鶏)
a228c522f1 Update autogen.sh 2018-09-01 14:54:40 +09:00
80 changed files with 547 additions and 314 deletions

View File

@@ -1,18 +1,19 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# BEARER_TOKEN= # __MISSKEY_BEARER_TOKEN=
# CAMPAIGN_ID= # __MISSKEY_CAMPAIGN_ID=
# GITHUB_TOKEN= # __MISSKEY_GITHUB_TOKEN=
# HEAD='acid-chicken:patch-autogen' # __MISSKEY_HEAD=acid-chicken:patch-autogen
# REPO='syuilo/misskey' # __MISSKEY_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_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)/.." && \ cd "$(dirname $0)/.." && \
touch null.cache && \ touch null.cache && \
rm *.cache && \ rm *.cache && \
git checkout master && \ git checkout $__MISSKEY_BRANCH && \
git pull origin master && \ git pull origin $__MISSKEY_BRANCH && \
git pull upstream master && \ git pull upstream $__MISSKEY_BRANCH && \
git stash && \ git stash && \
git rebase -f upstream/master && \ git rebase -f upstream/$__MISSKEY_BRANCH && \
git branch patch-autogen && \ git branch patch-autogen && \
git checkout patch-autogen && \ git checkout patch-autogen && \
git reset --hard HEAD || \ git reset --hard HEAD || \
@@ -20,12 +21,12 @@ exit 1
touch patreon.md.cache && \ touch patreon.md.cache && \
rm patreon.md.cache && \ rm patreon.md.cache && \
echo '<!-- PATREON_START -->' > 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 : while :
do do
touch patreon.raw.cache && \ touch patreon.raw.cache && \
rm 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 && \ touch patreon.cache && \
rm patreon.cache && \ rm patreon.cache && \
cat patreon.raw.cache | \ cat patreon.raw.cache | \
@@ -42,31 +43,31 @@ while :
xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \ xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
echo '</tr></table>' >> patreon.md.cache || \ echo '</tr></table>' >> patreon.md.cache || \
exit 1 exit 1
NEW_URL="$(cat patreon.raw.cache | jq -r '.links.next')" new_url="$(cat patreon.raw.cache | jq -r '.links.next')"
test "$NEW_URL" = 'null' && \ test "$new_url" = 'null' && \
break || \ break || \
URL="$NEW_URL" URL="$url"
done done
IGNORE= && \ ignore= && \
echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \ echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
touch README.md && \ touch README.md && \
touch .autogen/README.md && \ touch .autogen/README.md && \
rm .autogen/README.md && \ rm .autogen/README.md && \
mv README.md .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 do
if [[ -z "$IGNORE" ]] if [[ -z "$ignore" ]]
then then
if [[ "$LINE" = '<!-- PATREON_START -->' ]] if [[ "$line" = '<!-- PATREON_START -->' ]]
then then
IGNORE='PATREON_INSIDE' ignore='PATREON_INSIDE'
else else
echo "$LINE" >> README.md echo "$line" >> README.md
fi fi
else else
if [[ "$LINE" = '<!-- PATREON_END -->' ]] if [[ "$LINE" = '<!-- PATREON_END -->' ]]
then then
IGNORE= ignore=
cat patreon.md.cache >> README.md cat patreon.md.cache >> README.md
fi fi
fi fi
@@ -80,7 +81,7 @@ test 4 -lt $(cat diff.cache | wc -l) && \
git add README.md && \ git add README.md && \
git commit -m 'Update README.md [AUTOGEN]' && \ git commit -m 'Update README.md [AUTOGEN]' && \
git push -f origin patch-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 stash
git checkout master git checkout $__MISSKEY_BRANCH
git branch -D patch-autogen git branch -D patch-autogen

View File

@@ -47,13 +47,13 @@ Please run `node cli/migration/5.0.0` before launch.
オセロがリバーシに変更されました。 オセロがリバーシに変更されました。
Othello is now Reversi. Othello is rename to Reversi.
### Migration ### Migration
MongoDBの、`othelloGames``othelloMatchings`コレクションをそれぞれ`reversiGames``reversiMatchings`にリネームしてください。 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 3.0.0
----- -----

View File

@@ -1,27 +1,27 @@
# Contribution guide # Contribution guide
:v: Misskeyへの貢献ありがとうございます。 :v: :v: Thanks for your contributions :v:
## Issueの報告 ## Issues
新機能の提案や不具合の報告は https://github.com/syuilo/misskey/issues で管理しています。 Feature suggestions and bug reports are filed in https://github.com/syuilo/misskey/issues .
Issueを作成する前に、既に同じIssueが作成されていないかご確認ください。 Before creating a new issue, please search existing issues to avoid duplication.
もし既にIssueが作成されている場合は、既存のIssueにコメントをしたりリアクションをするようお願いします。 If you find the existing issue, please add your reaction or comment to the issue.
## Issueの解決 ## Internationalization (i18n)
[pr-welcomeのラベルがついているIssue](https://github.com/syuilo/misskey/labels/pr-welcome) Please see [Translation guide](./docs/translate.en.md).
の解決を目的としたPull Requestを作成してくださると非常にありがたいです。
## 翻訳の改善 ## Localization (l10n)
ソースコード中の `%i18n:id%` という形の文字列は、言語ファイルの対応するテキストに置換されます。 Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
言語ファイルは /locales ディレクトリに存在します。
## ドキュメントの編集 ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
現在Misskeyはドキュメントが大きく不足しています。
ドキュメントは /docs ディレクトリに存在します。
## テストの追加 ## Documentation
現在Misskeyはテストが大きく不足しています。 * Documents for contributors are located in `/docs`.
テストコードは /test ディレクトリに存在します。 * Documents for instance admins are located in `/docs`.
* Documents for end users are located in `src/docs`.
## 自動テスト及び自動リリース ## Test
Travis CIで行っています。 * Test codes are located in `/test`.
設定ファイルは /.travis に存在します。
## Continuous integration
Misskey uses Travis for automated test.
Configuration files are located in `/.travis`.

View File

@@ -24,7 +24,7 @@ Why don't you take a short break from the hustle and bustle of the city, and div
* Reactions * Reactions
* User lists * User lists
* Customizable column view (called MisskeyDeck) * Customizable column view (called MisskeyDeck)
* and widgets! * Customizable widgets
* Private messages * Private messages
* ActivityPub support * ActivityPub support
@@ -32,38 +32,25 @@ and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz
:package: Create your own instance :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!** Please see [Contribution guide](./CONTRIBUTING.md).
### i18n
Please see [Translation guide](./docs/translate.en.md).
### l10n
Misskey is using Crowdin for l10n.
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)](https://crowdin.com/project/misskey)
:heart: Backers & Sponsors :heart: Backers & Sponsors
---------------------------------------------------------------- ----------------------------------------------------------------
<!-- PATREON_START --> <!-- PATREON_START -->
<table><tr> <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/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/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/1?token-time=2145916800&token-hash=DVrSdOqQq2dufgNgWZ3XMnEtl_ZAktr8Lhf2tbHKtoA%3D" alt="Axella"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Axella"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> <td><img src="https://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> <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> </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=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=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
@@ -78,20 +65,16 @@ Misskey is using Crowdin for l10n.
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/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/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> </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=5881381">Naoki Kosaka</a></td>
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td> <td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td> <td><a href="https://www.patreon.com/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/user?u=12531784">Takashi Shibuya</a></td>
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
</tr></table> </tr></table>
**Last updated:** Sun, 26 Aug 2018 08:55:06 UTC **Last updated:** Sun, 02 Sep 2018 05:30:06 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

View File

@@ -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)` 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install` 3. `npm install`
4. `npm run build` 4. `npm run build`
5. Check [ChangeLog](../CHANGELOG.md) for migration information
---------------------------------------------------------------- ----------------------------------------------------------------

View File

@@ -120,6 +120,7 @@ WantedBy=multi-user.target
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install` 3. `npm install`
4. `npm run build` 4. `npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
---------------------------------------------------------------- ----------------------------------------------------------------

View File

@@ -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... Please see [Contribution guide](../CONTRIBUTING.md) for more information.
* i18n ... please see [Translation guide](../docs/translate.en.md).
* l10n ... please visit https://crowdin.com/project/misskey

View File

@@ -5,24 +5,9 @@
const fs = require('fs'); const fs = require('fs');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const loadLang = lang => yaml.safeLoad( const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES'];
fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
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 = { module.exports = locales.reduce((a, b) => ({ ...a, ...b }));
'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;

View File

@@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "8.19.0", "version": "8.22.0",
"clientVersion": "1.0.9217", "clientVersion": "1.0.9273",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@@ -210,13 +210,12 @@
"vue": "2.5.17", "vue": "2.5.17",
"vue-chartjs": "3.4.0", "vue-chartjs": "3.4.0",
"vue-cropperjs": "2.2.1", "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-json-tree-view": "2.1.4",
"vue-loader": "15.4.1", "vue-loader": "15.4.1",
"vue-router": "3.0.1", "vue-router": "3.0.1",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vue-template-compiler": "2.5.17", "vue-template-compiler": "2.5.17",
"vue-thin-modal": "1.1.1",
"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",

View File

@@ -6,6 +6,10 @@ html
&, * &, *
cursor progress !important cursor progress !important
html
// iOS
overflow auto
body body
overflow-wrap break-word overflow-wrap break-word

View File

@@ -1,6 +1,3 @@
<template> <template>
<div>
<router-view id="app"></router-view> <router-view id="app"></router-view>
<modal-portal/>
</div>
</template> </template>

View File

@@ -80,7 +80,7 @@ export default Vue.extend({
accepted() { accepted() {
this.state = 'accepted'; this.state = 'accepted';
if (this.session.app.callbackUrl) { 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}`;
} }
} }
} }

View File

@@ -94,7 +94,7 @@
// Get salt query // Get salt query
const salt = localStorage.getItem('salt') const salt = localStorage.getItem('salt')
? '?salt=' + localStorage.getItem('salt') ? `?salt=${localStorage.getItem('salt')}`
: ''; : '';
// Load an app script // Load an app script

View File

@@ -44,11 +44,11 @@ export default class Connection extends EventEmitter {
const query = params const query = params
? Object.keys(params) ? Object.keys(params)
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
.join('&') .join('&')
: null; : 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('open', this.onOpen);
this.socket.addEventListener('close', this.onClose); this.socket.addEventListener('close', this.onClose);
this.socket.addEventListener('message', this.onMessage); this.socket.addEventListener('message', this.onMessage);

View File

@@ -125,7 +125,7 @@ export default Vue.extend({
} }
if (this.type == 'user') { if (this.type == 'user') {
const cacheKey = 'autocomplete:user:' + this.q; const cacheKey = `autocomplete:user:${this.q}`;
const cache = sessionStorage.getItem(cacheKey); const cache = sessionStorage.getItem(cacheKey);
if (cache) { if (cache) {
const users = JSON.parse(cache); const users = JSON.parse(cache);
@@ -148,7 +148,7 @@ export default Vue.extend({
this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
this.fetching = false; this.fetching = false;
} else { } else {
const cacheKey = 'autocomplete:hashtag:' + this.q; const cacheKey = `autocomplete:hashtag:${this.q}`;
const cache = sessionStorage.getItem(cacheKey); const cache = sessionStorage.getItem(cacheKey);
if (cache) { if (cache) {
const hashtags = JSON.parse(cache); const hashtags = JSON.parse(cache);

View File

@@ -57,7 +57,7 @@ export default Vue.extend({
} }
// Check internet connection // Check internet connection
fetch('https://google.com?rand=' + Math.random(), { fetch(`https://google.com?rand=${Math.random()}`, {
mode: 'no-cors' mode: 'no-cors'
}).then(() => { }).then(() => {
this.internet = true; this.internet = true;

View File

@@ -159,11 +159,9 @@ export default Vue.extend({
canPutEverywhere: this.game.settings.canPutEverywhere, canPutEverywhere: this.game.settings.canPutEverywhere,
loopedBoard: this.game.settings.loopedBoard loopedBoard: this.game.settings.loopedBoard
}); });
this.logs.forEach((log, i) => { for (const log of this.logs.slice(0, v)) {
if (i < v) {
this.o.put(log.color, log.pos); this.o.put(log.color, log.pos);
} }
});
this.$forceUpdate(); this.$forceUpdate();
} }
}, },

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="mk-menu"> <div class="onchrpzrvnoruiaenfcqvccjfuupzzwv">
<div class="backdrop" ref="backdrop" @click="close"></div> <div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ hukidasi }" ref="popover"> <div class="popover" :class="{ hukidasi }" ref="popover">
<template v-for="item in items"> <template v-for="item in items">
@@ -119,9 +119,10 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
root(isDark)
$bg-color = isDark ? #2c303c : #fff
$border-color = rgba(27, 31, 35, 0.15) $border-color = rgba(27, 31, 35, 0.15)
.mk-menu
position initial position initial
> .backdrop > .backdrop
@@ -131,14 +132,14 @@ $border-color = rgba(27, 31, 35, 0.15)
z-index 10000 z-index 10000
width 100% width 100%
height 100% height 100%
background rgba(#000, 0.1) background rgba(#000, isDark ? 0.5 : 0.1)
opacity 0 opacity 0
> .popover > .popover
position absolute position absolute
z-index 10001 z-index 10001
padding 8px 0 padding 8px 0
background #fff background $bg-color
border 1px solid $border-color border 1px solid $border-color
border-radius 4px border-radius 4px
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15) box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
@@ -172,12 +173,13 @@ $border-color = rgba(27, 31, 35, 0.15)
border-top solid $balloon-size transparent border-top solid $balloon-size transparent
border-left solid $balloon-size transparent border-left solid $balloon-size transparent
border-right solid $balloon-size transparent border-right solid $balloon-size transparent
border-bottom solid $balloon-size #fff border-bottom solid $balloon-size $bg-color
> button > button
display block display block
padding 8px 16px padding 8px 16px
width 100% width 100%
color isDark ? #d6dce2 : #111
&:hover &:hover
color $theme-color-foreground color $theme-color-foreground
@@ -191,6 +193,12 @@ $border-color = rgba(27, 31, 35, 0.15)
> div > div
margin 8px 0 margin 8px 0
height 1px height 1px
background #eee background isDark ? #1c2023 : #eee
.onchrpzrvnoruiaenfcqvccjfuupzzwv[data-darkmode]
root(true)
.onchrpzrvnoruiaenfcqvccjfuupzzwv:not([data-darkmode])
root(false)
</style> </style>

View File

@@ -205,17 +205,8 @@ export default Vue.component('misskey-flavored-markdown', {
} }
})); }));
const _els = []; // el.tag === 'br' のとき i !== 0 が保証されるため、短絡評価により els[i - 1] は配列外参照しない
els.forEach((el, i) => { const _els = els.filter((el, i) => !(el.tag === 'br' && ['div', 'pre'].includes(els[i - 1].tag)));
if (el.tag == 'br') {
if (!['div', 'pre'].includes(els[i - 1].tag)) {
_els.push(el);
}
} else {
_els.push(el);
}
});
return createElement('span', _els); return createElement('span', _els);
} }
}); });

View File

@@ -170,7 +170,7 @@ export default Vue.extend({
return; return;
} }
fetch('/url?url=' + encodeURIComponent(this.url)).then(res => { fetch(`/url?url=${encodeURIComponent(this.url)}`).then(res => {
res.json().then(info => { res.json().then(info => {
if (info.url == null) return; if (info.url == null) return;
this.title = info.title; this.title = info.title;

View File

@@ -191,7 +191,7 @@ class Autocomplete {
const acct = renderAcct(value); const acct = renderAcct(value);
// 挿入 // 挿入
this.text = trimmedBefore + '@' + acct + ' ' + after; this.text = `${trimmedBefore}@${acct} ${after}`;
// キャレットを戻す // キャレットを戻す
this.vm.$nextTick(() => { this.vm.$nextTick(() => {
@@ -207,7 +207,7 @@ class Autocomplete {
const after = source.substr(caret); const after = source.substr(caret);
// 挿入 // 挿入
this.text = trimmedBefore + '#' + value + ' ' + after; this.text = `${trimmedBefore}#${value} ${after}`;
// キャレットを戻す // キャレットを戻す
this.vm.$nextTick(() => { this.vm.$nextTick(() => {

View File

@@ -1,5 +1,5 @@
import Vue from 'vue'; import Vue from 'vue';
Vue.filter('notePage', note => { Vue.filter('notePage', note => {
return '/notes/' + note.id; return `/notes/${note.id}`;
}); });

View File

@@ -11,5 +11,5 @@ Vue.filter('userName', user => {
}); });
Vue.filter('userPage', (user, path?) => { Vue.filter('userPage', (user, path?) => {
return '/@' + Vue.filter('acct')(user) + (path ? '/' + path : ''); return `/@${Vue.filter('acct')(user)}${(path ? `/${path}` : '')}`;
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="syxhndwprovvuqhmyvveewmbqayniwkv" v-if="!fetching" :data-darkmode="$store.state.device.darkmode"> <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> <main>
<div class="banner" :style="bannerStyle"></div> <div class="banner" :style="bannerStyle"></div>

View File

@@ -163,7 +163,7 @@ export default Vue.extend({
}); });
break; break;
default: default:
alert('%i18n:@unhandled-error% ' + err); alert(`%i18n:@unhandled-error% ${err}`);
} }
}); });
} }

View File

@@ -323,7 +323,7 @@ export default Vue.extend({
}); });
break; break;
default: default:
alert('%i18n:@unhandled-error% ' + err); alert(`%i18n:@unhandled-error% ${err}`);
} }
}); });
} }
@@ -404,7 +404,7 @@ export default Vue.extend({
folder: folder folder: folder
}); });
} else { } else {
window.open(url + '/i/drive/folder/' + folder.id, window.open(`${url}/i/drive/folder/${folder.id}`,
'drive_window', 'drive_window',
'height=500, width=800'); 'height=500, width=800');
} }

View File

@@ -48,7 +48,7 @@ export default Vue.extend({
const mouseY = e.clientY - rect.top; const mouseY = e.clientY - rect.top;
const xp = mouseX / this.$el.offsetWidth * 100; const xp = mouseX / this.$el.offsetWidth * 100;
const yp = mouseY / this.$el.offsetHeight * 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}")`; this.$el.style.backgroundImage = `url("${this.image.url}")`;
}, },

View File

@@ -110,9 +110,9 @@ export default Vue.extend({
computed: { computed: {
draftId(): string { draftId(): string {
return this.renote return this.renote
? 'renote:' + this.renote.id ? `renote:${this.renote.id}`
: this.reply : this.reply
? 'reply:' + this.reply.id ? `reply:${this.reply.id}`
: 'note'; : 'note';
}, },
@@ -313,7 +313,7 @@ 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('%i18n:@error%: ' + err.message); alert(`%i18n:@error%: ${err.message}`);
}, { }, {
enableHighAccuracy: true enableHighAccuracy: true
}); });

View File

@@ -31,7 +31,7 @@ export default Vue.extend({
const title = folder.name + ' | %i18n:@title%'; const title = folder.name + ' | %i18n:@title%';
// Rewrite URL // Rewrite URL
history.pushState(null, title, '/i/drive/folder/' + folder.id); history.pushState(null, title, `/i/drive/folder/${folder.id}`);
document.title = title; document.title = title;
} }

View File

@@ -16,10 +16,10 @@ export default Vue.extend({
methods: { methods: {
nav(game, actualNav) { nav(game, actualNav) {
if (actualNav) { if (actualNav) {
this.$router.push('/reversi/' + game.id); this.$router.push(`/reversi/${game.id}`);
} else { } else {
// TODO: https://github.com/vuejs/vue-router/issues/703 // TODO: https://github.com/vuejs/vue-router/issues/703
this.$router.push('/reversi/' + game.id); this.$router.push(`/reversi/${game.id}`);
} }
} }
} }

View File

@@ -46,7 +46,7 @@ export default Vue.extend({
this.user = user; this.user = user;
this.fetching = false; this.fetching = false;
document.title = 'メッセージ: ' + getUserName(this.user); document.title = `メッセージ: ${getUserName(this.user)}`;
Progress.done(); Progress.done();
}); });

View File

@@ -37,6 +37,7 @@
<div class="tl"> <div class="tl">
<mk-welcome-timeline :max="20"/> <mk-welcome-timeline :max="20"/>
</div> </div>
<modal name="signup" width="500px" height="auto" scrollable> <modal name="signup" width="500px" height="auto" scrollable>
<header :class="$style.signupFormHeader">%i18n:@signup%</header> <header :class="$style.signupFormHeader">%i18n:@signup%</header>
<mk-signup :class="$style.signupForm"/> <mk-signup :class="$style.signupForm"/>

View File

@@ -5,14 +5,12 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import VModal from 'vue-js-modal';
import * as TreeView from 'vue-json-tree-view'; import * as TreeView from 'vue-json-tree-view';
import VAnimateCss from 'v-animate-css'; import VAnimateCss from 'v-animate-css';
import Element from 'element-ui'; import Element from 'element-ui';
import ElementLocaleEn from 'element-ui/lib/locale/lang/en'; import ElementLocaleEn from 'element-ui/lib/locale/lang/en';
import ElementLocaleJa from 'element-ui/lib/locale/lang/ja'; import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
import VueThinModal from 'vue-thin-modal'; import VModal from 'vue-js-modal';
import 'vue-thin-modal/dist/vue-thin-modal.css';
import App from './app.vue'; import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update'; import checkForUpdate from './common/scripts/check-for-update';
@@ -28,13 +26,10 @@ switch (lang) {
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(VueRouter); Vue.use(VueRouter);
Vue.use(VModal);
Vue.use(TreeView); Vue.use(TreeView);
Vue.use(VAnimateCss); Vue.use(VAnimateCss);
Vue.use(Element, { locale: elementLocale }); Vue.use(Element, { locale: elementLocale });
Vue.use(VueThinModal, { Vue.use(VModal);
autoMountPortal: false
});
// Register global directives // Register global directives
require('./common/views/directives'); require('./common/views/directives');

View 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();
};

View File

@@ -14,6 +14,7 @@ import chooseDriveFolder from './api/choose-drive-folder';
import chooseDriveFile from './api/choose-drive-file'; import chooseDriveFile from './api/choose-drive-file';
import dialog from './api/dialog'; import dialog from './api/dialog';
import input from './api/input'; import input from './api/input';
import post from './api/post';
import notify from './api/notify'; import notify from './api/notify';
import MkIndex from './views/pages/index.vue'; import MkIndex from './views/pages/index.vue';
@@ -90,7 +91,7 @@ init((launch) => {
chooseDriveFile, chooseDriveFile,
dialog: dialog(os), dialog: dialog(os),
input, input,
post: () => alert('deprecated'), post: post(os),
notify notify
})); }));
}, true); }, true);

View File

@@ -17,13 +17,3 @@ body
display flex display flex
flex-direction column flex-direction column
min-height 100% 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

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="mk-drive-file-chooser"> <div class="cdxzvcfawjxdyxsekbxbfgtplebnoneb">
<div class="body"> <div class="body">
<header> <header>
<h1>%i18n:@select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1> <h1>%i18n:@select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
<button class="close" @click="cancel">%fa:times%</button> <button class="close" @click="cancel">%fa:times%</button>
<button v-if="multiple" class="ok" @click="ok">%fa:check%</button> <button v-if="multiple" class="ok" @click="ok">%fa:check%</button>
</header> </header>
<mk-drive ref="browser" <mk-drive class="drive" ref="browser"
:select-file="true" :select-file="true"
:multiple="multiple" :multiple="multiple"
@change-selection="onChangeSelection" @change-selection="onChangeSelection"
@@ -46,7 +46,7 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-drive-file-chooser root(isDark)
position fixed position fixed
z-index 20000 z-index 20000
top 0 top 0
@@ -59,10 +59,11 @@ export default Vue.extend({
> .body > .body
width 100% width 100%
height 100% height 100%
background #fff background isDark ? #282c37 : #fff
> header > header
border-bottom solid 1px #eee border-bottom solid 1px isDark ? #1b1f29 : #eee
color isDark ? #fff : #111
> h1 > h1
margin 0 margin 0
@@ -90,9 +91,15 @@ export default Vue.extend({
line-height 42px line-height 42px
width 42px width 42px
> .mk-drive > .drive
height calc(100% - 42px) height calc(100% - 42px)
overflow scroll overflow scroll
-webkit-overflow-scrolling touch -webkit-overflow-scrolling touch
.cdxzvcfawjxdyxsekbxbfgtplebnoneb[data-darkmode]
root(true)
.cdxzvcfawjxdyxsekbxbfgtplebnoneb:not([data-darkmode])
root(false)
</style> </style>

View File

@@ -75,13 +75,6 @@
<div class="replies" v-if="!compact"> <div class="replies" v-if="!compact">
<x-sub v-for="note in replies" :key="note.id" :note="note"/> <x-sub v-for="note in replies" :key="note.id" :note="note"/>
</div> </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> </div>
</template> </template>
@@ -192,19 +185,15 @@ export default Vue.extend({
}, },
reply() { reply() {
this.$modal.push('replyForm'); (this as any).apis.post({
}, reply: this.p
});
replyFormClosed() {
this.$modal.pop();
}, },
renote() { renote() {
this.$modal.push('renoteForm'); (this as any).apis.post({
}, renote: this.p
});
renoteFormClosed() {
this.$modal.pop();
}, },
react() { react() {

View File

@@ -60,13 +60,6 @@
</footer> </footer>
</div> </div>
</article> </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> </div>
</template> </template>
@@ -202,19 +195,15 @@ export default Vue.extend({
}, },
reply() { reply() {
this.$modal.push('replyForm'); (this as any).apis.post({
}, reply: this.p
});
replyFormClosed() {
this.$modal.pop();
}, },
renote() { renote() {
this.$modal.push('renoteForm'); (this as any).apis.post({
}, renote: this.p
});
renoteFormClosed() {
this.$modal.pop();
}, },
react() { react() {
@@ -482,10 +471,6 @@ root(isDark)
&.reacted &.reacted
color $theme-color color $theme-color
&.menu
@media (max-width 350px)
display none
.note[data-darkmode] .note[data-darkmode]
root(true) root(true)

View 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>

View File

@@ -105,9 +105,9 @@ export default Vue.extend({
computed: { computed: {
draftId(): string { draftId(): string {
return this.renote return this.renote
? 'renote:' + this.renote.id ? `renote:${this.renote.id}`
: this.reply : this.reply
? 'reply:' + this.reply.id ? `reply:${this.reply.id}`
: 'note'; : 'note';
}, },
@@ -170,6 +170,8 @@ export default Vue.extend({
}); });
} }
this.focus();
this.$nextTick(() => { this.$nextTick(() => {
this.focus(); this.focus();
}); });
@@ -227,7 +229,7 @@ export default Vue.extend({
navigator.geolocation.getCurrentPosition(pos => { navigator.geolocation.getCurrentPosition(pos => {
this.geo = pos.coords; this.geo = pos.coords;
}, err => { }, err => {
alert('%i18n:@error%: ' + err.message); alert(`%i18n:@error%: ${err.message}`);
}, { }, {
enableHighAccuracy: true enableHighAccuracy: true
}); });

View File

@@ -82,7 +82,7 @@ export default Vue.extend({
search() { search() {
const query = window.prompt('%i18n:@search%'); const query = window.prompt('%i18n:@search%');
if (query == null || query == '') return; if (query == null || query == '') return;
this.$router.push('/search?q=' + encodeURIComponent(query)); this.$router.push(`/search?q=${encodeURIComponent(query)}`);
}, },
onReversiInvited() { onReversiInvited() {
this.hasGameInvitation = true; this.hasGameInvitation = true;

View File

@@ -80,7 +80,7 @@ export default Vue.extend({
if (!silent) { if (!silent) {
// Rewrite URL // Rewrite URL
history.pushState(null, title, '/i/drive/folder/' + folder.id); history.pushState(null, title, `/i/drive/folder/${folder.id}`);
} }
document.title = title; document.title = title;
@@ -93,7 +93,7 @@ export default Vue.extend({
if (!silent) { if (!silent) {
// Rewrite URL // Rewrite URL
history.pushState(null, title, '/i/drive/file/' + file.id); history.pushState(null, title, `/i/drive/file/${file.id}`);
} }
document.title = title; document.title = title;

View File

@@ -49,7 +49,7 @@ export default Vue.extend({
this.user = user; this.user = user;
this.fetching = false; 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() { onLoaded() {

View File

@@ -48,7 +48,7 @@ export default Vue.extend({
this.user = user; this.user = user;
this.fetching = false; 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() { onLoaded() {

View File

@@ -16,10 +16,10 @@ export default Vue.extend({
methods: { methods: {
nav(game, actualNav) { nav(game, actualNav) {
if (actualNav) { if (actualNav) {
this.$router.push('/reversi/' + game.id); this.$router.push(`/reversi/${game.id}`);
} else { } else {
// TODO: https://github.com/vuejs/vue-router/issues/703 // TODO: https://github.com/vuejs/vue-router/issues/703
this.$router.push('/reversi/' + game.id); this.$router.push(`/reversi/${game.id}`);
} }
} }
} }

View File

@@ -42,10 +42,6 @@
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/> <mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div> </div>
</main> </main>
<modal name="postForm">
<mk-post-form @posted="postFormClosed" @cancel="postFormClosed"/>
</modal>
</mk-ui> </mk-ui>
</template> </template>
@@ -111,11 +107,7 @@ export default Vue.extend({
methods: { methods: {
fn() { fn() {
this.$modal.push('postForm'); (this as any).apis.post();
},
postFormClosed() {
this.$modal.pop();
}, },
saveSrc() { saveSrc() {

View File

@@ -2,7 +2,7 @@
<mk-ui> <mk-ui>
<span slot="header">%fa:cog%%i18n:@settings%</span> <span slot="header">%fa:cog%%i18n:@settings%</span>
<main :data-darkmode="$store.state.device.darkmode"> <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> <div>
<x-profile/> <x-profile/>

View File

@@ -43,7 +43,7 @@ export default Vue.extend({
title title
}); });
this.$router.push('/i/lists/' + list.id); this.$router.push(`/i/lists/${list.id}`);
}); });
} }
} }

View File

@@ -107,7 +107,7 @@ export default Vue.extend({
this.fetching = false; this.fetching = false;
Progress.done(); 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}`;
}); });
} }
} }

View File

@@ -16,7 +16,7 @@ export default function() {
}); });
ev.on('requestNotesStatsLog', id => { ev.on('requestNotesStatsLog', id => {
ev.emit('notesStatsLog:' + id, log.toArray()); ev.emit(`notesStatsLog:${id}`, log.toArray());
}); });
process.on('exit', code => { process.on('exit', code => {

View File

@@ -16,7 +16,7 @@ export default function() {
const log = new Deque<any>(); const log = new Deque<any>();
ev.on('requestServerStatsLog', x => { 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() { async function tick() {

View File

@@ -205,13 +205,7 @@ export default class Reversi {
* 打つことができる場所を取得します * 打つことができる場所を取得します
*/ */
public canPutSomewhere(color: Color): number[] { public canPutSomewhere(color: Color): number[] {
const result: number[] = []; return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
this.board.forEach((x, i) => {
if (this.canPut(color, i)) result.push(i);
});
return result;
} }
/** /**

View File

@@ -20,7 +20,6 @@ import Logger from './misc/logger';
import ProgressBar from './misc/cli/progressbar'; import ProgressBar from './misc/cli/progressbar';
import EnvironmentInfo from './misc/environmentInfo'; import EnvironmentInfo from './misc/environmentInfo';
import MachineInfo from './misc/machineInfo'; import MachineInfo from './misc/machineInfo';
import DependencyInfo from './misc/dependencyInfo';
import serverStats from './daemons/server-stats'; import serverStats from './daemons/server-stats';
import notesStats from './daemons/notes-stats'; import notesStats from './daemons/notes-stats';
import loadConfig from './config/load'; import loadConfig from './config/load';
@@ -116,7 +115,6 @@ async function init(): Promise<Config> {
new Logger('Deps').info(`Node.js ${process.version}`); new Logger('Deps').info(`Node.js ${process.version}`);
MachineInfo.show(); MachineInfo.show();
EnvironmentInfo.show(); EnvironmentInfo.show();
new DependencyInfo().showAll();
const configLogger = new Logger('Config'); const configLogger = new Logger('Config');
let config; let config;

View File

@@ -33,26 +33,27 @@ export default function(html: string): string {
case 'a': case 'a':
const txt = getText(node); 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('@'); const part = txt.split('@');
if (part.length == 2) { if (part.length == 2) {
//#region ホスト名部分が省略されているので復元する //#region ホスト名部分が省略されているので復元する
const href = new URL(node.attrs.find((x: any) => x.name == 'href').value); const acct = `${txt}@${(new URL(href.value)).hostname}`;
const acct = txt + '@' + href.hostname;
text += acct; text += acct;
break;
//#endregion //#endregion
} else if (part.length == 3) { } else if (part.length == 3) {
text += txt; text += txt;
break;
} }
} // その他
} else {
if (node.childNodes) { text += `[${txt}](${href.value})`;
node.childNodes.forEach((n: any) => analyze(n));
} }
break; break;

View File

@@ -44,8 +44,8 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
hashtag({ document }, { hashtag }) { hashtag({ document }, { hashtag }) {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = config.url + '/tags/' + hashtag; a.href = `${config.url}/tags/${hashtag}`;
a.textContent = '#' + hashtag; a.textContent = `#${hashtag}`;
a.setAttribute('rel', 'tag'); a.setAttribute('rel', 'tag');
document.body.appendChild(a); document.body.appendChild(a);
}, },

View File

@@ -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`);
}
}
}

View File

@@ -26,7 +26,7 @@ export const replacement = (match: string, key: string) => {
arg == 'B' ? 'fab' : arg == 'B' ? 'fab' :
''; '';
} else if (arg.startsWith('.')) { } else if (arg.startsWith('.')) {
classes.push('fa-' + arg.substr(1)); classes.push(`fa-${arg.substr(1)}`);
} else if (arg.startsWith('-')) { } else if (arg.startsWith('-')) {
transform = arg.substr(1).split('|').join(' '); transform = arg.substr(1).split('|').join(' ');
} else { } else {

View File

@@ -193,5 +193,10 @@ export const pack = (
*/ */
} }
delete _target.withoutChunks;
delete _target.storage;
delete _target.storageProps;
delete _target.isRemote;
resolve(_target); resolve(_target);
}); });

View File

@@ -2,7 +2,12 @@ import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
const Stats = db.get<IStats>('stats'); 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 }); Stats.createIndex({ span: -1, date: -1 }, { unique: true });
export default Stats; export default Stats;

View File

@@ -5,7 +5,7 @@ const httpSignature = require('http-signature');
import parseAcct from '../../../misc/acct/parse'; import parseAcct from '../../../misc/acct/parse';
import User, { IRemoteUser } from '../../../models/user'; import User, { IRemoteUser } from '../../../models/user';
import perform from '../../../remote/activitypub/perform'; 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 { toUnicode } from 'punycode';
import { URL } from 'url'; 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; user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
if (user === null) {
user = await resolvePerson(activity.actor) as IRemoteUser;
}
} else { } else {
// アクティビティ内のホストの検証 // アクティビティ内のホストの検証
const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase()); const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase());
@@ -64,12 +59,27 @@ export default async (job: bq.Job, done: any): Promise<void> => {
host: { $ne: null }, host: { $ne: null },
'publicKey.id': signature.keyId 'publicKey.id': signature.keyId
}) as IRemoteUser; }) 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サーバーに登録されていなかったら登録する // アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
if (user === null) { if (user === null) {
user = await resolvePerson(activity.actor) as IRemoteUser; user = await resolvePerson(activity.actor) as IRemoteUser;
} }
}
if (user === null) { if (user === null) {
done(new Error('failed to resolve user')); done(new Error('failed to resolve user'));

View File

@@ -139,6 +139,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
avatarId: null, avatarId: null,
bannerId: null, bannerId: null,
createdAt: Date.parse(person.published) || null, createdAt: Date.parse(person.published) || null,
updatedAt: new Date(),
description: htmlToMFM(person.summary), description: htmlToMFM(person.summary),
followersCount, followersCount,
followingCount, followingCount,
@@ -215,10 +216,12 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
/** /**
* Personの情報を更新します。 * Personの情報を更新します。
*
* Misskeyに対象の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'; if (typeof uri !== 'string') throw 'uri is not string';
// URIがこのサーバーを指しているならスキップ // URIがこのサーバーを指しているならスキップ
@@ -236,7 +239,7 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
if (resolver == null) resolver = new Resolver(); 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); const err = validatePerson(object, uri);
@@ -290,7 +293,14 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
name: person.name, name: person.name,
url: person.url, url: person.url,
endpoints: person.endpoints, 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
},
} }
}); });
} }

View File

@@ -3,5 +3,5 @@ import config from '../../../config';
export default (tag: string) => ({ export default (tag: string) => ({
type: 'Hashtag', type: 'Hashtag',
href: `${config.url}/tags/${encodeURIComponent(tag)}`, href: `${config.url}/tags/${encodeURIComponent(tag)}`,
name: '#' + tag name: `#${tag}`
}); });

View File

@@ -0,0 +1,4 @@
export default (id: string) => ({
id,
type: 'Tombstone'
});

View 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;
};

View File

@@ -22,7 +22,7 @@ const router = new Router();
function inbox(ctx: Router.IRouterContext) { function inbox(ctx: Router.IRouterContext) {
let signature; let signature;
ctx.req.headers.authorization = 'Signature ' + ctx.req.headers.signature; ctx.req.headers.authorization = `Signature ${ctx.req.headers.signature}`;
try { try {
signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); signature = httpSignature.parseRequest(ctx.req, { 'headers': [] });

View File

@@ -79,7 +79,7 @@ const files = glob.sync('**/*.js', {
}); });
const endpoints: IEndpoint[] = files.map(f => { const endpoints: IEndpoint[] = files.map(f => {
const ep = require('./endpoints/' + f); const ep = require(`./endpoints/${f}`);
return { return {
name: f.replace('.js', ''), name: f.replace('.js', ''),

View File

@@ -25,7 +25,7 @@ export default (params: any) => new Promise(async (res, rej) => {
const set = {} as any; const set = {} as any;
if (ps.disableRegistration === true || ps.disableRegistration === false) { if (typeof ps.disableRegistration === 'boolean') {
set.disableRegistration = ps.disableRegistration; set.disableRegistration = ps.disableRegistration;
} }

View File

@@ -5,6 +5,7 @@ import DriveFile from '../../../../models/drive-file';
import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; import acceptAllFollowRequests from '../../../../services/following/requests/accept-all';
import { IApp } from '../../../../models/app'; import { IApp } from '../../../../models/app';
import config from '../../../../config'; import config from '../../../../config';
import { publishToFollowers } from '../../../../services/i/update';
export const meta = { export const meta = {
desc: { desc: {
@@ -144,4 +145,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
if (user.isLocked && isLocked === false) { if (user.isLocked && isLocked === false) {
acceptAllFollowRequests(user); acceptAllFollowRequests(user);
} }
// フォロワーにUpdateを配信
publishToFollowers(user._id);
}); });

View File

@@ -0,0 +1,43 @@
import $ from 'cafy';
import ID from '../../../../../misc/cafy-id';
import UserList, { deleteUserList } from '../../../../../models/user-list';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
desc: {
'ja-JP': '指定したユーザーリストを削除します。',
'en-US': 'Delete a user list'
},
requireCredential: true,
kind: 'account-write',
params: {
listId: $.type(ID).note({
desc: {
'ja-JP': '対象となるユーザーリストのID',
'en-US': 'ID of target user list'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const userList = await UserList.findOne({
_id: ps.listId,
userId: user._id
});
if (userList == null) {
return rej('list not found');
}
deleteUserList(userList);
res();
});

View File

@@ -0,0 +1,56 @@
import $ from 'cafy';
import ID from '../../../../../misc/cafy-id';
import UserList, { pack } from '../../../../../models/user-list';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
desc: {
'ja-JP': '指定したユーザーリストを更新します。',
'en-US': 'Update a user list'
},
requireCredential: true,
kind: 'account-write',
params: {
listId: $.type(ID).note({
desc: {
'ja-JP': '対象となるユーザーリストのID',
'en-US': 'ID of target user list'
}
}),
title: $.str.range(1, 100).note({
desc: {
'ja-JP': 'このユーザーリストの名前',
'en-US': 'name of this user list'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
// Fetch the list
const userList = await UserList.findOne({
_id: ps.listId,
userId: user._id
});
if (userList == null) {
return rej('list not found');
}
// update
await UserList.update({ _id: userList._id }, {
$set: {
title: ps.title
}
});
// Response
res(await pack(userList._id));
});

View File

@@ -16,7 +16,7 @@ export default function(request: websocket.request, connection: websocket.connec
switch (msg.type) { switch (msg.type) {
case 'requestLog': case 'requestLog':
ev.once('notesStatsLog:' + msg.id, statsLog => { ev.once(`notesStatsLog:${msg.id}`, statsLog => {
connection.send(JSON.stringify({ connection.send(JSON.stringify({
type: 'statsLog', type: 'statsLog',
body: statsLog body: statsLog

View File

@@ -16,7 +16,7 @@ export default function(request: websocket.request, connection: websocket.connec
switch (msg.type) { switch (msg.type) {
case 'requestLog': case 'requestLog':
ev.once('serverStatsLog:' + msg.id, statsLog => { ev.once(`serverStatsLog:${msg.id}`, statsLog => {
connection.send(JSON.stringify({ connection.send(JSON.stringify({
type: 'statsLog', type: 'statsLog',
body: statsLog body: statsLog

View File

@@ -196,7 +196,7 @@ router.get('/*/api/entities/*', async ctx => {
const lang = ctx.params[0]; const lang = ctx.params[0];
const entity = ctx.params[1]; 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), { await ctx.render('../../../../src/docs/api/entities/view', Object.assign(await genVars(lang), {
id: `api/entities/${entity}`, id: `api/entities/${entity}`,

View File

@@ -2,7 +2,7 @@ extends ../../../../src/client/app/base
block vars block vars
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - 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; - const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
block title block title

View File

@@ -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 thumbnailKey = `${config.drive.prefix}/${uuid.v4()}/${name}.thumbnail.jpg`;
const baseUrl = config.drive.baseUrl 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, { await minio.putObject(config.drive.bucket, key, fs.createReadStream(path), size, {
'Content-Type': type, 'Content-Type': type,

38
src/services/i/update.ts Normal file
View 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);
});
}
}
}

View File

@@ -5,8 +5,9 @@ import renderDelete from '../../remote/activitypub/renderer/delete';
import pack from '../../remote/activitypub/renderer'; import pack from '../../remote/activitypub/renderer';
import { deliver } from '../../queue'; import { deliver } from '../../queue';
import Following from '../../models/following'; 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 { updateNoteStats } from '../update-chart';
import config from '../../config';
/** /**
* 投稿を削除します。 * 投稿を削除します。
@@ -32,7 +33,7 @@ export default async function(user: IUser, note: INote) {
//#region ローカルの投稿なら削除アクティビティを配送 //#region ローカルの投稿なら削除アクティビティを配送
if (isLocalUser(user)) { 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({ const followings = await Following.find({
followeeId: user._id, followeeId: user._id,

View File

@@ -20,7 +20,7 @@ const constants = require('./src/const.json');
const locales = require('./locales'); const locales = require('./locales');
const meta = require('./package.json'); 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; const codename = meta.codename;
declare var global: { declare var global: {
@@ -42,7 +42,7 @@ global['collapseSpacesReplacement'] = (html: string) => {
}; };
global['base64replacement'] = (_: any, key: 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; global['i18nReplacement'] = i18nReplacement;