Compare commits

...

153 Commits
7.0.0 ... 8.0.0

Author SHA1 Message Date
91f1c3a10a Merge pull request #2423 from syuilo/develop
8.0.0
2018-08-23 15:42:14 +09:00
8fc1e07136 1時間単位での集計を追加 2018-08-23 15:40:24 +09:00
a62e2b83ff Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-08-23 14:56:43 +09:00
e31a2f7e55 Fix bug: Check following request existance 2018-08-23 14:56:39 +09:00
1598e996b1 Merge pull request #2419 from rinsuki/patch-1
https://misskey.xyz/notes/5b7e20bd248403003019b860 の修正
2018-08-23 12:00:46 +09:00
4fb7ee760a https://misskey.xyz/notes/5b7e20bd248403003019b860 の修正 2018-08-23 11:58:44 +09:00
37865cb381 Merge pull request #2416 from syuilo/master
Master
2018-08-23 05:46:24 +09:00
ab8b882435 Merge pull request #2372 from syuilo/greenkeeper/vue-js-modal-1.3.18
Update vue-js-modal to the latest version 🚀
2018-08-23 05:46:04 +09:00
1b2996947e Merge pull request #2400 from syuilo/greenkeeper/webpack-4.17.1
Update webpack to the latest version 🚀
2018-08-23 05:45:30 +09:00
ea56d368e3 Merge pull request #2414 from syuilo/develop
7.4.1
2018-08-23 03:45:25 +09:00
dbd3a750f5 7.4.1 2018-08-23 03:45:04 +09:00
f41818141f Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-08-23 03:44:42 +09:00
d2f576accd 互換性の修正 2018-08-23 03:44:32 +09:00
4e483856d4 Merge pull request #2413 from syuilo/master
Master
2018-08-23 03:24:43 +09:00
2997f26e3c Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-23 03:24:15 +09:00
cdab596240 7.4.0 2018-08-23 03:24:06 +09:00
fca7a9da94 Merge pull request #2412 from syuilo/develop
update
2018-08-23 03:23:31 +09:00
8ba178f795 コントロールパネルから招待制のオンオフを切り替えられるように 2018-08-23 03:19:57 +09:00
d98c67e13c Add control panel link in nav 2018-08-23 02:47:12 +09:00
d129151fdf Fix #2410
なぜか .ts という拡張子で来るのかは不明
2018-08-23 02:28:58 +09:00
1b9c69f793 Fix bug 2018-08-23 01:55:39 +09:00
42dd092334 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-08-23 01:37:20 +09:00
8dc9ec06f8 良い感じに 2018-08-23 01:37:05 +09:00
ae5da782e5 Merge pull request #2409 from syuilo/l10n_develop
New Crowdin translations
2018-08-23 01:36:40 +09:00
313b0cec65 New translations ja-JP.yml (Japanese, Kansai) 2018-08-23 01:36:10 +09:00
12a51972ed New translations ja-JP.yml (Spanish) 2018-08-23 01:36:07 +09:00
a2f3b2966f New translations ja-JP.yml (Russian) 2018-08-23 01:36:05 +09:00
0c2627f08b New translations ja-JP.yml (Portuguese) 2018-08-23 01:36:03 +09:00
9535df12dd New translations ja-JP.yml (Polish) 2018-08-23 01:36:00 +09:00
2400471a0d New translations ja-JP.yml (Korean) 2018-08-23 01:35:57 +09:00
784da8c37b New translations ja-JP.yml (Italian) 2018-08-23 01:35:55 +09:00
3778f9c521 New translations ja-JP.yml (German) 2018-08-23 01:35:52 +09:00
e1cec85f1e New translations ja-JP.yml (French) 2018-08-23 01:35:49 +09:00
ca9c087060 New translations ja-JP.yml (English) 2018-08-23 01:35:47 +09:00
5b2d91baad New translations ja-JP.yml (Chinese Simplified) 2018-08-23 01:35:44 +09:00
08e099b88d New translations ja-JP.yml (Catalan) 2018-08-23 01:35:41 +09:00
4153b0db38 Merge pull request #2408 from syuilo/l10n_develop
New Crowdin translations
2018-08-23 01:17:48 +09:00
88701a21bb New translations ja-JP.yml (Japanese, Kansai) 2018-08-23 01:16:01 +09:00
3ddc73ca94 New translations ja-JP.yml (Spanish) 2018-08-23 01:15:59 +09:00
693d793265 New translations ja-JP.yml (Russian) 2018-08-23 01:15:57 +09:00
5d685233dd New translations ja-JP.yml (Portuguese) 2018-08-23 01:15:54 +09:00
16575751d9 New translations ja-JP.yml (Polish) 2018-08-23 01:15:52 +09:00
1f6295f437 New translations ja-JP.yml (Korean) 2018-08-23 01:15:49 +09:00
6737fe2ead New translations ja-JP.yml (Italian) 2018-08-23 01:15:47 +09:00
4e77939fca New translations ja-JP.yml (German) 2018-08-23 01:15:44 +09:00
21f528c07d New translations ja-JP.yml (French) 2018-08-23 01:15:41 +09:00
23f835fac0 New translations ja-JP.yml (English) 2018-08-23 01:15:38 +09:00
f21343225c New translations ja-JP.yml (Chinese Simplified) 2018-08-23 01:15:36 +09:00
f13a59f7db New translations ja-JP.yml (Catalan) 2018-08-23 01:15:33 +09:00
0315b9274c Merge pull request #2407 from syuilo/master
Master
2018-08-23 00:49:52 +09:00
da88043962 Merge pull request #2396 from Tosuke/fix-2354
Mute on mobile(#2354)
2018-08-23 00:47:50 +09:00
0352bf0cc2 Merge pull request #2393 from acid-chicken/patch-autogen-shell
Add autogen.sh
2018-08-23 00:47:08 +09:00
baa71070a8 Merge pull request #2403 from syuilo/greenkeeper/summaly-2.1.4
Update summaly to the latest version 🚀
2018-08-23 00:45:44 +09:00
2713064f27 Merge pull request #2405 from acid-chicken/patch-4
Migrate summaly to 2.1.4
2018-08-23 00:45:25 +09:00
f6387ac115 Merge pull request #2406 from syuilo/master
Master
2018-08-23 00:44:13 +09:00
d704aca035 Update url-preview.vue 2018-08-23 00:40:32 +09:00
2b54b4ac06 Update url-preview.vue 2018-08-23 00:34:35 +09:00
7410f2f4c0 fix(package): update summaly to version 2.1.4 2018-08-22 14:55:38 +00:00
99c3c1258a Merge pull request #2402 from acid-chicken/patch-3
Update Crowdin configuration file
2018-08-22 23:54:03 +09:00
e51184931d Update crowdin.yml 2018-08-22 23:53:27 +09:00
3bc9a40b48 fix(package): update webpack to version 4.17.1 2018-08-22 09:56:30 +00:00
9f49ca8fdb feature mute on mobile(#2354) 2018-08-22 15:54:22 +09:00
550d1547b4 Add autogen.sh
refs: https://github.com/syuilo/misskey/pull/2391#issuecomment-414915763
2018-08-22 14:41:41 +09:00
ca0b56ee57 Merge pull request #2391 from acid-chicken/patch-autogen
[AUTOMATED] Update README.md
2018-08-22 14:26:05 +09:00
ef1d854f2c Update README.md [AUTOGEN] 2018-08-22 14:25:06 +09:00
a5023271ef 7.3.0 2018-08-22 09:40:54 +09:00
c3747db670 Fix doc 2018-08-22 09:36:42 +09:00
fe1e60a28c Fix #2334 2018-08-22 09:33:59 +09:00
f91d2e8c8d Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-22 09:31:51 +09:00
dccc2c60e3 Fix bugs
Closes #2367
2018-08-22 09:31:35 +09:00
933e25804c Merge pull request #2389 from Tosuke/fix-2384
Fix login bug(#2384)
2018-08-22 09:27:09 +09:00
0b503661af fix login bug(#2384) 2018-08-22 09:26:06 +09:00
58082431ff Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-22 09:16:55 +09:00
2536bfb5f5 #2378 2018-08-22 09:16:52 +09:00
6428066552 Merge pull request #2388 from mei23/mei-0822-dbcheck
起動時のDB接続チェックでエラーを検出できないのを修正
2018-08-22 09:12:59 +09:00
4bf3827b73 Revert "#2387"
This reverts commit 3cad494404.
2018-08-22 09:12:37 +09:00
3cad494404 #2387 2018-08-22 09:11:59 +09:00
ef0793311f リバーシのアイコンのコントラストのオプションを追加するなど 2018-08-22 09:10:39 +09:00
6f3e341e89 Fix DB connectivity check 2018-08-22 08:46:31 +09:00
2fea3be7c0 Merge pull request #2364 from acid-chicken/patch-2
Resolve #2363
2018-08-22 08:41:47 +09:00
82059d4fd9 7.2.0 2018-08-22 05:24:16 +09:00
07ddeae2f1 Clean up 2018-08-22 04:53:02 +09:00
f2279758b2 Merge pull request #2366 from acid-chicken/patch-autogen
[AUTOMATED] Update README.md
2018-08-22 03:30:12 +09:00
1ed189a518 Merge pull request #2386 from acid-chicken/patch-3
Update boot.js
2018-08-22 02:25:41 +09:00
137741d307 Update index.js 2018-08-22 01:52:13 +09:00
d702f6e090 Rename ja-ks.yml to ja-KS.yml 2018-08-22 01:51:42 +09:00
f33701233c Update boot.js 2018-08-22 01:50:13 +09:00
70003269e5 Update boot.js 2018-08-22 01:48:08 +09:00
61896d2386 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-22 01:46:13 +09:00
52d640c5a7 Fix 2018-08-22 01:46:10 +09:00
c65f5761e1 Merge pull request #2385 from mei23/mei-0821-aptype
ActivityPub で Content-Type を正しく扱う
2018-08-22 01:23:43 +09:00
3016ac4805 Fix bug 2018-08-22 01:02:56 +09:00
28a47cd331 Fix 2018-08-22 00:59:07 +09:00
6ecb88b0d1 #2338 2018-08-22 00:52:00 +09:00
8df1278c8e Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-22 00:44:26 +09:00
52bec430d4 use ja-JP 2018-08-22 00:44:07 +09:00
da4cec8767 Merge pull request #2362 from syuilo/greenkeeper/vue-loader-15.4.0
Update vue-loader to the latest version 🚀
2018-08-22 00:11:24 +09:00
ad0087d7dd Merge pull request #2382 from syuilo/greenkeeper/webpack-4.17.0
Update webpack to the latest version 🚀
2018-08-22 00:11:12 +09:00
9630860035 Merge pull request #2383 from syuilo/greenkeeper/sharp-0.20.7
fix(package): update sharp to version 0.20.7
2018-08-22 00:11:01 +09:00
75e4c8d74d Better reponse 2018-08-21 23:56:15 +09:00
1a5ee81e7e fix(package): update sharp to version 0.20.7
Closes #2368
2018-08-21 11:18:21 +00:00
3476be16ab Merge pull request #2381 from mei23/mei-0821-apnote
ActivityPub Note/Outbox の公開範囲の修正
2018-08-21 20:07:33 +09:00
c42f61a0f4 fix(package): update webpack to version 4.17.0 2018-08-21 08:48:10 +00:00
b42a9e1c4e Set ActivityPub Content-Type 2018-08-21 13:48:03 +09:00
4495525705 Respect visibility in ActivityPub Note/Outbox 2018-08-21 13:22:30 +09:00
a603602f32 Clean up 2018-08-21 05:42:31 +09:00
67b28f9b6e fix(package): update vue-js-modal to version 1.3.18 2018-08-20 19:58:32 +00:00
fd947407af Revert "Fix bug?"
This reverts commit 2c9bacfcea.
2018-08-21 01:23:39 +09:00
30444e5f1a #2359 など 2018-08-21 01:03:58 +09:00
f0d818de24 Fix bug 2018-08-21 00:12:45 +09:00
3fb98e808f 7.1.2 2018-08-20 23:49:27 +09:00
2c9bacfcea Fix bug? 2018-08-20 23:49:00 +09:00
ae0284b1b1 Update README.md [AUTOGEN] 2018-08-20 19:03:01 +09:00
166c4ebda0 Update reversi.game.vue 2018-08-20 18:12:03 +09:00
319eed029b Update reversi.game.vue 2018-08-20 18:10:51 +09:00
ec5aa10167 fix(package): update vue-loader to version 15.4.0 2018-08-20 00:44:56 +00:00
a542765cf8 Merge pull request #2352 from acid-chicken/patch-2
Update README.md
2018-08-20 07:45:35 +09:00
fd3f8d43db Merge pull request #2356 from acid-chicken/patch-3
Fix z-index
2018-08-20 07:44:57 +09:00
4f4496078a Update user.vue 2018-08-20 02:05:57 +09:00
8f4f5b4ce0 7.1.1 2018-08-20 01:42:56 +09:00
bdc6718ae5 Fix bug 2018-08-20 01:41:53 +09:00
09d1f1c20d Update README.md 2018-08-19 23:14:22 +09:00
0efb7af17b 7.1.0 2018-08-19 23:11:55 +09:00
a45a78b94f Merge pull request #2336 from syuilo/l10n_master
New Crowdin translations
2018-08-19 23:06:47 +09:00
6337c26cf0 Merge pull request #2347 from syuilo/greenkeeper/html-minifier-3.5.20
Update html-minifier to the latest version 🚀
2018-08-19 23:06:34 +09:00
208dec25d9 Merge pull request #2349 from syuilo/configress-build
設定ファイルなしでビルドできるように
2018-08-19 22:32:58 +09:00
7d65a0c3d5 wip 2018-08-19 22:32:25 +09:00
bb925e5de3 wip 2018-08-19 21:07:18 +09:00
c9de5b65d4 wip 2018-08-19 19:15:29 +09:00
9bc3fcf74f Merge pull request #2348 from mei23/mei-0819-nsfw3
リモートにNSFWが効かないのを修正
2018-08-19 18:46:47 +09:00
19d979c330 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-19 18:38:15 +09:00
bb7b335491 nameId廃止 & アプリ作成時にシークレットを返すように 2018-08-19 18:38:02 +09:00
be5a0b4794 Fix リモートにNSFWが効かない 2018-08-19 18:08:29 +09:00
48eea03386 fix(package): update html-minifier to version 3.5.20 2018-08-19 09:03:23 +00:00
566317dc83 Fix bug 2018-08-19 13:32:02 +09:00
d4334645c2 New translations ja.yml (Japanese (Kansai-ben)) 2018-08-19 12:41:16 +09:00
d60c3c4ee3 7.0.2 2018-08-19 05:26:53 +09:00
452514f7fe Fix bug 2018-08-19 05:26:34 +09:00
58abb0ce8f 7.0.1 2018-08-19 05:15:55 +09:00
000d9cc1ce Fix bug 2018-08-19 05:15:50 +09:00
8cc017354a New translations ja.yml (English) 2018-08-19 04:11:04 +09:00
7caa083612 New translations ja.yml (Japanese (Kansai-ben)) 2018-08-19 04:01:58 +09:00
68e1b00eb1 New translations ja.yml (Catalan) 2018-08-19 04:01:55 +09:00
1d904c756a New translations ja.yml (Portuguese) 2018-08-19 04:01:53 +09:00
0a1db1f595 New translations ja.yml (Korean) 2018-08-19 04:01:50 +09:00
e30e8267dd New translations ja.yml (Polish) 2018-08-19 04:01:48 +09:00
288a881817 New translations ja.yml (Chinese Simplified) 2018-08-19 04:01:46 +09:00
f593790872 New translations ja.yml (Italian) 2018-08-19 04:01:43 +09:00
1044ad8589 New translations ja.yml (Russian) 2018-08-19 04:01:41 +09:00
6e24015e68 New translations ja.yml (English) 2018-08-19 04:01:39 +09:00
b9a2c449ff New translations ja.yml (Spanish) 2018-08-19 04:01:37 +09:00
7c390cbf7b New translations ja.yml (German) 2018-08-19 04:01:35 +09:00
a657d1c774 New translations ja.yml (French) 2018-08-19 04:01:33 +09:00
99 changed files with 2423 additions and 1999 deletions

86
.autogen/autogen.sh Executable file
View File

@ -0,0 +1,86 @@
#!/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
cd "$(dirname $0)/.." && \
touch null.cache && \
rm *.cache && \
git checkout master && \
git pull origin master && \
git pull upstream master && \
git stash && \
git rebase -f upstream/master && \
git branch patch-autogen && \
git checkout patch-autogen && \
git reset --hard HEAD || \
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"
while :
do
touch patreon.raw.cache && \
rm patreon.raw.cache && \
curl -LSs -w '\n' -H "Authorization: Bearer $BEARER_TOKEN" -- $URL > patreon.raw.cache && \
touch patreon.cache && \
rm patreon.cache && \
cat patreon.raw.cache | \
jq -r '(.data|map(select(.relationships.currently_entitled_tiers.data[]))|map(.relationships.user.data.id))as$data|.included|map(select(.attributes.hide_pledges==false))|map(select(.id as$id|$data|contains([$id])))|map(.attributes|[.full_name,.thumb_url,.url]|@tsv)|.[]|@text' >> patreon.cache && \
echo '<table><tr>' >> patreon.md.cache && \
cat patreon.cache | \
awk -F'\t' '{print $2,$1}' | \
sed -e 's/ /\\" alt=\\"/' | \
xargs -I% echo '<td><img src="%"></td>' >> patreon.md.cache && \
echo '</tr><tr>' >> patreon.md.cache && \
cat patreon.cache | \
awk -F'\t' '{print $3,$1}' | \
sed -e 's/ /\\">/' | \
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' && \
break || \
URL="$NEW_URL"
done
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;
do
if [[ -z "$IGNORE" ]]
then
if [[ "$LINE" = '<!-- PATREON_START -->' ]]
then
IGNORE='PATREON_INSIDE'
else
echo "$LINE" >> README.md
fi
else
if [[ "$LINE" = '<!-- PATREON_END -->' ]]
then
IGNORE=
cat patreon.md.cache >> README.md
fi
fi
done
cat patreon.md.cache
touch null.cache && \
rm *.cache && \
diff .autogen/README.md README.md > diff.cache
cat diff.cache && \
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"
git stash
git checkout master
git branch -D patch-autogen

View File

@ -39,9 +39,15 @@ please see [Setup and installation guide](./docs/setup.en.md).
---------------------------------------------------------------- ----------------------------------------------------------------
**[PR](https://github.com/syuilo/misskey/pulls)s welcome!** **[PR](https://github.com/syuilo/misskey/pulls)s welcome!**
If you want to... ### i18n
* i18n ... please see [Translation guide](./docs/translate.en.md).
* l10n ... please visit https://crowdin.com/project/misskey 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
---------------------------------------------------------------- ----------------------------------------------------------------
@ -49,35 +55,41 @@ If you want to...
<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/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/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/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/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></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/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></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=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=12913507">Melilot</a></td> <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td> <td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td> <td><a href="https://www.patreon.com/user?u=12718187">Peter G.</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=13039004">nemu</a></td>
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></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/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/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> <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=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/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=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> <td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
</tr></table> </tr></table>
**Last updated:** Sat, 18 Aug 2018 02:02:58 UTC **Last updated:** Wed, 22 Aug 2018 05:25:06 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

144
cli/migration/8.0.0.js Normal file
View File

@ -0,0 +1,144 @@
const { default: Stats } = require('../../built/models/stats');
const { default: User } = require('../../built/models/user');
const { default: Note } = require('../../built/models/note');
const { default: DriveFile } = require('../../built/models/drive-file');
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
const date = new Date(y, m, d, h);
async function main() {
await Stats.update({}, {
$set: {
span: 'day'
}
}, {
multi: true
});
const localUsersCount = await User.count({
host: null
});
const remoteUsersCount = await User.count({
host: { $ne: null }
});
const localNotesCount = await Note.count({
'_user.host': null
});
const remoteNotesCount = await Note.count({
'_user.host': { $ne: null }
});
const localDriveFilesCount = await DriveFile.count({
'metadata._user.host': null
});
const remoteDriveFilesCount = await DriveFile.count({
'metadata._user.host': { $ne: null }
});
const localDriveFilesSize = await DriveFile
.aggregate([{
$match: {
'metadata._user.host': null,
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(aggregates => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
const remoteDriveFilesSize = await DriveFile
.aggregate([{
$match: {
'metadata._user.host': { $ne: null },
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(aggregates => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
await Stats.insert({
date: date,
span: 'hour',
users: {
local: {
total: localUsersCount,
diff: 0
},
remote: {
total: remoteUsersCount,
diff: 0
}
},
notes: {
local: {
total: localNotesCount,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: remoteNotesCount,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: localDriveFilesCount,
totalSize: localDriveFilesSize,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: remoteDriveFilesCount,
totalSize: remoteDriveFilesSize,
diffCount: 0,
diffSize: 0
}
}
});
console.log('done');
}
main();

View File

@ -1,3 +1,3 @@
files: files:
- source: /locales/ja.yml - source: /locales/ja-JP.yml
translation: /locales/%two_letters_code%.yml translation: /locales/%locale%.yml

View File

@ -11,12 +11,12 @@ If you find an untranslated part on Misskey:
- In fact, `foo` should be a word that is appropriate for the situation and is easy to understand in English. - In fact, `foo` should be a word that is appropriate for the situation and is easy to understand in English.
- For example, if the untranslated portion is the following "タイムライン" you must write: `%i18n:@timeline%`. - For example, if the untranslated portion is the following "タイムライン" you must write: `%i18n:@timeline%`.
3. Open the `locales/ja.yml`, check whether the <strong>file name (path)</strong> found in step 1 exists, if not, create it. 3. Open the `locales/ja-JP.yml`, check whether the <strong>file name (path)</strong> found in step 1 exists, if not, create it.
- Do not put the beginning of the path `src/client/app/` in the locale file. - Do not put the beginning of the path `src/client/app/` in the locale file.
- For example, in this case we want to modify untranslated parts of `src/client/app/mobile/views/pages/home.vue`, so the key is `mobile/views/pages/home.vue`. - For example, in this case we want to modify untranslated parts of `src/client/app/mobile/views/pages/home.vue`, so the key is `mobile/views/pages/home.vue`.
4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes. 4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes.
- For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja.yml`. - For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja-JP.yml`.
5. And done 5. And done

View File

@ -16,7 +16,7 @@ Si vous trouvez un segment non-traduit sur Misskey :
- Par exemple, dans ce cas de figure, nous voulons modifier le segment non-traduit de : `src/client/app/mobile/views/pages/home.vue`donc il faut juste écrire : `mobile/views/pages/home.vue` dans les fichiers linguistiques. - Par exemple, dans ce cas de figure, nous voulons modifier le segment non-traduit de : `src/client/app/mobile/views/pages/home.vue`donc il faut juste écrire : `mobile/views/pages/home.vue` dans les fichiers linguistiques.
4. Ajoutez la propriété du texte traduit grâce à la clef `foo`, en-dessous du chemin correspondant à votre modification que vous avez trouvé ou créé dans l'étape 2. À côté, veuillez indiquer entre "guillemets" la valeur de votre traduction. 4. Ajoutez la propriété du texte traduit grâce à la clef `foo`, en-dessous du chemin correspondant à votre modification que vous avez trouvé ou créé dans l'étape 2. À côté, veuillez indiquer entre "guillemets" la valeur de votre traduction.
- Par exemple, dans ce cas de figure, nous ajoutons la propriété et la traduction `timeline: "Timeline"` à `locales/fr.yml`, mais aussi la propriété et la version originale `timeline: "タイムライン"` à `locales/ja.yml`. - Par exemple, dans ce cas de figure, nous ajoutons la propriété et la traduction `timeline: "Timeline"` à `locales/fr.yml`, mais aussi la propriété et la version originale `timeline: "タイムライン"` à `locales/ja-JP.yml`.
5. Vous avez réussi à traduire une portion de misskey 5. Vous avez réussi à traduire une portion de misskey

View File

@ -11,12 +11,12 @@ Misskey内の未翻訳箇所を見つけたら
- `foo`は実際にはその場に適したわかりやすい(英語の)名前にしてください。 - `foo`は実際にはその場に適したわかりやすい(英語の)名前にしてください。
- 例えば未翻訳箇所が「タイムライン」というテキストだった場合、`%i18n:@timeline%`のようにします。 - 例えば未翻訳箇所が「タイムライン」というテキストだった場合、`%i18n:@timeline%`のようにします。
3. `locales/ja.yml`を開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。 3. `locales/ja-JP.yml`を開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。
- パスの`src/client/app/`は省略してください。 - パスの`src/client/app/`は省略してください。
- 例えば、今回の例では`src/client/app/mobile/views/pages/home.vue`の未翻訳箇所を修正したいので、キーは`mobile/views/pages/home.vue`になります。 - 例えば、今回の例では`src/client/app/mobile/views/pages/home.vue`の未翻訳箇所を修正したいので、キーは`mobile/views/pages/home.vue`になります。
4. そのキーの直下に2.で置換した`foo`の部分をキーとし、テキストを値とするプロパティを追加します。 4. そのキーの直下に2.で置換した`foo`の部分をキーとし、テキストを値とするプロパティを追加します。
- 例えば、今回の例で言うと`locales/ja.yml``timeline: "タイムライン"`を追加します。 - 例えば、今回の例で言うと`locales/ja-JP.yml``timeline: "タイムライン"`を追加します。
5. 完了です! 5. 完了です!

View File

@ -23,7 +23,6 @@ const uglifyes = require('uglify-es');
const locales = require('./locales'); const locales = require('./locales');
import { fa } from './src/misc/fa'; import { fa } from './src/misc/fa';
import config from './src/config';
const uglify = uglifyComposer(uglifyes, console); const uglify = uglifyComposer(uglifyes, console);
@ -60,7 +59,16 @@ gulp.task('build:copy:views', () =>
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views')) gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
); );
gulp.task('build:copy', ['build:copy:views'], () => // 互換性のため
gulp.task('build:copy:lang', () =>
gulp.src(['./built/client/assets/*.*-*.js'])
.pipe(rename(path => {
path.basename = path.basename.replace(/\-(.*)$/, '');
}))
.pipe(gulp.dest('./built/client/assets/'))
);
gulp.task('build:copy', ['build:copy:views', 'build:copy:lang'], () =>
gulp.src([ gulp.src([
'./build/Release/crypto_key.node', './build/Release/crypto_key.node',
'./src/const.json', './src/const.json',
@ -118,7 +126,6 @@ gulp.task('build:client:script', () => {
const client = require('./built/client/meta.json'); const client = require('./built/client/meta.json');
return gulp.src(['./src/client/app/boot.js', './src/client/app/safe.js']) return gulp.src(['./src/client/app/boot.js', './src/client/app/safe.js'])
.pipe(replace('VERSION', JSON.stringify(client.version))) .pipe(replace('VERSION', JSON.stringify(client.version)))
.pipe(replace('API', JSON.stringify(config.api_url)))
.pipe(replace('ENV', JSON.stringify(env))) .pipe(replace('ENV', JSON.stringify(env)))
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) .pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(isProduction ? uglify({ .pipe(isProduction ? uglify({

View File

@ -1,4 +1,4 @@
# **Please DO NOT edit these files** except `ja.yml`. # **Please DO NOT edit these files** except `ja-JP.yml`.
If you want to... If you want to...
* i18n ... please see [Translation guide](../docs/translate.en.md). * i18n ... please see [Translation guide](../docs/translate.en.md).

View File

@ -58,7 +58,7 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "いいね" like: "ええやん"
love: "しゅき" love: "しゅき"
laugh: "笑" laugh: "笑"
hmm: "ふぅ~む" hmm: "ふぅ~む"

View File

@ -58,7 +58,7 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "Gefällt mir" like: "ええやん"
love: "Lieben" love: "Lieben"
laugh: "Lachen" laugh: "Lachen"
hmm: "Hmm...?" hmm: "Hmm...?"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Banner" banner: "Banner"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "Umbenennen" rename: "Umbenennen"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

View File

@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Banner" banner: "Banner"
nsfw: "NSFW"
contextmenu: contextmenu:
rename: "Rename" rename: "Rename"
mark-as-sensitive: "Mark as 'sensitive'" mark-as-sensitive: "Mark as 'sensitive'"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "Choose files" select-file: "Choose files"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "Choose a folder" select-folder: "Choose a folder"
mobile/views/components/drive.file.vue:
nsfw: "NSFW"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "Download" download: "Download"
rename: "Rename" rename: "Rename"
move: "Move" move: "Move"
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "NSFW"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "NSFW" sensitive: "NSFW"
click-to-show: "Click to show" click-to-show: "Click to show"

View File

@ -58,7 +58,7 @@ common:
friday: "Viernes" friday: "Viernes"
saturday: "Sábado" saturday: "Sábado"
reactions: reactions:
like: "me gusta" like: "ええやん"
love: "amor" love: "amor"
laugh: "risa" laugh: "risa"
hmm: "hmm" hmm: "hmm"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Banner" banner: "Banner"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "Renombrar" rename: "Renombrar"
mark-as-sensitive: "Marcar como 'sensible'" mark-as-sensitive: "Marcar como 'sensible'"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

View File

@ -30,7 +30,7 @@ common:
quoted-by: "Cité·e par {} :" quoted-by: "Cité·e par {} :"
time: time:
unknown: "inconnu" unknown: "inconnu"
future: "future" future: "à l'instant"
just_now: "à l'instant" just_now: "à l'instant"
seconds_ago: "Il y a {} seconde·s" seconds_ago: "Il y a {} seconde·s"
minutes_ago: "Il y a {} minute·s" minutes_ago: "Il y a {} minute·s"
@ -58,7 +58,7 @@ common:
friday: "Vendredi" friday: "Vendredi"
saturday: "Samedi" saturday: "Samedi"
reactions: reactions:
like: "Aime" like: "ええやん"
love: "Adore" love: "Adore"
laugh: "Rire" laugh: "Rire"
hmm: "Hmm ... ?" hmm: "Hmm ... ?"
@ -287,7 +287,7 @@ common/views/components/signin.vue:
signin: "Se connecter" signin: "Se connecter"
or: "Ou" or: "Ou"
signin-with-twitter: "Se connecter via Twitter" signin-with-twitter: "Se connecter via Twitter"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" login-failed: "Échec d'authentification. Veuillez vérifier que votre nom d'utilisateur et mot de passe sont corrects."
common/views/components/signup.vue: common/views/components/signup.vue:
invitation-code: "Code dinvitation" invitation-code: "Code dinvitation"
invitation-info: "Si vous navez pas de code dinvitation, contactez un·e <a href=\"{}\">administrateur·rice</a>." invitation-info: "Si vous navez pas de code dinvitation, contactez un·e <a href=\"{}\">administrateur·rice</a>."
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Bannière" banner: "Bannière"
nsfw: "CW"
contextmenu: contextmenu:
rename: "Renommer" rename: "Renommer"
mark-as-sensitive: "Marquer comme sensible" mark-as-sensitive: "Marquer comme sensible"
@ -637,7 +638,7 @@ desktop/views/components/settings.vue:
show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。" show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
sound: "Son" sound: "Son"
enable-sounds: "Activer le son" enable-sounds: "Activer le son"
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。" enable-sounds-desc: "Jouer un son lorsque vous recevez un message. Ce paramètre est sauvegardé dans le navigateur."
volume: "Volume" volume: "Volume"
test: "Test" test: "Test"
mobile: "Mobile" mobile: "Mobile"
@ -698,7 +699,7 @@ desktop/views/components/settings.2fa.vue:
desktop/views/components/settings.api.vue: desktop/views/components/settings.api.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer."
regenerate-token: "Regenerer le token" regenerate-token: "Regenerer le token"
token: "Jeton :" token: "Jeton :"
enter-password: "Veuillez entrer le mot de passe" enter-password: "Veuillez entrer le mot de passe"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "Choisissez un fichier" select-file: "Choisissez un fichier"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "Choisissez un dossier" select-folder: "Choisissez un dossier"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "Télécharger" download: "Télécharger"
rename: "Renommer" rename: "Renommer"
move: "Déplacer" move: "Déplacer"
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "Le contenu est NSFW" sensitive: "Le contenu est NSFW"
click-to-show: "Cliquer pour afficher" click-to-show: "Cliquer pour afficher"

View File

@ -8,16 +8,16 @@ const yaml = require('js-yaml');
const loadLang = lang => yaml.safeLoad( const loadLang = lang => yaml.safeLoad(
fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8')); fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
const native = loadLang('ja'); const native = loadLang('ja-JP');
const langs = { const langs = {
'de': loadLang('de'), 'de-DE': loadLang('de-DE'),
'en': loadLang('en'), 'en-US': loadLang('en-US'),
'fr': loadLang('fr'), 'fr-FR': loadLang('fr-FR'),
'ja': native, 'ja-JP': native,
'ja-ks': loadLang('ja-ks'), 'ja-KS': loadLang('ja-KS'),
'pl': loadLang('pl'), 'pl-PL': loadLang('pl-PL'),
'es': loadLang('es') 'es-ES': loadLang('es-ES')
}; };
Object.values(langs).forEach(locale => { Object.values(langs).forEach(locale => {

View File

@ -58,7 +58,7 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "いいね" like: "ええやん"
love: "しゅき" love: "しゅき"
laugh: "笑" laugh: "笑"
hmm: "ふぅ~む" hmm: "ふぅ~む"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,33 @@
--- ---
meta: meta:
lang: "Português" lang: "日本語 (関西弁)"
divider: "" divider: ""
common: common:
misskey: "A ⭐ of fediverse" misskey: "A ⭐ of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "ようMisskeyを見つけてくれて、おおきにやで。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>やねん。Fediverse(ぎょうさんのSNSで構成される宇宙)っちゅうもんの中におるから、お隣さんのSNSとも仲良うさせてもろてんねん。ちょいとやかましい心斎橋から離れて、新しいインターネットにダイブしてみぃひん?"
adblock: adblock:
detected: "広告ブロッカーを無効にしてください" detected: "広告ブロッカーを無効にして"
warning: "<strong>Misskeyは広告を掲載していません</strong>、広告をブロックる機能が有効だと一部の機能が利用できなったり、不具合が発生する場合があります。" warning: "<strong>Misskeyは広告を掲載してん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
application-authorization: "アプリの連携" application-authorization: "アプリの連携"
close: "閉じる" close: "さいなら"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。" do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
got-it: "わかった" got-it: "ほい"
customization-tips: customization-tips:
title: "カスタマイズのヒント" title: "カスタマイズのヒント"
paragraph1: "ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。" paragraph1: "ホームのカスタマイズやと、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりできんねやわ。"
paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。" paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>したったら表示を変更できんねやわ。"
paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。" paragraph3: "ウィジェットを削除するんやったら、ヘッダーの<strong>「ゴミ箱」</strong>と書いたぁるエリアにウィジェットをドラッグ&ドロップしてな。"
paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。" paragraph4: "カスタマイズを終了するんやったら、右上の「完了」をクリックしてな。"
gotit: "Got it!" gotit: "Got it!"
notification: notification:
file-uploaded: "ファイルがアップロードされました" file-uploaded: "ファイルがアップロードされた"
message-from: "{}んからメッセージ:" message-from: "{}んからメッセージ:"
reversi-invited: "対局への招待があります" reversi-invited: "対局への招待がきとるで"
reversi-invited-by: "{}んから" reversi-invited-by: "{}んから"
notified-by: "{}んから" notified-by: "{}んから"
reply-from: "{}んから返信:" reply-from: "{}んから返信:"
quoted-by: "{}んが引用:" quoted-by: "{}んが引用:"
time: time:
unknown: "なぞのじかん" unknown: "なぞのじかん"
future: "未来" future: "未来"
@ -58,41 +58,41 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "いいね" like: "ええやん"
love: "しゅき" love: "好きやねん"
laugh: "" laugh: "わろた"
hmm: "ふぅ~む" hmm: "ふぅ~む"
surprise: "わお" surprise: "わお"
congrats: "おめでとう" congrats: "おめでとうさん"
angry: "おこ" angry: "何言うてまんねん"
confused: "こまこまのこまり" confused: "こまこまのこまりやわぁ"
rip: "RIP" rip: "RIP"
pudding: "Pudding" pudding: "アメちゃんちゃうんちゃう?"
note-placeholders: note-placeholders:
a: "今どうして" a: "今なにして"
b: "何かありましたか?" b: "何かあったんか?"
c: "何をお考えですか" c: "何考えとりますん"
d: "言たいことは?" d: "言うときたいことは?"
e: "ここに書いてください" e: "ここに書いて"
f: "あなたが書くを待っています..." f: "あんさんが書くを待っちょります..."
search: "検索" search: "検索"
delete: "削除" delete: "削除"
loading: "読み込み中" loading: "読み込み中"
ok: "わかった" ok: "ほい"
update-available-title: "更新があります" update-available-title: "更新があんで"
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みると更新が適用されます。" update-available: "Misskeyの新しいバージョンがあんで({newer}。現在{current}をつこてるわ)。ページを再度読み込みしたると更新が適用されるわ。"
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。"
i-like-sushi: "私は(プリンよりむしろ)寿司が好き" i-like-sushi: "寿司(のほうがプリンよりむしろ)ウマい、タコ焼きはあらへんけど。"
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示や!"
verified-user: "公式アカウント" verified-user: "アメちゃん付きアカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
reversi: reversi:
drawn: "引き分け" drawn: "おあいこ"
my-turn: "あなたのターンです" my-turn: "あんさんのターン"
opponent-turn: "相手のターンです" opponent-turn: "相手のターン"
turn-of: "{}のターンです" turn-of: "{}のターン"
past-turn-of: "{}のターン" past-turn-of: "{}のターン"
won: "{}の勝ち" won: "{}の勝ちや!"
black: "黒" black: "黒"
white: "白" white: "白"
total: "合計" total: "合計"
@ -123,67 +123,67 @@ common:
hashtags: "ハッシュタグ" hashtags: "ハッシュタグ"
deck: deck:
widgets: "ウィジェット" widgets: "ウィジェット"
home: "ホーム" home: "うち"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動や!"
swap-right: "右に移動" swap-right: "右に移動や!"
swap-up: "上に移動" swap-up: "上に移動"
swap-down: "下に移動" swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除や!"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更" rename: "名前を変更や!"
stack-left: "左に重ね" stack-left: "左に重ねんで!"
pop-right: "右に出す" pop-right: "右に出すで!"
auth/views/form.vue: auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?" share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?"
permission-ask: "このアプリは次の権限を要求しています:" permission-ask: "このアプリは次の権限を要求してんで:"
account-read: "アカウントの情報を見。" account-read: "アカウントの情報を見させてもらうで。"
account-write: "アカウントの情報を操作する。" account-write: "アカウントの情報を操作させてもらうで。"
note-write: "投稿する。" note-write: "投稿させてもらうで。"
like-write: "いいねしたりいいね解除する。" like-write: "いいねしたりいいね解除させてもらうで。"
following-write: "フォローしたりフォロー解除する。" following-write: "フォローしたりフォロー解除させてもらうで。"
drive-read: "ドライブを見。" drive-read: "ドライブを見させてもらうで。"
drive-write: "ドライブを操作する。" drive-write: "ドライブを操作させてもらうで。"
notification-read: "通知を見。" notification-read: "通知を見させてもらうで。"
notification-write: "通知を操作する。" notification-write: "通知を操作させてもらうで。"
cancel: "キャンセル" cancel: "やめとくわ"
accept: "アクセスを許可" accept: "アクセスを許可や!"
auth/views/index.vue: auth/views/index.vue:
loading: "読み込み中" loading: "読み込み中"
denied: "アプリケーションの連携をキャンセルしました。" denied: "アプリケーションの連携をやめといたわ。"
denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。" denied-paragraph: "このアプリがあんさんのアカウントにアクセスすることは多分あらへん。知らんけど。"
already-authorized: "このアプリは既に連携済みです" already-authorized: "このアプリはもう連携済みやったわ"
allowed: "アプリケーションの連携を許可しました" allowed: "アプリケーションの連携を許可した"
callback-url: "アプリケーションに戻っています" callback-url: "アプリケーションに戻っとります"
please-go-back: "アプリケーションに戻って、ってってください。" please-go-back: "アプリケーションに戻って、気張ってって。"
error: "セッションが存在しません。" error: "セッションが存在してへん。"
sign-in: "サインインしてください" sign-in: "サインインして"
common/views/components/games/reversi/reversi.vue: common/views/components/games/reversi/reversi.vue:
matching: matching:
waiting-for: "{}を待っています" waiting-for: "{}を待っとります"
cancel: "キャンセル" cancel: "やめとくわ"
common/views/components/games/reversi/reversi.game.vue: common/views/components/games/reversi/reversi.game.vue:
surrender: "投了" surrender: "投了や..."
surrendered: "投了により" surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)" is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ" looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード" can-put-everywhere: "どこに置いてもええモード"
common/views/components/games/reversi/reversi.index.vue: common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi" title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう" sub-title: "お隣のミスキストはんらとリバーシで対戦や!"
invite: "招待" invite: "招待"
rule: "遊び方" rule: "遊び方"
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。" rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてって、最終的に残った石が多い方が勝ちっちゅうボードゲーム。"
mode-invite: "招待" mode-invite: "招待"
mode-invite-desc: "指定したユーザーと対戦するモードです。" mode-invite-desc: "指定したユーザーと対戦するモード。"
invitations: "対局の招待があります" invitations: "対局の招待がきてんで"
my-games: "自分の対局" my-games: "自分の対局"
all-games: "みんなの対局" all-games: "みんなの対局"
enter-username: "ユーザー名を入力してください" enter-username: "ユーザー名を入力して"
game-state: game-state:
ended: "終了" ended: "終了"
playing: "進行中" playing: "進行中"
@ -202,7 +202,7 @@ common/views/components/games/reversi/reversi.room.vue:
waiting-for-other: "相手の準備が完了するのを待っています" waiting-for-other: "相手の準備が完了するのを待っています"
waiting-for-me: "あなたの準備が完了するのを待っています" waiting-for-me: "あなたの準備が完了するのを待っています"
waiting-for-both: "準備中" waiting-for-both: "準備中"
cancel: "キャンセル" cancel: "やめとくわ"
ready: "準備完了" ready: "準備完了"
cancel-ready: "準備続行" cancel-ready: "準備続行"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
@ -426,16 +426,16 @@ desktop/views/components/calendar.vue:
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
cancel: "キャンセル" cancel: "やめとくわ"
ok: "決定" ok: "決定"
choose-prompt: "ファイルを選択" choose-prompt: "ファイルを選択"
desktop/views/components/choose-folder-from-drive-window.vue: desktop/views/components/choose-folder-from-drive-window.vue:
cancel: "キャンセル" cancel: "やめとくわ"
ok: "決定" ok: "決定"
choose-prompt: "フォルダを選択" choose-prompt: "フォルダを選択"
desktop/views/components/crop-window.vue: desktop/views/components/crop-window.vue:
skip: "クロップをスキップ" skip: "クロップをスキップ"
cancel: "キャンセル" cancel: "やめとくわ"
ok: "決定" ok: "決定"
desktop/views/components/drive-window.vue: desktop/views/components/drive-window.vue:
used: "使用中" used: "使用中"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -522,7 +523,7 @@ desktop/views/components/home.vue:
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"
add: "追加" add: "追加"
desktop/views/input-dialog.vue: desktop/views/input-dialog.vue:
cancel: "キャンセル" cancel: "やめとくわ"
ok: "決定" ok: "決定"
desktop/views/components/messaging-room-window.vue: desktop/views/components/messaging-room-window.vue:
title: "メッセージ:" title: "メッセージ:"
@ -591,7 +592,7 @@ desktop/views/components/progress-dialog.vue:
waiting: "待機中" waiting: "待機中"
desktop/views/components/renote-form.vue: desktop/views/components/renote-form.vue:
quote: "引用する..." quote: "引用する..."
cancel: "キャンセル" cancel: "やめとくわ"
renote: "Renote" renote: "Renote"
reposting: "しています..." reposting: "しています..."
success: "Renoteしました" success: "Renoteしました"
@ -858,7 +859,7 @@ desktop/views/pages/note.vue:
desktop/views/pages/selectdrive.vue: desktop/views/pages/selectdrive.vue:
title: "ファイルを選択してください" title: "ファイルを選択してください"
ok: "決定" ok: "決定"
cancel: "キャンセル" cancel: "やめとくわ"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue: desktop/views/pages/search.vue:
not-available: "検索機能はインスタンスの設定で無効になっています。" not-available: "検索機能はインスタンスの設定で無効になっています。"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ common:
friday: "금요일" friday: "금요일"
saturday: "토요일" saturday: "토요일"
reactions: reactions:
like: "좋네" like: "ええやん"
love: "좋아" love: "좋아"
laugh: "크크" laugh: "크크"
hmm: "음..." hmm: "음..."
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

View File

@ -58,7 +58,7 @@ common:
friday: "Piątek" friday: "Piątek"
saturday: "Sobota" saturday: "Sobota"
reactions: reactions:
like: "Lubię" like: "ええやん"
love: "Kocham" love: "Kocham"
laugh: "Śmieszne" laugh: "Śmieszne"
hmm: "Hmm…?" hmm: "Hmm…?"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "Awatar" avatar: "Awatar"
banner: "Baner" banner: "Baner"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "Zmień nazwę" rename: "Zmień nazwę"
mark-as-sensitive: "Oznacz jako zawartość wrażliwą" mark-as-sensitive: "Oznacz jako zawartość wrażliwą"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "Wybierz plik" select-file: "Wybierz plik"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "Wybierz katalog" select-folder: "Wybierz katalog"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "Pobierz" download: "Pobierz"
rename: "Zmień nazwę" rename: "Zmień nazwę"
move: "Przenieś" move: "Przenieś"
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "To jest zawartość NSFW" sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić" click-to-show: "Naciśnij aby wyświetlić"

1219
locales/pt-PT.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "いいね" like: "ええやん"
love: "しゅき" love: "しゅき"
laugh: "笑" laugh: "笑"
hmm: "ふぅ~む" hmm: "ふぅ~む"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

View File

@ -58,7 +58,7 @@ common:
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
reactions: reactions:
like: "いいね" like: "ええやん"
love: "しゅき" love: "しゅき"
laugh: "笑" laugh: "笑"
hmm: "ふぅ~む" hmm: "ふぅ~む"
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定" mark-as-sensitive: "閲覧注意に設定"
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
select-folder: "フォルダーを選択" select-folder: "フォルダーを選択"
mobile/views/components/drive.file.vue:
nsfw: "閲覧注意"
mobile/views/components/drive.file-detail.vue: mobile/views/components/drive.file-detail.vue:
download: "ダウンロード" download: "ダウンロード"
rename: "名前を変更" rename: "名前を変更"
move: "移動" move: "移動"
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "7.0.0", "version": "8.0.0",
"clientVersion": "1.0.8654", "clientVersion": "1.0.8790",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -126,7 +126,7 @@
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.12.0", "hard-source-webpack-plugin": "0.12.0",
"highlight.js": "9.12.0", "highlight.js": "9.12.0",
"html-minifier": "3.5.19", "html-minifier": "3.5.20",
"http-signature": "1.2.0", "http-signature": "1.2.0",
"insert-text-at-cursor": "0.1.1", "insert-text-at-cursor": "0.1.1",
"is-root": "2.0.0", "is-root": "2.0.0",
@ -157,6 +157,7 @@
"monk": "6.0.6", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.1",
"nan": "2.10.0", "nan": "2.10.0",
"nested-property": "0.0.7",
"node-sass": "4.9.3", "node-sass": "4.9.3",
"node-sass-json-importer": "3.3.1", "node-sass-json-importer": "3.3.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
@ -181,7 +182,7 @@
"s-age": "1.1.2", "s-age": "1.1.2",
"sass-loader": "7.1.0", "sass-loader": "7.1.0",
"seedrandom": "2.4.4", "seedrandom": "2.4.4",
"sharp": "0.20.5", "sharp": "0.20.7",
"showdown": "1.8.6", "showdown": "1.8.6",
"showdown-highlightjs-extension": "0.1.2", "showdown-highlightjs-extension": "0.1.2",
"single-line-log": "1.1.2", "single-line-log": "1.1.2",
@ -190,7 +191,7 @@
"style-loader": "0.22.1", "style-loader": "0.22.1",
"stylus": "0.54.5", "stylus": "0.54.5",
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.1.3", "summaly": "2.1.4",
"systeminformation": "3.42.9", "systeminformation": "3.42.9",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
@ -206,9 +207,9 @@
"v-animate-css": "0.0.2", "v-animate-css": "0.0.2",
"vue": "2.5.17", "vue": "2.5.17",
"vue-cropperjs": "2.2.1", "vue-cropperjs": "2.2.1",
"vue-js-modal": "1.3.17", "vue-js-modal": "1.3.18",
"vue-json-tree-view": "2.1.4", "vue-json-tree-view": "2.1.4",
"vue-loader": "15.3.0", "vue-loader": "15.4.0",
"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",
@ -217,7 +218,7 @@
"vuex-persistedstate": "2.5.4", "vuex-persistedstate": "2.5.4",
"web-push": "3.3.2", "web-push": "3.3.2",
"webfinger.js": "2.6.6", "webfinger.js": "2.6.6",
"webpack": "4.16.5", "webpack": "4.17.1",
"webpack-cli": "3.1.0", "webpack-cli": "3.1.0",
"websocket": "1.0.26", "websocket": "1.0.26",
"ws": "6.0.0", "ws": "6.0.0",

View File

@ -7,7 +7,7 @@
<div class="app"> <div class="app">
<section> <section>
<h2>{{ app.name }}</h2> <h2>{{ app.name }}</h2>
<p class="nid">{{ app.nameId }}</p> <p class="id">{{ app.id }}</p>
<p class="description">{{ app.description }}</p> <p class="description">{{ app.description }}</p>
</section> </section>
<section> <section>

View File

@ -32,16 +32,24 @@
//#region Detect app name //#region Detect app name
let app = null; let app = null;
if (url.pathname == '/docs' || url.pathname.startsWith('/docs/')) app = 'docs'; if (`${url.pathname}/`.startsWith('/docs/')) app = 'docs';
if (url.pathname == '/dev' || url.pathname.startsWith('/dev/')) app = 'dev'; if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev';
if (url.pathname == '/auth' || url.pathname.startsWith('/auth/')) app = 'auth'; if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth';
//#endregion //#endregion
//#region Detect the user language //#region Detect the user language
let lang = navigator.language.split('-')[0]; let lang = null;
// The default language is English if (LANGS.includes(navigator.language)) {
if (!LANGS.includes(lang)) lang = 'en'; lang = navigator.language;
} else {
lang = LANGS.find(x => x.split('-')[0] == navigator.language);
if (lang == null) {
// Fallback
lang = 'en-US';
}
}
if (settings) { if (settings) {
if (settings.device.lang) lang = settings.device.lang; if (settings.device.lang) lang = settings.device.lang;
@ -104,7 +112,7 @@
// グローバルにタイマーIDを代入しておく // グローバルにタイマーIDを代入しておく
window.mkBootTimer = window.setTimeout(async () => { window.mkBootTimer = window.setTimeout(async () => {
// Fetch meta // Fetch meta
const res = await fetch(API + '/meta', { const res = await fetch('/api/meta', {
method: 'POST', method: 'POST',
cache: 'no-cache' cache: 'no-cache'
}); });

View File

@ -18,11 +18,11 @@
</div> </div>
<div class="board"> <div class="board">
<div class="labels-x" v-if="this.$store.state.settings.reversiBoardLabels"> <div class="labels-x" v-if="this.$store.state.settings.games.reversi.showBoardLabels">
<span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span> <span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div> </div>
<div class="flex"> <div class="flex">
<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels"> <div class="labels-y" v-if="this.$store.state.settings.games.reversi.showBoardLabels">
<div v-for="i in game.settings.map.length">{{ i }}</div> <div v-for="i in game.settings.map.length">{{ i }}</div>
</div> </div>
<div class="cells" :style="cellsStyle"> <div class="cells" :style="cellsStyle">
@ -30,15 +30,15 @@
:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }" :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
@click="set(i)" @click="set(i)"
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"> :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
<img v-if="stone === true" :src="blackUser.avatarUrl" alt=""> <img v-if="stone === true" :src="blackUser.avatarUrl" alt="black" :class="{ contrast: $store.state.settings.games.reversi.useContrastStones }">
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt=""> <img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white" :class="{ contrast: $store.state.settings.games.reversi.useContrastStones }">
</div> </div>
</div> </div>
<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels"> <div class="labels-y" v-if="this.$store.state.settings.games.reversi.showBoardLabels">
<div v-for="i in game.settings.map.length">{{ i }}</div> <div v-for="i in game.settings.map.length">{{ i }}</div>
</div> </div>
</div> </div>
<div class="labels-x" v-if="this.$store.state.settings.reversiBoardLabels"> <div class="labels-x" v-if="this.$store.state.settings.games.reversi.showBoardLabels">
<span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span> <span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div> </div>
</div> </div>
@ -421,6 +421,13 @@ root(isDark)
width 100% width 100%
height 100% height 100%
&.contrast
&[alt="black"]
filter brightness(.5)
&[alt="white"]
filter brightness(2)
> .graph > .graph
display grid display grid
grid-template-columns repeat(61, 1fr) grid-template-columns repeat(61, 1fr)

View File

@ -6,7 +6,7 @@
<i></i> <i></i>
<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a> <a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a>
<i></i> <i></i>
<a :href="devUrl">%i18n:@develop%</a> <a href="/dev">%i18n:@develop%</a>
<i></i> <i></i>
<a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a> <a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a>
</span> </span>
@ -14,18 +14,21 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { docsUrl, statsUrl, statusUrl, devUrl, repositoryUrl, feedbackUrl, lang } from '../../../config'; import { lang } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
aboutUrl: `${docsUrl}/${lang}/about`, aboutUrl: `/docs/${lang}/about`,
statsUrl, repositoryUrl: 'https://github.com/syuilo/misskey',
statusUrl, feedbackUrl: 'https://github.com/syuilo/misskey/issues/new'
devUrl,
repositoryUrl: repositoryUrl || `https://github.com/syuilo/misskey`,
feedbackUrl: feedbackUrl || `https://github.com/syuilo/misskey/issues/new`
} }
},
created() {
(this as any).os.getMeta().then(meta => {
if (meta.repositoryUrl) this.repositoryUrl = meta.repositoryUrl;
if (meta.feedbackUrl) this.feedbackUrl = meta.feedbackUrl;
});
} }
}); });
</script> </script>

View File

@ -12,13 +12,13 @@
</ui-input> </ui-input>
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/> <ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button> <ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
<p style="margin: 8px 0;" v-if="twitterIntegration">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p> <p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
</form> </form>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { apiUrl, host, twitterIntegration } from '../../../config'; import { apiUrl, host } from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: { props: {
@ -36,8 +36,7 @@ export default Vue.extend({
password: '', password: '',
token: '', token: '',
apiUrl, apiUrl,
host, host
twitterIntegration
}; };
}, },
methods: { methods: {

View File

@ -1,48 +1,50 @@
<template> <template>
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> <template v-if="meta">
<span>%i18n:@invitation-code%</span> <ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<span slot="prefix">%fa:id-card-alt%</span> <span>%i18n:@invitation-code%</span>
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p> <span slot="prefix">%fa:id-card-alt%</span>
</ui-input> <p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername"> </ui-input>
<span>%i18n:@username%</span> <ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
<span slot="prefix">@</span> <span>%i18n:@username%</span>
<span slot="suffix">@{{ host }}</span> <span slot="prefix">@</span>
<p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p> <span slot="suffix">@{{ host }}</span>
<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p> <p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p> <p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p> <p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
<p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p> <p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p>
<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p> <p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p>
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p> <p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
</ui-input> <p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true"> </ui-input>
<span>%i18n:@password%</span> <ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
<span slot="prefix">%fa:lock%</span> <span>%i18n:@password%</span>
<div slot="text"> <span slot="prefix">%fa:lock%</span>
<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p> <div slot="text">
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p> <p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p> <p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
</div> <p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
</ui-input> </div>
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype"> </ui-input>
<span>%i18n:@password% (%i18n:@retype%)</span> <ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
<span slot="prefix">%fa:lock%</span> <span>%i18n:@password% (%i18n:@retype%)</span>
<div slot="text"> <span slot="prefix">%fa:lock%</span>
<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p> <div slot="text">
<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p> <p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
</div> <p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
</ui-input> </div>
<div v-if="recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div> </ui-input>
<ui-button type="submit">%i18n:@create%</ui-button> <div v-if="meta.recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="meta.recaptchaSitekey" style="margin: 16px 0;"></div>
<ui-button type="submit">%i18n:@create%</ui-button>
</template>
</form> </form>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength'); const getPasswordStrength = require('syuilo-password-strength');
import { host, url, recaptchaSitekey } from '../../../config'; import { host, url } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -53,7 +55,6 @@ export default Vue.extend({
retypedPassword: '', retypedPassword: '',
invitationCode: '', invitationCode: '',
url, url,
recaptchaSitekey,
usernameState: null, usernameState: null,
passwordStrength: '', passwordStrength: '',
passwordRetypeState: null, passwordRetypeState: null,
@ -73,6 +74,12 @@ export default Vue.extend({
this.meta = meta; this.meta = meta;
}); });
}, },
mounted() {
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
head.appendChild(script);
},
methods: { methods: {
onChangeUsername() { onChangeUsername() {
if (this.username == '') { if (this.username == '') {
@ -123,7 +130,7 @@ export default Vue.extend({
username: this.username, username: this.username,
password: this.password, password: this.password,
invitationCode: this.invitationCode, invitationCode: this.invitationCode,
'g-recaptcha-response': recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null 'g-recaptcha-response': this.meta.recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null
}).then(() => { }).then(() => {
(this as any).api('signin', { (this as any).api('signin', {
username: this.username, username: this.username,
@ -134,19 +141,11 @@ export default Vue.extend({
}).catch(() => { }).catch(() => {
alert('%i18n:@some-error%'); alert('%i18n:@some-error%');
if (recaptchaSitekey != null) { if (this.meta.recaptchaSitekey != null) {
(window as any).grecaptcha.reset(); (window as any).grecaptcha.reset();
} }
}); });
} }
},
mounted() {
if (recaptchaSitekey != null) {
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
head.appendChild(script);
}
} }
}); });
</script> </script>

View File

@ -1,5 +1,7 @@
<template> <template>
<iframe v-if="player" :src="player" heigth="250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> <div v-if="player.url" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
<iframe :src="player.url" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
</div>
<div v-else-if="tweetUrl && detail" class="twitter"> <div v-else-if="tweetUrl && detail" class="twitter">
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null"> <blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
<a :href="url"></a> <a :href="url"></a>
@ -46,7 +48,11 @@ export default Vue.extend({
thumbnail: null, thumbnail: null,
icon: null, icon: null,
sitename: null, sitename: null,
player: null, player: {
url: null,
width: null,
height: null
},
tweetUrl: null, tweetUrl: null,
misskeyUrl misskeyUrl
}; };
@ -170,9 +176,17 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
iframe .twitter
position relative
width 100% width 100%
> iframe
height 100%
left 0
position absolute
top 0
width 100%
root(isDark) root(isDark)
> a > a
display block display block

View File

@ -1,51 +1,20 @@
declare const _HOST_: string;
declare const _HOSTNAME_: string;
declare const _URL_: string;
declare const _NAME_: string;
declare const _DESCRIPTION_: string;
declare const _API_URL_: string;
declare const _WS_URL_: string;
declare const _DOCS_URL_: string;
declare const _STATS_URL_: string;
declare const _STATUS_URL_: string;
declare const _DEV_URL_: string;
declare const _REPOSITORY_URL_: string;
declare const _FEEDBACK_URL_: string;
declare const _LANG_: string; declare const _LANG_: string;
declare const _LANGS_: string; declare const _LANGS_: string;
declare const _RECAPTCHA_SITEKEY_: string;
declare const _SW_PUBLICKEY_: string;
declare const _THEME_COLOR_: string; declare const _THEME_COLOR_: string;
declare const _COPYRIGHT_: string; declare const _COPYRIGHT_: string;
declare const _VERSION_: string; declare const _VERSION_: string;
declare const _CODENAME_: string; declare const _CODENAME_: string;
declare const _LICENSE_: string;
declare const _GOOGLE_MAPS_API_KEY_: string;
declare const _WELCOME_BG_URL_: string;
declare const _TWITTER_INTEGRATION_: boolean;
export const host = _HOST_; const address = new URL(location.href);
export const hostname = _HOSTNAME_;
export const url = _URL_; export const host = address.host;
export const name = _NAME_; export const hostname = address.hostname;
export const description = _DESCRIPTION_; export const url = address.origin;
export const apiUrl = _API_URL_; export const apiUrl = url + '/api';
export const wsUrl = _WS_URL_; export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://');
export const docsUrl = _DOCS_URL_;
export const statsUrl = _STATS_URL_;
export const statusUrl = _STATUS_URL_;
export const devUrl = _DEV_URL_;
export const repositoryUrl = _REPOSITORY_URL_;
export const feedbackUrl = _FEEDBACK_URL_;
export const lang = _LANG_; export const lang = _LANG_;
export const langs = _LANGS_; export const langs = _LANGS_;
export const recaptchaSitekey = _RECAPTCHA_SITEKEY_;
export const swPublickey = _SW_PUBLICKEY_;
export const themeColor = _THEME_COLOR_; export const themeColor = _THEME_COLOR_;
export const copyright = _COPYRIGHT_; export const copyright = _COPYRIGHT_;
export const version = _VERSION_; export const version = _VERSION_;
export const codename = _CODENAME_; export const codename = _CODENAME_;
export const license = _LICENSE_;
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
export const welcomeBgUrl = _WELCOME_BG_URL_;
export const twitterIntegration = _TWITTER_INTEGRATION_;

View File

@ -193,7 +193,7 @@ export default Vue.extend({
clearNotification() { clearNotification() {
this.unreadCount = 0; this.unreadCount = 0;
document.title = config.name; document.title = (this as any).os.instanceName;
}, },
onVisibilitychange() { onVisibilitychange() {

View File

@ -56,8 +56,9 @@
<mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%"> <mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
<span>%i18n:@show-maps-desc%</span> <span>%i18n:@show-maps-desc%</span>
</mk-switch> </mk-switch>
<mk-switch v-model="$store.state.settings.reversiBoardLabels" @change="onChangeReversiBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
<mk-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/> <mk-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/>
<mk-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
<mk-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones" text="%i18n:common.use-contrast-reversi-stones%"/>
</section> </section>
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
@ -191,12 +192,6 @@
<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button> <button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
</details> </details>
</section> </section>
<section class="other" v-show="page == 'other'">
<h1>%i18n:@license%</h1>
<div v-html="license"></div>
<a :href="licenseUrl" target="_blank">%i18n:@third-parties%</a>
</section>
</div> </div>
</div> </div>
</template> </template>
@ -211,7 +206,7 @@ import XApi from './settings.api.vue';
import XApps from './settings.apps.vue'; import XApps from './settings.apps.vue';
import XSignins from './settings.signins.vue'; import XSignins from './settings.signins.vue';
import XDrive from './settings.drive.vue'; import XDrive from './settings.drive.vue';
import { url, docsUrl, license, lang, langs, version } from '../../../config'; import { url, langs, version } from '../../../config';
import checkForUpdate from '../../../common/scripts/check-for-update'; import checkForUpdate from '../../../common/scripts/check-for-update';
import MkTaskManager from './taskmanager.vue'; import MkTaskManager from './taskmanager.vue';
@ -230,7 +225,6 @@ export default Vue.extend({
return { return {
page: 'profile', page: 'profile',
meta: null, meta: null,
license,
version, version,
langs, langs,
latestVersion: undefined, latestVersion: undefined,
@ -238,10 +232,6 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
licenseUrl(): string {
return `${docsUrl}/${lang}/license`;
},
apiViaStream: { apiViaStream: {
get() { return this.$store.state.device.apiViaStream; }, get() { return this.$store.state.device.apiViaStream; },
set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); } set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); }
@ -387,7 +377,13 @@ export default Vue.extend({
}, },
onChangeReversiBoardLabels(v) { onChangeReversiBoardLabels(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'reversiBoardLabels', key: 'games.reversi.showBoardLabels',
value: v
});
},
onChangeUseContrastReversiStones(v) {
this.$store.dispatch('settings/set', {
key: 'games.reversi.useContrastStones',
value: v value: v
}); });
}, },

View File

@ -30,10 +30,8 @@
<li @click="settings"> <li @click="settings">
<p>%fa:cog%<span>%i18n:@settings%</span>%fa:angle-right%</p> <p>%fa:cog%<span>%i18n:@settings%</span>%fa:angle-right%</p>
</li> </li>
</ul> <li v-if="$store.state.i.isAdmin">
<ul> <router-link to="/admin">%fa:terminal%<span>%i18n:@admin%</span>%fa:angle-right%</router-link>
<li @click="signout">
<p class="signout">%fa:power-off%<span>%i18n:@signout%</span></p>
</li> </li>
</ul> </ul>
<ul> <ul>
@ -41,6 +39,11 @@
<p><span>%i18n:@dark%</span><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template></p> <p><span>%i18n:@dark%</span><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template></p>
</li> </li>
</ul> </ul>
<ul>
<li @click="signout">
<p class="signout">%fa:power-off%<span>%i18n:@signout%</span></p>
</li>
</ul>
</div> </div>
</transition> </transition>
</div> </div>

View File

@ -11,6 +11,10 @@
<x-cpu-memory :connection="connection"/> <x-cpu-memory :connection="connection"/>
</div> </div>
<div> <div>
<label>
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
<span>disableRegistration</span>
</label>
<button class="ui" @click="invite">%i18n:@invite%</button> <button class="ui" @click="invite">%i18n:@invite%</button>
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p> <p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
</div> </div>
@ -28,6 +32,7 @@ export default Vue.extend({
data() { data() {
return { return {
stats: null, stats: null,
disableRegistration: false,
inviteCode: null, inviteCode: null,
connection: null, connection: null,
connectionId: null connectionId: null
@ -37,6 +42,10 @@ export default Vue.extend({
this.connection = (this as any).os.streams.serverStatsStream.getConnection(); this.connection = (this as any).os.streams.serverStatsStream.getConnection();
this.connectionId = (this as any).os.streams.serverStatsStream.use(); this.connectionId = (this as any).os.streams.serverStatsStream.use();
(this as any).os.getMeta().then(meta => {
this.disableRegistration = meta.disableRegistration;
});
(this as any).api('stats').then(stats => { (this as any).api('stats').then(stats => {
this.stats = stats; this.stats = stats;
}); });
@ -49,6 +58,11 @@ export default Vue.extend({
(this as any).api('admin/invite').then(x => { (this as any).api('admin/invite').then(x => {
this.inviteCode = x.code; this.inviteCode = x.code;
}); });
},
updateMeta() {
(this as any).api('admin/update-meta', {
disableRegistration: this.disableRegistration
});
} }
} }
}); });

View File

@ -4,11 +4,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
mounted() { mounted() {
document.title = `${config.name} - %i18n:@title%`; document.title = `${(this as any).os.instanceName} - %i18n:@title%`;
} }
}); });
</script> </script>

View File

@ -7,7 +7,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: { props: {
@ -17,7 +16,7 @@ export default Vue.extend({
} }
}, },
mounted() { mounted() {
document.title = config.name; document.title = (this as any).os.instanceName;
Progress.start(); Progress.start();
}, },

View File

@ -12,12 +12,11 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
name: config.name, name: (this as any).os.instanceName,
posted: false, posted: false,
text: new URLSearchParams(location.search).get('text') text: new URLSearchParams(location.search).get('text')
}; };

View File

@ -5,7 +5,7 @@
<template v-if="$store.state.device.darkmode">%fa:moon%</template> <template v-if="$store.state.device.darkmode">%fa:moon%</template>
<template v-else>%fa:R moon%</template> <template v-else>%fa:R moon%</template>
</button> </button>
<div class="body" :style="{ backgroundImage: `url('${ welcomeBgUrl }')` }"> <div class="body">
<div class="container"> <div class="container">
<div class="info"> <div class="info">
<span><b>{{ host }}</b></span> <span><b>{{ host }}</b></span>
@ -46,22 +46,26 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { host, name, description, copyright, welcomeBgUrl } from '../../../config'; import { host, copyright } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
stats: null, stats: null,
copyright, copyright,
welcomeBgUrl,
host, host,
name, name: 'Misskey',
description, description: '',
pointerInterval: null, pointerInterval: null,
tags: [] tags: []
}; };
}, },
created() { created() {
(this as any).os.getMeta().then(meta => {
this.name = meta.name;
this.description = meta.description;
});
(this as any).api('stats').then(stats => { (this as any).api('stats').then(stats => {
this.stats = stats; this.stats = stats;
}); });

View File

@ -5,16 +5,6 @@
<b-form-group label="アプリケーション名" description="あなたのアプリの名称。"> <b-form-group label="アプリケーション名" description="あなたのアプリの名称。">
<b-form-input v-model="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required/> <b-form-input v-model="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required/>
</b-form-group> </b-form-group>
<b-form-group label="ID" description="あなたのアプリのID。">
<b-input v-model="nid" type="text" pattern="^[a-zA-Z0-9_]{1,30}$" placeholder="ex) misskey-for-ios" autocomplete="off" required/>
<p class="info" v-if="nidState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%確認しています...</p>
<p class="info" v-if="nidState == 'ok'" style="color:#3CB7B5">%fa:fw check%利用できます</p>
<p class="info" v-if="nidState == 'unavailable'" style="color:#FF1161">%fa:fw exclamation-triangle%既に利用されています</p>
<p class="info" v-if="nidState == 'error'" style="color:#FF1161">%fa:fw exclamation-triangle%通信エラー</p>
<p class="info" v-if="nidState == 'invalid-format'" style="color:#FF1161">%fa:fw exclamation-triangle%a~zA~Z0~9_が使えます</p>
<p class="info" v-if="nidState == 'min-range'" style="color:#FF1161">%fa:fw exclamation-triangle%1文字以上でお願いします</p>
<p class="info" v-if="nidState == 'max-range'" style="color:#FF1161">%fa:fw exclamation-triangle%30文字以内でお願いします</p>
</b-form-group>
<b-form-group label="アプリの概要" description="あなたのアプリの簡単な説明や紹介。"> <b-form-group label="アプリの概要" description="あなたのアプリの簡単な説明や紹介。">
<b-textarea v-model="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required></b-textarea> <b-textarea v-model="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required></b-textarea>
</b-form-group> </b-form-group>
@ -50,47 +40,16 @@ export default Vue.extend({
data() { data() {
return { return {
name: '', name: '',
nid: '',
description: '', description: '',
cb: '', cb: '',
nidState: null, nidState: null,
permission: [] permission: []
}; };
}, },
watch: {
nid() {
if (this.nid == null || this.nid == '') {
this.nidState = null;
return;
}
const err =
!this.nid.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
this.nid.length < 1 ? 'min-range' :
this.nid.length > 30 ? 'max-range' :
null;
if (err) {
this.nidState = err;
return;
}
this.nidState = 'wait';
(this as any).api('app/name_id/available', {
nameId: this.nid
}).then(result => {
this.nidState = result.available ? 'ok' : 'unavailable';
}).catch(err => {
this.nidState = 'error';
});
}
},
methods: { methods: {
onSubmit() { onSubmit() {
(this as any).api('app/create', { (this as any).api('app/create', {
name: this.name, name: this.name,
nameId: this.nid,
description: this.description, description: this.description,
callbackUrl: this.cb, callbackUrl: this.cb,
permission: this.permission permission: this.permission

View File

@ -70,6 +70,10 @@ export default class MiOS extends EventEmitter {
chachedAt: Date; chachedAt: Date;
}; };
public get instanceName() {
return this.meta ? this.meta.data.name : 'Misskey';
}
private isMetaFetching = false; private isMetaFetching = false;
public app: Vue; public app: Vue;

View File

@ -99,7 +99,7 @@ export default Vue.extend({
cursor pointer cursor pointer
padding 0 16px padding 0 16px
margin 0 margin 0
min-width 150px min-width 100px
line-height 36px line-height 36px
font-size 14px font-size 14px
font-weight bold font-weight bold

View File

@ -12,6 +12,7 @@ import noteCard from './note-card.vue';
import userCard from './user-card.vue'; import userCard from './user-card.vue';
import noteDetail from './note-detail.vue'; import noteDetail from './note-detail.vue';
import followButton from './follow-button.vue'; import followButton from './follow-button.vue';
import muteButton from './mute-button.vue';
import friendsMaker from './friends-maker.vue'; import friendsMaker from './friends-maker.vue';
import notification from './notification.vue'; import notification from './notification.vue';
import notifications from './notifications.vue'; import notifications from './notifications.vue';
@ -36,6 +37,7 @@ Vue.component('mk-note-card', noteCard);
Vue.component('mk-user-card', userCard); Vue.component('mk-user-card', userCard);
Vue.component('mk-note-detail', noteDetail); Vue.component('mk-note-detail', noteDetail);
Vue.component('mk-follow-button', followButton); Vue.component('mk-follow-button', followButton);
Vue.component('mk-mute-button', muteButton);
Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-friends-maker', friendsMaker);
Vue.component('mk-notification', notification); Vue.component('mk-notification', notification);
Vue.component('mk-notifications', notifications); Vue.component('mk-notifications', notifications);

View File

@ -0,0 +1,79 @@
<template>
<button
class="mk-mute-button"
:class="{ active: user.isMuted }"
@click="onClick">
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
<span v-else>%fa:eye% %i18n:@unmute%</span>
</button>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props: {
user: {
type: Object,
required: true
}
},
methods: {
onClick() {
if (!this.user.isMuted) {
this.mute();
} else {
this.unmute();
}
},
mute() {
(this as any).api('mute/create', { userId: this.user.id})
.then(() => { this.user.isMuted = true })
.catch(() => { alert('error')})
},
unmute() {
(this as any).api('mute/delete', { userId: this.user.id })
.then(() => { this.user.isMuted = false })
.catch(() => { alert('error') })
}
},
})
</script>
<style lang="stylus" scoped>
@import '~const.styl'
.mk-mute-button
display block
user-select none
cursor pointer
padding 0 16px
margin 0
min-width 100px
line-height 36px
font-size 14px
font-weight bold
color $theme-color
background transparent
outline none
border solid 1px $theme-color
border-radius 36px
&:hover
background rgba($theme-color, 0.1)
&:active
background rgba($theme-color, 0.2)
&.active
color $theme-color-foreground
background $theme-color
&:hover
background lighten($theme-color, 10%)
border-color lighten($theme-color, 10%)
&:active
background darken($theme-color, 10%)
border-color darken($theme-color, 10%)
</style>

View File

@ -38,7 +38,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getNoteSummary from '../../../../../misc/get-note-summary'; import getNoteSummary from '../../../../../misc/get-note-summary';
import * as config from '../../../config';
const displayLimit = 30; const displayLimit = 30;
@ -190,7 +189,7 @@ export default Vue.extend({
clearNotification() { clearNotification() {
this.unreadCount = 0; this.unreadCount = 0;
document.title = config.name; document.title = (this as any).os.instanceName;
}, },
onVisibilitychange() { onVisibilitychange() {

View File

@ -8,7 +8,7 @@
<button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button> <button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button>
<template v-if="hasUnreadNotification || hasUnreadMessagingMessage || hasGameInvitation">%fa:circle%</template> <template v-if="hasUnreadNotification || hasUnreadMessagingMessage || hasGameInvitation">%fa:circle%</template>
<h1> <h1>
<slot>config.name</slot> <slot>{{ os.instanceName }}</slot>
</h1> </h1>
<slot name="func"></slot> <slot name="func"></slot>
</div> </div>
@ -20,13 +20,11 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as anime from 'animejs'; import * as anime from 'animejs';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: ['func'], props: ['func'],
data() { data() {
return { return {
config,
hasGameInvitation: false, hasGameInvitation: false,
connection: null, connection: null,
connectionId: null connectionId: null

View File

@ -30,6 +30,7 @@
<ul> <ul>
<li><a @click="search">%fa:search%%i18n:@search%%fa:angle-right%</a></li> <li><a @click="search">%fa:search%%i18n:@search%%fa:angle-right%</a></li>
<li><router-link to="/i/settings" :data-active="$route.name == 'settings'">%fa:cog%%i18n:@settings%%fa:angle-right%</router-link></li> <li><router-link to="/i/settings" :data-active="$route.name == 'settings'">%fa:cog%%i18n:@settings%%fa:angle-right%</router-link></li>
<li v-if="$store.getters.isSignedIn && $store.state.i.isAdmin"><router-link to="/admin">%fa:terminal%<span>%i18n:@admin%</span>%fa:angle-right%</router-link></li>
<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li> <li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
</ul> </ul>
</div> </div>
@ -41,7 +42,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { docsUrl, lang } from '../../../config'; import { lang } from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: ['isOpen'], props: ['isOpen'],
@ -50,7 +51,7 @@ export default Vue.extend({
hasGameInvitation: false, hasGameInvitation: false,
connection: null, connection: null,
connectionId: null, connectionId: null,
aboutUrl: `${docsUrl}/${lang}/about` aboutUrl: `/docs/${lang}/about`
}; };
}, },
computed: { computed: {

View File

@ -25,7 +25,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -44,7 +43,7 @@ export default Vue.extend({
window.addEventListener('popstate', this.onPopState); window.addEventListener('popstate', this.onPopState);
}, },
mounted() { mounted() {
document.title = `${config.name} Drive`; document.title = `${(this as any).os.instanceName} Drive`;
document.documentElement.style.background = '#fff'; document.documentElement.style.background = '#fff';
}, },
beforeDestroy() { beforeDestroy() {
@ -64,7 +63,7 @@ export default Vue.extend({
(this.$refs as any).browser.openContextMenu(); (this.$refs as any).browser.openContextMenu();
}, },
onMoveRoot(silent) { onMoveRoot(silent) {
const title = `${config.name} Drive`; const title = `${(this as any).os.instanceName} Drive`;
if (!silent) { if (!silent) {
// Rewrite URL // Rewrite URL
@ -77,7 +76,7 @@ export default Vue.extend({
this.folder = null; this.folder = null;
}, },
onOpenFolder(folder, silent) { onOpenFolder(folder, silent) {
const title = `${folder.name} | ${config.name} Drive`; const title = `${folder.name} | ${(this as any).os.instanceName} Drive`;
if (!silent) { if (!silent) {
// Rewrite URL // Rewrite URL
@ -90,7 +89,7 @@ export default Vue.extend({
this.folder = folder; this.folder = folder;
}, },
onOpenFile(file, silent) { onOpenFile(file, silent) {
const title = `${file.name} | ${config.name} Drive`; const title = `${file.name} | ${(this as any).os.instanceName} Drive`;
if (!silent) { if (!silent) {
// Rewrite URL // Rewrite URL

View File

@ -14,7 +14,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -29,7 +28,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
mounted() { mounted() {
document.title = `${config.name} | %i18n:@notifications%`; document.title = `${(this as any).os.instanceName} | %i18n:@notifications%`;
}, },
methods: { methods: {
fetch() { fetch() {

View File

@ -21,7 +21,6 @@ import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import getUserName from '../../../../../misc/get-user-name'; import getUserName from '../../../../../misc/get-user-name';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -50,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) + ' | ' + config.name; document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName;
}); });
}, },
onLoaded() { onLoaded() {

View File

@ -20,7 +20,6 @@
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -49,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) + ' | ' + config.name; document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName;
}); });
}, },
onLoaded() { onLoaded() {

View File

@ -7,11 +7,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as config from '../../../../config';
export default Vue.extend({ export default Vue.extend({
mounted() { mounted() {
document.title = `${config.name} %i18n:@reversi%`; document.title = `${(this as any).os.instanceName} %i18n:@reversi%`;
document.documentElement.style.background = '#fff'; document.documentElement.style.background = '#fff';
}, },
methods: { methods: {

View File

@ -49,7 +49,6 @@
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import XTl from './home.timeline.vue'; import XTl from './home.timeline.vue';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -97,7 +96,7 @@ export default Vue.extend({
}, },
mounted() { mounted() {
document.title = config.name; document.title = (this as any).os.instanceName;
Progress.start(); Progress.start();

View File

@ -11,7 +11,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -48,7 +47,7 @@ export default Vue.extend({
this.user = user; this.user = user;
this.fetching = false; this.fetching = false;
document.title = `%i18n:@messaging%: ${Vue.filter('userName')(this.user)} | ${config.name}`; document.title = `%i18n:@messaging%: ${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`;
}); });
} }
} }

View File

@ -8,11 +8,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getAcct from '../../../../../misc/acct/render'; import getAcct from '../../../../../misc/acct/render';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
mounted() { mounted() {
document.title = `${config.name} %i18n:@messaging%`; document.title = `${(this as any).os.instanceName} %i18n:@messaging%`;
}, },
methods: { methods: {
navigate(user) { navigate(user) {

View File

@ -16,7 +16,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -32,7 +31,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
mounted() { mounted() {
document.title = config.name; document.title = (this as any).os.instanceName;
}, },
methods: { methods: {
fetch() { fetch() {

View File

@ -12,7 +12,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import * as config from '../../../config';
const limit = 20; const limit = 20;
@ -35,7 +34,7 @@ export default Vue.extend({
} }
}, },
mounted() { mounted() {
document.title = `%i18n:@search%: ${this.q} | ${config.name}`; document.title = `%i18n:@search%: ${this.q} | ${(this as any).os.instanceName}`;
this.fetch(); this.fetch();
}, },

View File

@ -13,8 +13,9 @@
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch> <ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch> <ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch> <ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
<ui-switch v-model="$store.state.settings.reversiBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> <ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
<ui-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
<div> <div>
<div>%i18n:@timeline%</div> <div>%i18n:@timeline%</div>
@ -189,7 +190,14 @@ export default Vue.extend({
onChangeReversiBoardLabels(v) { onChangeReversiBoardLabels(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'reversiBoardLabels', key: 'games.reversi.showBoardLabels',
value: v
});
},
onChangeUseContrastReversiStones(v) {
this.$store.dispatch('settings/set', {
key: 'games.reversi.useContrastStones',
value: v value: v
}); });
}, },

View File

@ -91,15 +91,15 @@ export default Vue.extend({
method: 'POST', method: 'POST',
body: data body: data
}) })
.then(response => response.json()) .then(response => response.json())
.then(f => { .then(f => {
this.avatarId = f.id; this.avatarId = f.id;
this.avatarUploading = false; this.avatarUploading = false;
}) })
.catch(e => { .catch(e => {
this.avatarUploading = false; this.avatarUploading = false;
alert('%18n:!@upload-failed%'); alert('%18n:@upload-failed%');
}); });
}, },
onBannerChange([file]) { onBannerChange([file]) {
@ -113,15 +113,15 @@ export default Vue.extend({
method: 'POST', method: 'POST',
body: data body: data
}) })
.then(response => response.json()) .then(response => response.json())
.then(f => { .then(f => {
this.bannerId = f.id; this.bannerId = f.id;
this.bannerUploading = false; this.bannerUploading = false;
}) })
.catch(e => { .catch(e => {
this.bannerUploading = false; this.bannerUploading = false;
alert('%18n:!@upload-failed%'); alert('%18n:@upload-failed%');
}); });
}, },
save() { save() {

View File

@ -12,12 +12,11 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
name: config.name, name: (this as any).os.instanceName,
posted: false, posted: false,
text: new URLSearchParams(location.search).get('text') text: new URLSearchParams(location.search).get('text')
}; };

View File

@ -11,6 +11,7 @@
<a class="avatar"> <a class="avatar">
<img :src="user.avatarUrl" alt="avatar"/> <img :src="user.avatarUrl" alt="avatar"/>
</a> </a>
<mk-mute-button v-if="$store.state.i.id != user.id" :user="user"/>
<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> <mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
</div> </div>
<div class="title"> <div class="title">
@ -67,7 +68,6 @@ import * as age from 's-age';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import XHome from './user/home.vue'; import XHome from './user/home.vue';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -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) + ' | ' + config.name; document.title = Vue.filter('userName')(this.user) + ' | ' + (this as any).os.instanceName;
}); });
} }
} }
@ -185,6 +185,9 @@ root(isDark)
border 4px solid $bg border 4px solid $bg
border-radius 12px border-radius 12px
> .mk-mute-button
float right
> .mk-follow-button > .mk-follow-button
float right float right
@ -248,7 +251,7 @@ root(isDark)
top 47px top 47px
box-shadow 0 4px 4px isDark ? rgba(#000, 0.3) : rgba(#000, 0.07) box-shadow 0 4px 4px isDark ? rgba(#000, 0.3) : rgba(#000, 0.07)
background-color $bg background-color $bg
z-index 1 z-index 2
> .nav-container > .nav-container
display flex display flex

View File

@ -30,7 +30,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { apiUrl, copyright, host, name, description } from '../../../config'; import { apiUrl, copyright, host } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -39,12 +39,17 @@ export default Vue.extend({
copyright, copyright,
stats: null, stats: null,
host, host,
name, name: 'Misskey',
description, description: '',
tags: [] tags: []
}; };
}, },
created() { created() {
(this as any).os.getMeta().then(meta => {
this.name = meta.name;
this.description = meta.description;
});
(this as any).api('stats').then(stats => { (this as any).api('stats').then(stats => {
this.stats = stats; this.stats = stats;
}); });

View File

@ -53,7 +53,6 @@
import Vue from 'vue'; import Vue from 'vue';
import * as XDraggable from 'vuedraggable'; import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import * as config from '../../../config';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -103,7 +102,7 @@ export default Vue.extend({
}, },
mounted() { mounted() {
document.title = config.name; document.title = (this as any).os.instanceName;
}, },
methods: { methods: {

View File

@ -1,5 +1,6 @@
import Vuex from 'vuex'; import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate'; import createPersistedState from 'vuex-persistedstate';
import * as nestedProperty from 'nested-property';
import MiOS from './mios'; import MiOS from './mios';
import { hostname } from './config'; import { hostname } from './config';
@ -22,7 +23,12 @@ const defaultSettings = {
disableViaMobile: false, disableViaMobile: false,
memo: null, memo: null,
iLikeSushi: false, iLikeSushi: false,
reversiBoardLabels: false games: {
reversi: {
showBoardLabels: false,
useContrastStones: false
}
}
}; };
const defaultDeviceSettings = { const defaultDeviceSettings = {
@ -125,7 +131,7 @@ export default (os: MiOS) => new Vuex.Store({
mutations: { mutations: {
set(state, x: { key: string; value: any }) { set(state, x: { key: string; value: any }) {
state[x.key] = x.value; nestedProperty.set(state, x.key, x.value);
}, },
setHome(state, data) { setHome(state, data) {

View File

@ -82,7 +82,7 @@ props:
ja: "フォルダ" ja: "フォルダ"
en: "The folder of this file" en: "The folder of this file"
sensitive: isSensitive:
type: "boolean" type: "boolean"
optional: true optional: true
desc: desc:

View File

@ -14,6 +14,7 @@ import * as portscanner from 'portscanner';
import isRoot = require('is-root'); import isRoot = require('is-root');
import Xev from 'xev'; import Xev from 'xev';
import * as program from 'commander'; import * as program from 'commander';
import mongo from './db/mongodb';
import Logger from './misc/logger'; import Logger from './misc/logger';
import ProgressBar from './misc/cli/progressbar'; import ProgressBar from './misc/cli/progressbar';
@ -158,8 +159,13 @@ function checkMongoDb(config: Config) {
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null; const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
const uri = `mongodb://${u && p ? `${u}:****@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`; const uri = `mongodb://${u && p ? `${u}:****@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
mongoDBLogger.info(`Connecting to ${uri}`); mongoDBLogger.info(`Connecting to ${uri}`);
require('./db/mongodb');
mongoDBLogger.succ('Connectivity confirmed'); mongo.then(() => {
mongoDBLogger.succ('Connectivity confirmed');
})
.catch(err => {
mongoDBLogger.error(err.message);
});
} }
function spawnWorkers(limit: number) { function spawnWorkers(limit: number) {

View File

@ -49,9 +49,6 @@ export default function(html: string): string {
text += txt; text += txt;
break; break;
} }
// メンション以外
} else {
text += `[${txt}](${node.attrs.find((x: any) => x.name == 'href').value})`;
} }
if (node.childNodes) { if (node.childNodes) {

View File

@ -27,10 +27,12 @@ export default class Replacer {
let text = texts; let text = texts;
if (path) { if (path) {
path = path.replace('.ts', '');
if (text.hasOwnProperty(path)) { if (text.hasOwnProperty(path)) {
text = text[path]; text = text[path];
} else { } else {
if (this.lang === 'ja') console.warn(`path '${path}' not found`); if (this.lang === 'ja-JP') console.warn(`path '${path}' not found`);
return key; // Fallback return key; // Fallback
} }
} }
@ -46,10 +48,10 @@ export default class Replacer {
}); });
if (error) { if (error) {
if (this.lang === 'ja') console.warn(`key '${key}' not found in '${path}'`); if (this.lang === 'ja-JP') console.warn(`key '${key}' not found in '${path}'`);
return key; // Fallback return key; // Fallback
} else if (typeof text !== 'string') { } else if (typeof text !== 'string') {
if (this.lang === 'ja') console.warn(`key '${key}' is not string in '${path}'`); if (this.lang === 'ja-JP') console.warn(`key '${key}' is not string in '${path}'`);
return key; // Fallback return key; // Fallback
} else { } else {
return text; return text;

View File

@ -5,8 +5,6 @@ import db from '../db/mongodb';
import config from '../config'; import config from '../config';
const App = db.get<IApp>('apps'); const App = db.get<IApp>('apps');
App.createIndex('nameId');
App.createIndex('nameIdLower');
App.createIndex('secret'); App.createIndex('secret');
export default App; export default App;
@ -16,17 +14,11 @@ export type IApp = {
userId: mongo.ObjectID | null; userId: mongo.ObjectID | null;
secret: string; secret: string;
name: string; name: string;
nameId: string;
nameIdLower: string;
description: string; description: string;
permission: string[]; permission: string[];
callbackUrl: string; callbackUrl: string;
}; };
export function isValidNameId(nameId: string): boolean {
return typeof nameId == 'string' && /^[a-zA-Z0-9_]{1,30}$/.test(nameId);
}
/** /**
* Pack an app for API response * Pack an app for API response
* *
@ -76,8 +68,6 @@ export const pack = (
_app.id = _app._id; _app.id = _app._id;
delete _app._id; delete _app._id;
delete _app.nameIdLower;
// Visible by only owner // Visible by only owner
if (!opts.includeSecret) { if (!opts.includeSecret) {
delete _app.secret; delete _app.secret;

View File

@ -10,6 +10,8 @@ export interface IStats {
date: Date; date: Date;
span: 'day' | 'hour';
/** /**
* ユーザーに関する統計 * ユーザーに関する統計
*/ */

View File

@ -118,6 +118,7 @@ export interface IRemoteUser extends IUserBase {
publicKeyPem: string; publicKeyPem: string;
}; };
updatedAt: Date; updatedAt: Date;
isAdmin: false;
} }
export type IUser = ILocalUser | IRemoteUser; export type IUser = ILocalUser | IRemoteUser;

View File

@ -81,7 +81,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
// 添付メディア // 添付メディア
// TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしもImageではない
// TODO: attachmentは必ずしも配列ではない // TODO: attachmentは必ずしも配列ではない
// Noteがsensitiveなら添付もsensitiveにする
const media = note.attachment const media = note.attachment
.map(attach => attach.sensitive = note.sensitive)
? await Promise.all(note.attachment.map(x => resolveImage(actor, x))) ? await Promise.all(note.attachment.map(x => resolveImage(actor, x)))
: []; : [];

View File

@ -11,6 +11,7 @@ import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type';
import { IDriveFile } from '../../../models/drive-file'; import { IDriveFile } from '../../../models/drive-file';
import Meta from '../../../models/meta'; import Meta from '../../../models/meta';
import htmlToMFM from '../../../mfm/html-to-mfm'; import htmlToMFM from '../../../mfm/html-to-mfm';
import { updateUserStats } from '../../../services/update-chart';
const log = debug('misskey:activitypub'); const log = debug('misskey:activitypub');
@ -130,7 +131,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
endpoints: person.endpoints, endpoints: person.endpoints,
uri: person.id, uri: person.id,
url: person.url, url: person.url,
isBot isBot: isBot,
isCat: (person as any).isCat === true ? true : false
}) as IRemoteUser; }) as IRemoteUser;
} catch (e) { } catch (e) {
// duplicate key error // duplicate key error
@ -148,6 +150,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
'stats.usersCount': 1 'stats.usersCount': 1
} }
}, { upsert: true }); }, { upsert: true });
updateUserStats(user, true);
//#endregion //#endregion
//#region アイコンとヘッダー画像をフェッチ //#region アイコンとヘッダー画像をフェッチ
@ -259,7 +263,8 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
notesCount, notesCount,
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
} }
}); });
} }

View File

@ -79,6 +79,8 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
...mentionTags, ...mentionTags,
]; ];
const files = await promisedFiles;
return { return {
id: `${config.url}/notes/${note._id}`, id: `${config.url}/notes/${note._id}`,
type: 'Note', type: 'Note',
@ -89,7 +91,8 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
to, to,
cc, cc,
inReplyTo, inReplyTo,
attachment: (await promisedFiles).map(renderDocument), attachment: files.map(renderDocument),
sensitive: files.some(file => file.metadata.isSensitive),
tag tag
}; };
} }

View File

@ -29,6 +29,7 @@ export default async (user: ILocalUser) => {
icon: user.avatarId && renderImage(avatar), icon: user.avatarId && renderImage(avatar),
image: user.bannerId && renderImage(banner), image: user.bannerId && renderImage(banner),
manuallyApprovesFollowers: user.isLocked, manuallyApprovesFollowers: user.isLocked,
publicKey: renderKey(user) publicKey: renderKey(user),
isCat: user.isCat
}; };
}; };

View File

@ -16,6 +16,7 @@ export interface IObject {
image?: any; image?: any;
url?: string; url?: string;
tag?: any[]; tag?: any[];
sensitive?: boolean;
} }
export interface IActivity extends IObject { export interface IActivity extends IObject {

View File

@ -15,7 +15,7 @@ export default async (username: string, _host: string, option?: any): Promise<IU
const host = toUnicode(hostAscii); const host = toUnicode(hostAscii);
if (config.host == host) { if (config.host == host) {
return await User.findOne({ usernameLower }); return await User.findOne({ usernameLower, host: null });
} }
let user = await User.findOne({ usernameLower, host }, option); let user = await User.findOne({ usernameLower, host }, option);

View File

@ -41,10 +41,20 @@ function inbox(ctx: Router.IRouterContext) {
} }
function isActivityPubReq(ctx: Router.IRouterContext) { function isActivityPubReq(ctx: Router.IRouterContext) {
ctx.response.vary('Accept');
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json'); const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json');
return ['application/activity+json', 'application/ld+json'].includes(accepted as string); return ['application/activity+json', 'application/ld+json'].includes(accepted as string);
} }
export function setResponseType(ctx: Router.IRouterContext) {
const accpet = ctx.accepts('application/activity+json', 'application/ld+json');
if (accpet === 'application/ld+json') {
ctx.response.type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
} else {
ctx.response.type = 'application/activity+json; charset=utf-8';
}
}
// inbox // inbox
router.post('/inbox', json(), inbox); router.post('/inbox', json(), inbox);
router.post('/users/:user/inbox', json(), inbox); router.post('/users/:user/inbox', json(), inbox);
@ -54,7 +64,8 @@ router.get('/notes/:note', async (ctx, next) => {
if (!isActivityPubReq(ctx)) return await next(); if (!isActivityPubReq(ctx)) return await next();
const note = await Note.findOne({ const note = await Note.findOne({
_id: new mongo.ObjectID(ctx.params.note) _id: new mongo.ObjectID(ctx.params.note),
$or: [ { visibility: 'public' }, { visibility: 'home' } ]
}); });
if (note === null) { if (note === null) {
@ -62,7 +73,8 @@ router.get('/notes/:note', async (ctx, next) => {
return; return;
} }
ctx.body = pack(await renderNote(note)); ctx.body = pack(await renderNote(note, false));
setResponseType(ctx);
}); });
// outbox // outbox
@ -90,6 +102,7 @@ router.get('/users/:user/publickey', async ctx => {
if (isLocalUser(user)) { if (isLocalUser(user)) {
ctx.body = pack(renderKey(user)); ctx.body = pack(renderKey(user));
setResponseType(ctx);
} else { } else {
ctx.status = 400; ctx.status = 400;
} }
@ -103,6 +116,7 @@ async function userInfo(ctx: Router.IRouterContext, user: IUser) {
} }
ctx.body = pack(await renderPerson(user as ILocalUser)); ctx.body = pack(await renderPerson(user as ILocalUser));
setResponseType(ctx);
} }
router.get('/users/:user', async ctx => { router.get('/users/:user', async ctx => {

View File

@ -1,5 +1,5 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import * as Koa from 'koa'; import * as Router from 'koa-router';
import config from '../../config'; import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id'; import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user'; import User from '../../models/user';
@ -8,8 +8,9 @@ import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
import { setResponseType } from '../activitypub';
export default async (ctx: Koa.Context) => { export default async (ctx: Router.IRouterContext) => {
const userId = new mongo.ObjectID(ctx.params.user); const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter // Get 'cursor' parameter
@ -72,9 +73,11 @@ export default async (ctx: Koa.Context) => {
); );
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} else { } else {
// index page // index page
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null); const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} }
}; };

View File

@ -1,5 +1,5 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import * as Koa from 'koa'; import * as Router from 'koa-router';
import config from '../../config'; import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id'; import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user'; import User from '../../models/user';
@ -8,8 +8,9 @@ import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
import { setResponseType } from '../activitypub';
export default async (ctx: Koa.Context) => { export default async (ctx: Router.IRouterContext) => {
const userId = new mongo.ObjectID(ctx.params.user); const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter // Get 'cursor' parameter
@ -72,9 +73,11 @@ export default async (ctx: Koa.Context) => {
); );
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} else { } else {
// index page // index page
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null); const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} }
}; };

View File

@ -1,16 +1,17 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import * as Koa from 'koa'; import * as Router from 'koa-router';
import config from '../../config'; import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id'; import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user'; import User from '../../models/user';
import pack from '../../remote/activitypub/renderer'; import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import { setResponseType } from '../activitypub';
import Note from '../../models/note'; import Note from '../../models/note';
import renderNote from '../../remote/activitypub/renderer/note'; import renderNote from '../../remote/activitypub/renderer/note';
export default async (ctx: Koa.Context) => { export default async (ctx: Router.IRouterContext) => {
const userId = new mongo.ObjectID(ctx.params.user); const userId = new mongo.ObjectID(ctx.params.user);
// Get 'sinceId' parameter // Get 'sinceId' parameter
@ -83,7 +84,7 @@ export default async (ctx: Koa.Context) => {
if (sinceId) notes.reverse(); if (sinceId) notes.reverse();
const renderedNotes = await Promise.all(notes.map(note => renderNote(note))); const renderedNotes = await Promise.all(notes.map(note => renderNote(note, false)));
const rendered = renderOrderedCollectionPage( const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`, `${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
user.notesCount, renderedNotes, partOf, user.notesCount, renderedNotes, partOf,
@ -92,6 +93,7 @@ export default async (ctx: Koa.Context) => {
); );
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} else { } else {
// index page // index page
const rendered = renderOrderedCollection(partOf, user.notesCount, const rendered = renderOrderedCollection(partOf, user.notesCount,
@ -99,5 +101,6 @@ export default async (ctx: Koa.Context) => {
`${partOf}?page=true&since_id=000000000000000000000000` `${partOf}?page=true&since_id=000000000000000000000000`
); );
ctx.body = pack(rendered); ctx.body = pack(rendered);
setResponseType(ctx);
} }
}; };

View File

@ -1,6 +1,6 @@
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import limitter from './limitter'; import limitter from './limitter';
import { IUser, isLocalUser } from '../../models/user'; import { IUser } from '../../models/user';
import { IApp } from '../../models/app'; import { IApp } from '../../models/app';
import endpoints from './endpoints'; import endpoints from './endpoints';
@ -21,7 +21,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED'); return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
} }
if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) { if (ep.meta.requireAdmin && !user.isAdmin) {
return rej('YOU_ARE_NOT_ADMIN'); return rej('YOU_ARE_NOT_ADMIN');
} }

View File

@ -14,6 +14,7 @@ export default (params: any) => new Promise(async (res, rej) => {
const d = now.getDate(); const d = now.getDate();
const stats = await Stats.find({ const stats = await Stats.find({
span: 'day',
date: { date: {
$gt: new Date(y - 1, m, d) $gt: new Date(y - 1, m, d)
} }
@ -44,6 +45,7 @@ export default (params: any) => new Promise(async (res, rej) => {
} else { } else {
chart.unshift({ chart.unshift({
date: day, date: day,
span: 'day',
users: { users: {
local: { local: {
total: 0, total: 0,

View File

@ -34,6 +34,10 @@ export default (params: any) => new Promise(async (res, rej) => {
return rej('user not found'); return rej('user not found');
} }
if (user.isAdmin) {
return rej('cannot suspend admin');
}
await User.findOneAndUpdate({ await User.findOneAndUpdate({
_id: user._id _id: user._id
}, { }, {

View File

@ -0,0 +1,37 @@
import $ from 'cafy';
import Meta from '../../../../models/meta';
import getParams from '../../get-params';
export const meta = {
desc: {
ja: 'インスタンスの設定を更新します。'
},
requireCredential: true,
requireAdmin: true,
params: {
disableRegistration: $.bool.optional.nullable.note({
desc: {
ja: '招待制か否か'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const set = {} as any;
if (ps.disableRegistration === true || ps.disableRegistration === false) {
set.disableRegistration = ps.disableRegistration;
}
await Meta.update({}, {
$set: set
}, { upsert: true });
res();
});

View File

@ -1,6 +1,6 @@
import rndstr from 'rndstr'; import rndstr from 'rndstr';
import $ from 'cafy'; import $ from 'cafy';
import App, { isValidNameId, pack } from '../../../../models/app'; import App, { pack } from '../../../../models/app';
import { ILocalUser } from '../../../../models/user'; import { ILocalUser } from '../../../../models/user';
export const meta = { export const meta = {
@ -11,10 +11,6 @@ export const meta = {
* Create an app * Create an app
*/ */
export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'nameId' parameter
const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId);
if (nameIdErr) return rej('invalid nameId param');
// Get 'name' parameter // Get 'name' parameter
const [name, nameErr] = $.str.get(params.name); const [name, nameErr] = $.str.get(params.name);
if (nameErr) return rej('invalid name param'); if (nameErr) return rej('invalid name param');
@ -40,8 +36,6 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
createdAt: new Date(), createdAt: new Date(),
userId: user && user._id, userId: user && user._id,
name: name, name: name,
nameId: nameId,
nameIdLower: nameId.toLowerCase(),
description: description, description: description,
permission: permission, permission: permission,
callbackUrl: callbackUrl, callbackUrl: callbackUrl,
@ -49,5 +43,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
}); });
// Response // Response
res(await pack(app)); res(await pack(app, null, {
includeSecret: true
}));
}); });

View File

@ -1,31 +0,0 @@
/**
* Module dependencies
*/
import $ from 'cafy';
import App from '../../../../../models/app';
import { isValidNameId } from '../../../../../models/app';
/**
* Check available nameId of app
*
* @param {any} params
* @return {Promise<any>}
*/
export default async (params: any) => new Promise(async (res, rej) => {
// Get 'nameId' parameter
const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId);
if (nameIdErr) return rej('invalid nameId param');
// Get exist
const exist = await App
.count({
nameIdLower: nameId.toLowerCase()
}, {
limit: 1
});
// Reply
res({
available: exist === 0
});
});

View File

@ -9,21 +9,11 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
const isSecure = user != null && app == null; const isSecure = user != null && app == null;
// Get 'appId' parameter // Get 'appId' parameter
const [appId, appIdErr] = $.type(ID).optional.get(params.appId); const [appId, appIdErr] = $.type(ID).get(params.appId);
if (appIdErr) return rej('invalid appId param'); if (appIdErr) return rej('invalid appId param');
// Get 'nameId' parameter
const [nameId, nameIdErr] = $.str.optional.get(params.nameId);
if (nameIdErr) return rej('invalid nameId param');
if (appId === undefined && nameId === undefined) {
return rej('appId or nameId is required');
}
// Lookup app // Lookup app
const ap = appId !== undefined const ap = await App.findOne({ _id: appId });
? await App.findOne({ _id: appId })
: await App.findOne({ nameIdLower: nameId.toLowerCase() });
if (ap === null) { if (ap === null) {
return rej('app not found'); return rej('app not found');

View File

@ -34,9 +34,12 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
const sort = { const sort = {
_id: -1 _id: -1
}; };
const query = { const query = {
'metadata.userId': user._id 'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
} as any; } as any;
if (sinceId) { if (sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {
@ -47,6 +50,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
$lt: untilId $lt: untilId
}; };
} }
if (type) { if (type) {
query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`);
} }
@ -59,6 +63,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
}); });
// Serialize // Serialize
res(await Promise.all(files.map(async file => res(await Promise.all(files.map(file => pack(file))));
await pack(file))));
}); });

View File

@ -27,7 +27,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
return rej('followee not found'); return rej('followee not found');
} }
await cancelFollowRequest(followee, user); try {
await cancelFollowRequest(followee, user);
} catch (e) {
return rej(e);
}
// Send response // Send response
res(await pack(followee._id, user)); res(await pack(followee._id, user));

View File

@ -20,6 +20,9 @@ export default () => new Promise(async (res, rej) => {
version: pkg.version, version: pkg.version,
clientVersion: client.version, clientVersion: client.version,
name: config.name || 'Misskey',
description: config.description,
secure: config.https != null, secure: config.https != null,
machine: os.hostname(), machine: os.hostname(),
os: os.platform(), os: os.platform(),
@ -29,6 +32,8 @@ export default () => new Promise(async (res, rej) => {
cores: os.cpus().length cores: os.cpus().length
}, },
broadcasts: meta.broadcasts, broadcasts: meta.broadcasts,
disableRegistration: meta.disableRegistration disableRegistration: meta.disableRegistration,
recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
swPublickey: config.sw ? config.sw.public_key : null
}); });
}); });

View File

@ -12,8 +12,9 @@ export default async (ctx: Koa.Context) => {
ctx.set('Access-Control-Allow-Credentials', 'true'); ctx.set('Access-Control-Allow-Credentials', 'true');
const body = ctx.request.body as any; const body = ctx.request.body as any;
const username = body['username']; // See: https://github.com/syuilo/misskey/issues/2384
const password = body['password']; const username = body['username'] || body['x'];
const password = body['password'] || body['y'];
const token = body['token']; const token = body['token'];
if (typeof username != 'string') { if (typeof username != 'string') {
@ -63,7 +64,7 @@ export default async (ctx: Koa.Context) => {
if (verified) { if (verified) {
signin(ctx, user); signin(ctx, user);
} else { } else {
ctx.throw(400, { ctx.throw(403, {
error: 'invalid token' error: 'invalid token'
}); });
} }
@ -71,7 +72,7 @@ export default async (ctx: Koa.Context) => {
signin(ctx, user); signin(ctx, user);
} }
} else { } else {
ctx.throw(400, { ctx.throw(403, {
error: 'incorrect password' error: 'incorrect password'
}); });
} }

View File

@ -7,6 +7,7 @@ import generateUserToken from '../common/generate-native-user-token';
import config from '../../../config'; import config from '../../../config';
import Meta from '../../../models/meta'; import Meta from '../../../models/meta';
import RegistrationTicket from '../../../models/registration-tickets'; import RegistrationTicket from '../../../models/registration-tickets';
import { updateUserStats } from '../../../services/update-chart';
if (config.recaptcha) { if (config.recaptcha) {
recaptcha.init({ recaptcha.init({
@ -129,6 +130,8 @@ export default async (ctx: Koa.Context) => {
}, { upsert: true }); }, { upsert: true });
//#endregion //#endregion
updateUserStats(account, true);
// Response // Response
ctx.body = await pack(account); ctx.body = await pack(account);
}; };

View File

@ -11,13 +11,13 @@ export default async function(
subscriber: Xev, subscriber: Xev,
user: IUser user: IUser
) { ) {
const mute = await Mute.find({ muterId: user._id });
const mutedUserIds = mute.map(m => m.muteeId.toString());
// Subscribe stream // Subscribe stream
subscriber.on('hybrid-timeline', onEvent); subscriber.on('hybrid-timeline', onEvent);
subscriber.on(`hybrid-timeline:${user._id}`, onEvent); subscriber.on(`hybrid-timeline:${user._id}`, onEvent);
const mute = await Mute.find({ muterId: user._id });
const mutedUserIds = mute.map(m => m.muteeId.toString());
async function onEvent(note: any) { async function onEvent(note: any) {
//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する //#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (mutedUserIds.indexOf(note.userId) != -1) { if (mutedUserIds.indexOf(note.userId) != -1) {

View File

@ -12,6 +12,15 @@ export default async function(followee: IUser, follower: IUser) {
deliver(follower as ILocalUser, content, followee.inbox); deliver(follower as ILocalUser, content, followee.inbox);
} }
const request = await FollowRequest.findOne({
followeeId: followee._id,
followerId: follower._id
});
if (request == null) {
throw 'request not found';
}
await FollowRequest.remove({ await FollowRequest.remove({
followeeId: followee._id, followeeId: followee._id,
followerId: follower._id followerId: follower._id

View File

@ -5,89 +5,46 @@ import { IDriveFile } from '../models/drive-file';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
async function getTodayStats(): Promise<IStats> { async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> {
const now = new Date(); const now = new Date();
const y = now.getFullYear(); const y = now.getFullYear();
const m = now.getMonth(); const m = now.getMonth();
const d = now.getDate(); const d = now.getDate();
const today = new Date(y, m, d); const h = now.getHours();
// 今日の統計 const current =
const todayStats = await Stats.findOne({ span == 'day' ? new Date(y, m, d) :
date: today span == 'hour' ? new Date(y, m, d, h) :
null;
// 現在(今日または今のHour)の統計
const currentStats = await Stats.findOne({
span: span,
date: current
}); });
// 日付が変わってから、初めてのチャート更新なら if (currentStats) {
if (todayStats == null) { return currentStats;
} else {
// 集計期間が変わってから、初めてのチャート更新なら
// 最も最近の統計を持ってくる // 最も最近の統計を持ってくる
// * 例えば集計期間が「日」である場合で考えると、
// * 昨日何もチャートを更新するような出来事がなかった場合は、 // * 昨日何もチャートを更新するような出来事がなかった場合は、
// 統計がそもそも作られずドキュメントが存在しないということがあり得るため、 // * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
// 「昨日の」と決め打ちせずに「もっとも最近の」とします // * 「昨日の」と決め打ちせずに「もっとも最近の」とします
const mostRecentStats = await Stats.findOne({}, { const mostRecentStats = await Stats.findOne({
span: span
}, {
sort: { sort: {
date: -1 date: -1
} }
}); });
// 統計が存在しなかったら if (mostRecentStats) {
// * Misskeyインスタンスを建てて初めてのチャート更新時など // 現在の統計を初期挿入
if (mostRecentStats == null) {
// 空の統計を作成
const data: Omit<IStats, '_id'> = { const data: Omit<IStats, '_id'> = {
date: today, span: span,
users: { date: current,
local: {
total: 0,
diff: 0
},
remote: {
total: 0,
diff: 0
}
},
notes: {
local: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
}
}
};
const stats = await Stats.insert(data);
return stats;
} else {
// 今日の統計を初期挿入
const data: Omit<IStats, '_id'> = {
date: today,
users: { users: {
local: { local: {
total: mostRecentStats.users.local.total, total: mostRecentStats.users.local.total,
@ -136,20 +93,83 @@ async function getTodayStats(): Promise<IStats> {
const stats = await Stats.insert(data); const stats = await Stats.insert(data);
return stats;
} else {
// 統計が存在しなかったら
// * Misskeyインスタンスを建てて初めてのチャート更新時など
// 空の統計を作成
const emptyStat: Omit<IStats, '_id'> = {
span: span,
date: current,
users: {
local: {
total: 0,
diff: 0
},
remote: {
total: 0,
diff: 0
}
},
notes: {
local: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
}
}
};
const stats = await Stats.insert(emptyStat);
return stats; return stats;
} }
} else {
return todayStats;
} }
} }
async function update(inc: any) { function update(inc: any) {
const stats = await getTodayStats(); getCurrentStats('day').then(stats => {
Stats.findOneAndUpdate({
_id: stats._id
}, {
$inc: inc
});
});
await Stats.findOneAndUpdate({ getCurrentStats('hour').then(stats => {
_id: stats._id Stats.findOneAndUpdate({
}, { _id: stats._id
$inc: inc }, {
$inc: inc
});
}); });
} }

View File

@ -16,8 +16,6 @@ import I18nReplacer from './src/misc/i18n';
import { pattern as i18nPattern, replacement as i18nReplacement } from './webpack/i18n'; import { pattern as i18nPattern, replacement as i18nReplacement } from './webpack/i18n';
import { pattern as faPattern, replacement as faReplacement } from './src/misc/fa'; import { pattern as faPattern, replacement as faReplacement } from './src/misc/fa';
const constants = require('./src/const.json'); const constants = require('./src/const.json');
import config from './src/config';
import { licenseHtml } from './src/misc/license';
const locales = require('./locales'); const locales = require('./locales');
const meta = require('./package.json'); const meta = require('./package.json');
@ -58,8 +56,6 @@ const isProduction = process.env.NODE_ENV == 'production';
const entry = { const entry = {
desktop: './src/client/app/desktop/script.ts', desktop: './src/client/app/desktop/script.ts',
mobile: './src/client/app/mobile/script.ts', mobile: './src/client/app/mobile/script.ts',
//stats: './src/client/app/stats/script.ts',
//status: './src/client/app/status/script.ts',
dev: './src/client/app/dev/script.ts', dev: './src/client/app/dev/script.ts',
auth: './src/client/app/auth/script.ts', auth: './src/client/app/auth/script.ts',
sw: './src/client/app/sw.js' sw: './src/client/app/sw.js'
@ -72,31 +68,12 @@ const output = {
//#region Define consts //#region Define consts
const consts = { const consts = {
_RECAPTCHA_SITEKEY_: config.recaptcha ? config.recaptcha.site_key : null,
_SW_PUBLICKEY_: config.sw ? config.sw.public_key : null,
_THEME_COLOR_: constants.themeColor, _THEME_COLOR_: constants.themeColor,
_COPYRIGHT_: constants.copyright, _COPYRIGHT_: constants.copyright,
_VERSION_: version, _VERSION_: version,
_CODENAME_: codename, _CODENAME_: codename,
_STATUS_URL_: config.status_url,
_STATS_URL_: config.stats_url,
_DOCS_URL_: config.docs_url,
_API_URL_: config.api_url,
_WS_URL_: config.ws_url,
_DEV_URL_: config.dev_url,
_REPOSITORY_URL_: config.maintainer.repository_url,
_FEEDBACK_URL_: config.maintainer.feedback_url,
_LANG_: '%lang%', _LANG_: '%lang%',
_LANGS_: Object.keys(locales).map(l => [l, locales[l].meta.lang]), _LANGS_: Object.keys(locales).map(l => [l, locales[l].meta.lang])
_NAME_: config.name,
_DESCRIPTION_: config.description,
_HOST_: config.host,
_HOSTNAME_: config.hostname,
_URL_: config.url,
_LICENSE_: licenseHtml,
_GOOGLE_MAPS_API_KEY_: config.google_maps_api_key,
_WELCOME_BG_URL_: config.welcome_bg_url,
_TWITTER_INTEGRATION_: config.twitter != null
}; };
const _consts: { [ key: string ]: any } = {}; const _consts: { [ key: string ]: any } = {};