Compare commits
	
		
			67 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ea9b48db3c | ||
|   | c145c994a9 | ||
|   | d033998b56 | ||
|   | 3136c714bf | ||
|   | c0ee134f19 | ||
|   | d15ebe5732 | ||
|   | ef630195fa | ||
|   | e31921151e | ||
|   | f94992abbe | ||
|   | b00060c09c | ||
|   | f6217d96d2 | ||
|   | 3a6947c7ed | ||
|   | 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 | ||
|   | 7e35048829 | ||
|   | 4d6b9f62e5 | 
| @@ -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`. | ||||
|   | ||||
							
								
								
									
										29
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								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,38 +32,25 @@ 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> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/1?token-time=2145916800&token-hash=DVrSdOqQq2dufgNgWZ3XMnEtl_ZAktr8Lhf2tbHKtoA%3D" alt="Axella"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Axella"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> | ||||
| <td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12731202">negao</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||
| @@ -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/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td> | ||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | ||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||
| <td><a href="https://www.patreon.com/fujishan">fujishan</a></td> | ||||
| </tr></table> | ||||
|  | ||||
| **Last updated:** Sun, 26 Aug 2018 08:55:06 UTC | ||||
| **Last updated:** Sun, 02 Sep 2018 05:30:06 UTC | ||||
| <!-- PATREON_END --> | ||||
|  | ||||
| :four_leaf_clover: Copyright | ||||
|   | ||||
| @@ -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.17.0", | ||||
| 	"clientVersion": "1.0.9212", | ||||
| 	"version": "8.22.0", | ||||
| 	"clientVersion": "1.0.9273", | ||||
| 	"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(); | ||||
| 		} | ||||
| 	}, | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="mk-menu"> | ||||
| <div class="onchrpzrvnoruiaenfcqvccjfuupzzwv"> | ||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||
| 	<div class="popover" :class="{ hukidasi }" ref="popover"> | ||||
| 		<template v-for="item in items"> | ||||
| @@ -119,9 +119,10 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| $border-color = rgba(27, 31, 35, 0.15) | ||||
| root(isDark) | ||||
| 	$bg-color = isDark ? #2c303c : #fff | ||||
| 	$border-color = rgba(27, 31, 35, 0.15) | ||||
|  | ||||
| .mk-menu | ||||
| 	position initial | ||||
|  | ||||
| 	> .backdrop | ||||
| @@ -131,14 +132,14 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 		z-index 10000 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.1) | ||||
| 		background rgba(#000, isDark ? 0.5 : 0.1) | ||||
| 		opacity 0 | ||||
|  | ||||
| 	> .popover | ||||
| 		position absolute | ||||
| 		z-index 10001 | ||||
| 		padding 8px 0 | ||||
| 		background #fff | ||||
| 		background $bg-color | ||||
| 		border 1px solid $border-color | ||||
| 		border-radius 4px | ||||
| 		box-shadow 0 3px 12px rgba(27, 31, 35, 0.15) | ||||
| @@ -172,12 +173,13 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 				border-top solid $balloon-size transparent | ||||
| 				border-left solid $balloon-size transparent | ||||
| 				border-right solid $balloon-size transparent | ||||
| 				border-bottom solid $balloon-size #fff | ||||
| 				border-bottom solid $balloon-size $bg-color | ||||
|  | ||||
| 		> button | ||||
| 			display block | ||||
| 			padding 8px 16px | ||||
| 			width 100% | ||||
| 			color isDark ? #d6dce2 : #111 | ||||
|  | ||||
| 			&:hover | ||||
| 				color $theme-color-foreground | ||||
| @@ -191,6 +193,12 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 		> div | ||||
| 			margin 8px 0 | ||||
| 			height 1px | ||||
| 			background #eee | ||||
| 			background isDark ? #1c2023 : #eee | ||||
|  | ||||
| .onchrpzrvnoruiaenfcqvccjfuupzzwv[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .onchrpzrvnoruiaenfcqvccjfuupzzwv:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| <template> | ||||
| <div class="mk-drive-file-chooser"> | ||||
| <div class="cdxzvcfawjxdyxsekbxbfgtplebnoneb"> | ||||
| 	<div class="body"> | ||||
| 		<header> | ||||
| 			<h1>%i18n:@select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1> | ||||
| 			<button class="close" @click="cancel">%fa:times%</button> | ||||
| 			<button v-if="multiple" class="ok" @click="ok">%fa:check%</button> | ||||
| 		</header> | ||||
| 		<mk-drive ref="browser" | ||||
| 		<mk-drive class="drive" ref="browser" | ||||
| 			:select-file="true" | ||||
| 			:multiple="multiple" | ||||
| 			@change-selection="onChangeSelection" | ||||
| @@ -46,7 +46,7 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-drive-file-chooser | ||||
| root(isDark) | ||||
| 	position fixed | ||||
| 	z-index 20000 | ||||
| 	top 0 | ||||
| @@ -59,10 +59,11 @@ export default Vue.extend({ | ||||
| 	> .body | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background #fff | ||||
| 		background isDark ? #282c37 : #fff | ||||
|  | ||||
| 		> header | ||||
| 			border-bottom solid 1px #eee | ||||
| 			border-bottom solid 1px isDark ? #1b1f29 : #eee | ||||
| 			color isDark ? #fff : #111 | ||||
|  | ||||
| 			> h1 | ||||
| 				margin 0 | ||||
| @@ -90,9 +91,15 @@ export default Vue.extend({ | ||||
| 				line-height 42px | ||||
| 				width 42px | ||||
|  | ||||
| 		> .mk-drive | ||||
| 		> .drive | ||||
| 			height calc(100% - 42px) | ||||
| 			overflow scroll | ||||
| 			-webkit-overflow-scrolling touch | ||||
|  | ||||
| .cdxzvcfawjxdyxsekbxbfgtplebnoneb[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .cdxzvcfawjxdyxsekbxbfgtplebnoneb:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="file-detail"> | ||||
| <div class="pyvicwrksnfyhpfgkjwqknuururpaztw"> | ||||
| 	<div class="preview"> | ||||
| 		<img v-if="kind == 'image'" ref="img" | ||||
| 			:src="file.url" | ||||
| @@ -25,7 +25,7 @@ | ||||
| 	</div> | ||||
| 	<div class="info"> | ||||
| 		<div> | ||||
| 			<span class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</span> | ||||
| 			<span class="type"><mk-file-type-icon :type="file.type"/> {{ file.type }}</span> | ||||
| 			<span class="separator"></span> | ||||
| 			<span class="data-size">{{ file.datasize | bytes }}</span> | ||||
| 			<span class="separator"></span> | ||||
| @@ -134,11 +134,10 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .file-detail | ||||
|  | ||||
| root(isDark) | ||||
| 	> .preview | ||||
| 		padding 8px | ||||
| 		background #f0f0f0 | ||||
| 		background isDark ? #191b22 : #f0f0f0 | ||||
|  | ||||
| 		> img | ||||
| 			display block | ||||
| @@ -150,7 +149,7 @@ export default Vue.extend({ | ||||
| 		> footer | ||||
| 			padding 8px 8px 0 8px | ||||
| 			font-size 0.8em | ||||
| 			color #888 | ||||
| 			color isDark ? #606984 : #888 | ||||
| 			text-align center | ||||
|  | ||||
| 			> .separator | ||||
| @@ -179,25 +178,17 @@ export default Vue.extend({ | ||||
| 	> .info | ||||
| 		padding 14px | ||||
| 		font-size 0.8em | ||||
| 		border-top solid 1px #dfdfdf | ||||
| 		border-top solid 1px isDark ? #1c2023 : #dfdfdf | ||||
|  | ||||
| 		> div | ||||
| 			max-width 500px | ||||
| 			margin 0 auto | ||||
| 			color isDark ? #9397a2 : #9d9d9d | ||||
|  | ||||
| 			> .separator | ||||
| 				padding 0 4px | ||||
| 				color #cdcdcd | ||||
|  | ||||
| 			> .type | ||||
| 			> .data-size | ||||
| 				color #9d9d9d | ||||
|  | ||||
| 				> mk-file-type-icon | ||||
| 					margin-right 4px | ||||
|  | ||||
| 			> .created-at | ||||
| 				color #bdbdbd | ||||
|  | ||||
| 				> [data-fa] | ||||
| 					margin-right 2px | ||||
| @@ -207,7 +198,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 	> .menu | ||||
| 		padding 14px | ||||
| 		border-top solid 1px #dfdfdf | ||||
| 		border-top solid 1px isDark ? #1c2023 : #dfdfdf | ||||
|  | ||||
| 		> div | ||||
| 			max-width 500px | ||||
| @@ -218,14 +209,14 @@ export default Vue.extend({ | ||||
| 				width 100% | ||||
| 				padding 10px 16px | ||||
| 				margin 0 0 12px 0 | ||||
| 				color #333 | ||||
| 				color isDark ? #dfe3e8 : #333 | ||||
| 				font-size 0.9em | ||||
| 				text-align center | ||||
| 				text-decoration none | ||||
| 				text-shadow 0 1px 0 rgba(255, 255, 255, 0.9) | ||||
| 				background-image linear-gradient(#fafafa, #eaeaea) | ||||
| 				border 1px solid #ddd | ||||
| 				border-bottom-color #cecece | ||||
| 				text-shadow 0 1px 0 isDark ? rgba(0, 0, 0, 0.9) : rgba(255, 255, 255, 0.9) | ||||
| 				background-image isDark ? linear-gradient(#292f3c, #1b2025) : linear-gradient(#fafafa, #eaeaea) | ||||
| 				border 1px solid isDark ? #121417 : #ddd | ||||
| 				border-bottom-color isDark ? #060606 : #cecece | ||||
| 				border-radius 3px | ||||
|  | ||||
| 				&:last-child | ||||
| @@ -242,7 +233,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 	> .hash | ||||
| 		padding 14px | ||||
| 		border-top solid 1px #dfdfdf | ||||
| 		border-top solid 1px isDark ? #1c2023 : #dfdfdf | ||||
|  | ||||
| 		> div | ||||
| 			max-width 500px | ||||
| @@ -252,7 +243,7 @@ export default Vue.extend({ | ||||
| 				display block | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				color #555 | ||||
| 				color isDark ? #a8b7d0 : #555 | ||||
| 				font-size 0.9em | ||||
|  | ||||
| 				> [data-fa] | ||||
| @@ -273,7 +264,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 	> .exif | ||||
| 		padding 14px | ||||
| 		border-top solid 1px #dfdfdf | ||||
| 		border-top solid 1px isDark ? #1c2023 : #dfdfdf | ||||
|  | ||||
| 		> div | ||||
| 			max-width 500px | ||||
| @@ -283,7 +274,7 @@ export default Vue.extend({ | ||||
| 				display block | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				color #555 | ||||
| 				color isDark ? #a8b7d0 : #555 | ||||
| 				font-size 0.9em | ||||
|  | ||||
| 				> [data-fa] | ||||
| @@ -301,4 +292,10 @@ export default Vue.extend({ | ||||
| 				border-radius 2px | ||||
| 				background #f5f5f5 | ||||
|  | ||||
| .pyvicwrksnfyhpfgkjwqknuururpaztw[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .pyvicwrksnfyhpfgkjwqknuururpaztw:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <a class="file" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected"> | ||||
| <a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected"> | ||||
| 	<div class="container"> | ||||
| 		<div class="thumbnail" :style="thumbnail"></div> | ||||
| 		<div class="body"> | ||||
| @@ -7,20 +7,12 @@ | ||||
| 				<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> | ||||
| 				<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> | ||||
| 			</p> | ||||
| 			<!-- | ||||
| 			if file.tags.length > 0 | ||||
| 				ul.tags | ||||
| 					each tag in file.tags | ||||
| 						li.tag(style={background: tag.color, color: contrast(tag.color)})= tag.name | ||||
| 			--> | ||||
| 			<footer> | ||||
| 				<span class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</span> | ||||
| 				<span class="separator"></span> | ||||
| 				<span class="data-size">{{ file.datasize | bytes }}</span> | ||||
| 				<span class="separator"></span> | ||||
| 				<span class="created-at"> | ||||
| 					%fa:R clock%<mk-time :time="file.createdAt"/> | ||||
| 				</span> | ||||
| 				<span class="created-at">%fa:R clock%<mk-time :time="file.createdAt"/></span> | ||||
| 				<template v-if="file.isSensitive"> | ||||
| 					<span class="separator"></span> | ||||
| 					<span class="nsfw">%fa:eye-slash% %i18n:@nsfw%</span> | ||||
| @@ -73,7 +65,7 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .file | ||||
| root(isDark) | ||||
| 	display block | ||||
| 	text-decoration none !important | ||||
|  | ||||
| @@ -111,7 +103,7 @@ export default Vue.extend({ | ||||
| 				padding 0 | ||||
| 				font-size 0.9em | ||||
| 				font-weight bold | ||||
| 				color #555 | ||||
| 				color isDark ? #fff : #555 | ||||
| 				text-overflow ellipsis | ||||
| 				overflow-wrap break-word | ||||
|  | ||||
| @@ -138,7 +130,6 @@ export default Vue.extend({ | ||||
|  | ||||
| 				> .separator | ||||
| 					padding 0 4px | ||||
| 					color #CDCDCD | ||||
|  | ||||
| 				> .type | ||||
| 					color #9D9D9D | ||||
| @@ -164,4 +155,10 @@ export default Vue.extend({ | ||||
| 		&, * | ||||
| 			color #fff !important | ||||
|  | ||||
| .vupkuhvjnjyqaqhsiogfbywvjxynrgsm[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .vupkuhvjnjyqaqhsiogfbywvjxynrgsm:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <a class="root folder" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`"> | ||||
| <a class="jvwxssxsytqlqvrpiymarjlzlsxskqsr" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`"> | ||||
| 	<div class="container"> | ||||
| 		<p class="name">%fa:folder%{{ folder.name }}</p>%fa:angle-right% | ||||
| 	</div> | ||||
| @@ -24,9 +24,9 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .root.folder | ||||
| root(isDark) | ||||
| 	display block | ||||
| 	color #777 | ||||
| 	color isDark ? #fff : #777 | ||||
| 	text-decoration none !important | ||||
|  | ||||
| 	* | ||||
| @@ -55,4 +55,10 @@ export default Vue.extend({ | ||||
| 			> * | ||||
| 				height 100% | ||||
|  | ||||
| .jvwxssxsytqlqvrpiymarjlzlsxskqsr[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .jvwxssxsytqlqvrpiymarjlzlsxskqsr:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="mk-drive"> | ||||
| <div class="kmmwchoexgckptowjmjgfsygeltxfeqs"> | ||||
| 	<nav ref="nav"> | ||||
| 		<a @click.prevent="goRoot()" href="/i/drive">%fa:cloud%%i18n:@drive%</a> | ||||
| 		<template v-for="folder in hierarchyFolders"> | ||||
| @@ -26,11 +26,11 @@ | ||||
| 			</p> | ||||
| 		</div> | ||||
| 		<div class="folders" v-if="folders.length > 0"> | ||||
| 			<x-folder v-for="folder in folders" :key="folder.id" :folder="folder"/> | ||||
| 			<x-folder class="folder" v-for="folder in folders" :key="folder.id" :folder="folder"/> | ||||
| 			<p v-if="moreFolders">%i18n:@load-more%</p> | ||||
| 		</div> | ||||
| 		<div class="files" v-if="files.length > 0"> | ||||
| 			<x-file v-for="file in files" :key="file.id" :file="file"/> | ||||
| 			<x-file class="file" v-for="file in files" :key="file.id" :file="file"/> | ||||
| 			<button class="more" v-if="moreFiles" @click="fetchMoreFiles"> | ||||
| 				{{ fetchingMoreFiles ? '%i18n:common.loading%' : '%i18n:@load-more%' }} | ||||
| 			</button> | ||||
| @@ -94,6 +94,13 @@ export default Vue.extend({ | ||||
| 			return this.selectFile; | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		top() { | ||||
| 			if (this.isNaked) { | ||||
| 				(this.$refs.nav as any).style.top = `${this.top}px`; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection = (this as any).os.streams.driveStream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.streams.driveStream.use(); | ||||
| @@ -466,8 +473,8 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-drive | ||||
| 	background #fff | ||||
| root(isDark) | ||||
| 	background isDark ? #282c37 : #fff | ||||
|  | ||||
| 	> nav | ||||
| 		display block | ||||
| @@ -480,10 +487,10 @@ export default Vue.extend({ | ||||
| 		overflow auto | ||||
| 		white-space nowrap | ||||
| 		font-size 0.9em | ||||
| 		color rgba(#000, 0.67) | ||||
| 		color rgba(isDark ? #fff : #000, 0.67) | ||||
| 		-webkit-backdrop-filter blur(12px) | ||||
| 		backdrop-filter blur(12px) | ||||
| 		background-color rgba(#fff, 0.75) | ||||
| 		background-color rgba(isDark ? #313543 : #fff, 0.75) | ||||
| 		border-bottom solid 1px rgba(#000, 0.13) | ||||
|  | ||||
| 		> p | ||||
| @@ -509,7 +516,7 @@ export default Vue.extend({ | ||||
| 			opacity 0.5 | ||||
|  | ||||
| 		> .info | ||||
| 			border-bottom solid 1px #eee | ||||
| 			border-bottom solid 1px isDark ? #1c2023 : #eee | ||||
|  | ||||
| 			&:empty | ||||
| 				display none | ||||
| @@ -520,15 +527,15 @@ export default Vue.extend({ | ||||
| 				margin 0 auto | ||||
| 				padding 4px 16px | ||||
| 				font-size 10px | ||||
| 				color #777 | ||||
| 				color isDark ? #606984 : #777 | ||||
|  | ||||
| 		> .folders | ||||
| 			> .folder | ||||
| 				border-bottom solid 1px #eee | ||||
| 				border-bottom solid 1px isDark ? #1c2023 : #eee | ||||
|  | ||||
| 		> .files | ||||
| 			> .file | ||||
| 				border-bottom solid 1px #eee | ||||
| 				border-bottom solid 1px isDark ? #1c2023 : #eee | ||||
|  | ||||
| 			> .more | ||||
| 				display block | ||||
| @@ -584,4 +591,10 @@ export default Vue.extend({ | ||||
| 	> .file | ||||
| 		display none | ||||
|  | ||||
| .kmmwchoexgckptowjmjgfsygeltxfeqs[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .kmmwchoexgckptowjmjgfsygeltxfeqs:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -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() { | ||||
| @@ -482,10 +471,6 @@ root(isDark) | ||||
| 					&.reacted | ||||
| 						color $theme-color | ||||
|  | ||||
| 					&.menu | ||||
| 						@media (max-width 350px) | ||||
| 							display none | ||||
|  | ||||
| .note[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
|   | ||||
							
								
								
									
										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; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 		:init-folder="initFolder" | ||||
| 		:init-file="initFile" | ||||
| 		:is-naked="true" | ||||
| 		:top="48" | ||||
| 		:top="$store.state.uiHeaderHeight" | ||||
| 		@begin-fetch="Progress.start()" | ||||
| 		@fetched-mid="Progress.set(0.5)" | ||||
| 		@fetched="Progress.done()" | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 		<button class="upload" @click="upload">%fa:upload%</button> | ||||
| 		<button v-if="multiple" class="ok" @click="ok">%fa:check%</button> | ||||
| 	</header> | ||||
| 	<mk-drive ref="browser" select-file :multiple="multiple" is-naked :top="42"/> | ||||
| 	<mk-drive ref="browser" select-file :multiple="multiple" is-naked :top="$store.state.uiHeaderHeight"/> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import $ from 'cafy'; | ||||
| import ID from '../../../../../misc/cafy-id'; | ||||
| import UserList, { deleteUserList } from '../../../../../models/user-list'; | ||||
| import { ILocalUser } from '../../../../../models/user'; | ||||
| import getParams from '../../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーリストを削除します。', | ||||
| 		'en-US': 'Delete a user list' | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'account-write', | ||||
|  | ||||
| 	params: { | ||||
| 		listId: $.type(ID).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '対象となるユーザーリストのID', | ||||
| 				'en-US': 'ID of target user list' | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const userList = await UserList.findOne({ | ||||
| 		_id: ps.listId, | ||||
| 		userId: user._id | ||||
| 	}); | ||||
|  | ||||
| 	if (userList == null) { | ||||
| 		return rej('list not found'); | ||||
| 	} | ||||
|  | ||||
| 	deleteUserList(userList); | ||||
|  | ||||
| 	res(); | ||||
| }); | ||||
							
								
								
									
										56
									
								
								src/server/api/endpoints/users/lists/update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/server/api/endpoints/users/lists/update.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import $ from 'cafy'; | ||||
| import ID from '../../../../../misc/cafy-id'; | ||||
| import UserList, { pack } from '../../../../../models/user-list'; | ||||
| import { ILocalUser } from '../../../../../models/user'; | ||||
| import getParams from '../../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーリストを更新します。', | ||||
| 		'en-US': 'Update a user list' | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'account-write', | ||||
|  | ||||
| 	params: { | ||||
| 		listId: $.type(ID).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '対象となるユーザーリストのID', | ||||
| 				'en-US': 'ID of target user list' | ||||
| 			} | ||||
| 		}), | ||||
| 		title: $.str.range(1, 100).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': 'このユーザーリストの名前', | ||||
| 				'en-US': 'name of this user list' | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
|  | ||||
| 	// Fetch the list | ||||
| 	const userList = await UserList.findOne({ | ||||
| 		_id: ps.listId, | ||||
| 		userId: user._id | ||||
| 	}); | ||||
|  | ||||
| 	if (userList == null) { | ||||
| 		return rej('list not found'); | ||||
| 	} | ||||
|  | ||||
| 	// update | ||||
| 	await UserList.update({ _id: userList._id }, { | ||||
| 		$set: { | ||||
| 			title: ps.title | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// Response | ||||
| 	res(await pack(userList._id)); | ||||
| }); | ||||
| @@ -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