Compare commits
165 Commits
Author | SHA1 | Date | |
---|---|---|---|
a0adcf0d1a | |||
93b2b82993 | |||
4dee7d91b1 | |||
59e2ed8ab0 | |||
83790004dd | |||
efae7a7bce | |||
e59f13e8ff | |||
31ed8949b9 | |||
f1174a15e0 | |||
912ffae600 | |||
71a5662195 | |||
774834a31f | |||
91f1c3a10a | |||
8fc1e07136 | |||
a62e2b83ff | |||
e31a2f7e55 | |||
1598e996b1 | |||
4fb7ee760a | |||
37865cb381 | |||
ab8b882435 | |||
1b2996947e | |||
ea56d368e3 | |||
dbd3a750f5 | |||
f41818141f | |||
d2f576accd | |||
4e483856d4 | |||
2997f26e3c | |||
cdab596240 | |||
fca7a9da94 | |||
8ba178f795 | |||
d98c67e13c | |||
d129151fdf | |||
1b9c69f793 | |||
42dd092334 | |||
8dc9ec06f8 | |||
ae5da782e5 | |||
313b0cec65 | |||
12a51972ed | |||
a2f3b2966f | |||
0c2627f08b | |||
9535df12dd | |||
2400471a0d | |||
784da8c37b | |||
3778f9c521 | |||
e1cec85f1e | |||
ca9c087060 | |||
5b2d91baad | |||
08e099b88d | |||
4153b0db38 | |||
88701a21bb | |||
3ddc73ca94 | |||
693d793265 | |||
5d685233dd | |||
16575751d9 | |||
1f6295f437 | |||
6737fe2ead | |||
4e77939fca | |||
21f528c07d | |||
23f835fac0 | |||
f21343225c | |||
f13a59f7db | |||
0315b9274c | |||
da88043962 | |||
0352bf0cc2 | |||
baa71070a8 | |||
2713064f27 | |||
f6387ac115 | |||
d704aca035 | |||
2b54b4ac06 | |||
7410f2f4c0 | |||
99c3c1258a | |||
e51184931d | |||
3bc9a40b48 | |||
9f49ca8fdb | |||
550d1547b4 | |||
ca0b56ee57 | |||
ef1d854f2c | |||
a5023271ef | |||
c3747db670 | |||
fe1e60a28c | |||
f91d2e8c8d | |||
dccc2c60e3 | |||
933e25804c | |||
0b503661af | |||
58082431ff | |||
2536bfb5f5 | |||
6428066552 | |||
4bf3827b73 | |||
3cad494404 | |||
ef0793311f | |||
6f3e341e89 | |||
2fea3be7c0 | |||
82059d4fd9 | |||
07ddeae2f1 | |||
f2279758b2 | |||
1ed189a518 | |||
137741d307 | |||
d702f6e090 | |||
f33701233c | |||
70003269e5 | |||
61896d2386 | |||
52d640c5a7 | |||
c65f5761e1 | |||
3016ac4805 | |||
28a47cd331 | |||
6ecb88b0d1 | |||
8df1278c8e | |||
52bec430d4 | |||
da4cec8767 | |||
ad0087d7dd | |||
9630860035 | |||
75e4c8d74d | |||
1a5ee81e7e | |||
3476be16ab | |||
c42f61a0f4 | |||
b42a9e1c4e | |||
4495525705 | |||
a603602f32 | |||
67b28f9b6e | |||
fd947407af | |||
30444e5f1a | |||
f0d818de24 | |||
3fb98e808f | |||
2c9bacfcea | |||
ae0284b1b1 | |||
166c4ebda0 | |||
319eed029b | |||
ec5aa10167 | |||
a542765cf8 | |||
fd3f8d43db | |||
4f4496078a | |||
8f4f5b4ce0 | |||
bdc6718ae5 | |||
09d1f1c20d | |||
0efb7af17b | |||
a45a78b94f | |||
6337c26cf0 | |||
208dec25d9 | |||
7d65a0c3d5 | |||
bb925e5de3 | |||
c9de5b65d4 | |||
9bc3fcf74f | |||
19d979c330 | |||
bb7b335491 | |||
be5a0b4794 | |||
48eea03386 | |||
566317dc83 | |||
d4334645c2 | |||
d60c3c4ee3 | |||
452514f7fe | |||
58abb0ce8f | |||
000d9cc1ce | |||
8cc017354a | |||
7caa083612 | |||
68e1b00eb1 | |||
1d904c756a | |||
0a1db1f595 | |||
e30e8267dd | |||
288a881817 | |||
f593790872 | |||
1044ad8589 | |||
6e24015e68 | |||
b9a2c449ff | |||
7c390cbf7b | |||
a657d1c774 |
86
.autogen/autogen.sh
Executable file
86
.autogen/autogen.sh
Executable 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
|
28
README.md
28
README.md
@ -39,9 +39,15 @@ please see [Setup and installation guide](./docs/setup.en.md).
|
||||
----------------------------------------------------------------
|
||||
**[PR](https://github.com/syuilo/misskey/pulls)s welcome!**
|
||||
|
||||
If you want to...
|
||||
* i18n ... please see [Translation guide](./docs/translate.en.md).
|
||||
* l10n ... please visit https://crowdin.com/project/misskey
|
||||
### i18n
|
||||
|
||||
Please see [Translation guide](./docs/translate.en.md).
|
||||
|
||||
### l10n
|
||||
|
||||
Misskey is using Crowdin for l10n.
|
||||
|
||||
[](https://crowdin.com/project/misskey)
|
||||
|
||||
:heart: Backers & Sponsors
|
||||
----------------------------------------------------------------
|
||||
@ -49,35 +55,41 @@ If you want to...
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
||||
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/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/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
<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/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=12931605">Reiju</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
|
||||
</tr></table>
|
||||
<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/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
|
||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Sat, 18 Aug 2018 02:02:58 UTC
|
||||
**Last updated:** Wed, 22 Aug 2018 05:25:06 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
144
cli/migration/8.0.0.js
Normal file
144
cli/migration/8.0.0.js
Normal 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();
|
@ -1,3 +1,3 @@
|
||||
files:
|
||||
- source: /locales/ja.yml
|
||||
translation: /locales/%two_letters_code%.yml
|
||||
- source: /locales/ja-JP.yml
|
||||
translation: /locales/%locale%.yml
|
||||
|
@ -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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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!
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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 !
|
||||
|
||||
|
@ -11,12 +11,12 @@ Misskey内の未翻訳箇所を見つけたら
|
||||
- `foo`は実際にはその場に適したわかりやすい(英語の)名前にしてください。
|
||||
- 例えば未翻訳箇所が「タイムライン」というテキストだった場合、`%i18n:@timeline%`のようにします。
|
||||
|
||||
3. `locales/ja.yml`を開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。
|
||||
3. `locales/ja-JP.yml`を開き、1.で見つけた<strong>ファイル名(パス)</strong>のキーが存在するか確認し、無ければ作成してください。
|
||||
- パスの`src/client/app/`は省略してください。
|
||||
- 例えば、今回の例では`src/client/app/mobile/views/pages/home.vue`の未翻訳箇所を修正したいので、キーは`mobile/views/pages/home.vue`になります。
|
||||
|
||||
4. そのキーの直下に2.で置換した`foo`の部分をキーとし、テキストを値とするプロパティを追加します。
|
||||
- 例えば、今回の例で言うと`locales/ja.yml`に`timeline: "タイムライン"`を追加します。
|
||||
- 例えば、今回の例で言うと`locales/ja-JP.yml`に`timeline: "タイムライン"`を追加します。
|
||||
|
||||
5. 完了です!
|
||||
|
||||
|
13
gulpfile.ts
13
gulpfile.ts
@ -23,7 +23,6 @@ const uglifyes = require('uglify-es');
|
||||
|
||||
const locales = require('./locales');
|
||||
import { fa } from './src/misc/fa';
|
||||
import config from './src/config';
|
||||
|
||||
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.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([
|
||||
'./build/Release/crypto_key.node',
|
||||
'./src/const.json',
|
||||
@ -118,7 +126,6 @@ gulp.task('build:client:script', () => {
|
||||
const client = require('./built/client/meta.json');
|
||||
return gulp.src(['./src/client/app/boot.js', './src/client/app/safe.js'])
|
||||
.pipe(replace('VERSION', JSON.stringify(client.version)))
|
||||
.pipe(replace('API', JSON.stringify(config.api_url)))
|
||||
.pipe(replace('ENV', JSON.stringify(env)))
|
||||
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
|
||||
.pipe(isProduction ? uglify({
|
||||
|
@ -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...
|
||||
* i18n ... please see [Translation guide](../docs/translate.en.md).
|
||||
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
like: "ええやん"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
hmm: "ふぅ~む"
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "Gefällt mir"
|
||||
like: "ええやん"
|
||||
love: "Lieben"
|
||||
laugh: "Lachen"
|
||||
hmm: "Hmm...?"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "Umbenennen"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
nsfw: "NSFW"
|
||||
contextmenu:
|
||||
rename: "Rename"
|
||||
mark-as-sensitive: "Mark as 'sensitive'"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "Choose files"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "Choose a folder"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "NSFW"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "Download"
|
||||
rename: "Rename"
|
||||
move: "Move"
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "NSFW"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "Click to show"
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "Viernes"
|
||||
saturday: "Sábado"
|
||||
reactions:
|
||||
like: "me gusta"
|
||||
like: "ええやん"
|
||||
love: "amor"
|
||||
laugh: "risa"
|
||||
hmm: "hmm"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "Renombrar"
|
||||
mark-as-sensitive: "Marcar como 'sensible'"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
@ -30,7 +30,7 @@ common:
|
||||
quoted-by: "Cité·e par {} :"
|
||||
time:
|
||||
unknown: "inconnu"
|
||||
future: "future"
|
||||
future: "à l'instant"
|
||||
just_now: "à l'instant"
|
||||
seconds_ago: "Il y a {} seconde·s"
|
||||
minutes_ago: "Il y a {} minute·s"
|
||||
@ -58,7 +58,7 @@ common:
|
||||
friday: "Vendredi"
|
||||
saturday: "Samedi"
|
||||
reactions:
|
||||
like: "Aime"
|
||||
like: "ええやん"
|
||||
love: "Adore"
|
||||
laugh: "Rire"
|
||||
hmm: "Hmm ... ?"
|
||||
@ -287,7 +287,7 @@ common/views/components/signin.vue:
|
||||
signin: "Se connecter"
|
||||
or: "Ou"
|
||||
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:
|
||||
invitation-code: "Code d’invitation"
|
||||
invitation-info: "Si vous n’avez pas de code d’invitation, contactez un·e <a href=\"{}\">administrateur·rice</a>."
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Avatar"
|
||||
banner: "Bannière"
|
||||
nsfw: "CW"
|
||||
contextmenu:
|
||||
rename: "Renommer"
|
||||
mark-as-sensitive: "Marquer comme sensible"
|
||||
@ -637,7 +638,7 @@ desktop/views/components/settings.vue:
|
||||
show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
|
||||
sound: "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"
|
||||
test: "Test"
|
||||
mobile: "Mobile"
|
||||
@ -698,7 +699,7 @@ desktop/views/components/settings.2fa.vue:
|
||||
desktop/views/components/settings.api.vue:
|
||||
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
|
||||
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
|
||||
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
|
||||
regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer."
|
||||
regenerate-token: "Regenerer le token"
|
||||
token: "Jeton :"
|
||||
enter-password: "Veuillez entrer le mot de passe"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "Choisissez un fichier"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "Choisissez un dossier"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "Télécharger"
|
||||
rename: "Renommer"
|
||||
move: "Déplacer"
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "Le contenu est NSFW"
|
||||
click-to-show: "Cliquer pour afficher"
|
@ -8,16 +8,16 @@ const yaml = require('js-yaml');
|
||||
const loadLang = lang => yaml.safeLoad(
|
||||
fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
|
||||
|
||||
const native = loadLang('ja');
|
||||
const native = loadLang('ja-JP');
|
||||
|
||||
const langs = {
|
||||
'de': loadLang('de'),
|
||||
'en': loadLang('en'),
|
||||
'fr': loadLang('fr'),
|
||||
'ja': native,
|
||||
'ja-ks': loadLang('ja-ks'),
|
||||
'pl': loadLang('pl'),
|
||||
'es': loadLang('es')
|
||||
'de-DE': loadLang('de-DE'),
|
||||
'en-US': loadLang('en-US'),
|
||||
'fr-FR': loadLang('fr-FR'),
|
||||
'ja-JP': native,
|
||||
'ja-KS': loadLang('ja-KS'),
|
||||
'pl-PL': loadLang('pl-PL'),
|
||||
'es-ES': loadLang('es-ES')
|
||||
};
|
||||
|
||||
Object.values(langs).forEach(locale => {
|
||||
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
like: "ええやん"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
hmm: "ふぅ~む"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,33 @@
|
||||
---
|
||||
meta:
|
||||
lang: "Português"
|
||||
lang: "日本語 (関西弁)"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "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:
|
||||
detected: "広告ブロッカーを無効にしてください"
|
||||
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
||||
detected: "広告ブロッカーを無効にしてや"
|
||||
warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "閉じる"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
||||
got-it: "わかった"
|
||||
close: "さいなら"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
|
||||
got-it: "ほい"
|
||||
customization-tips:
|
||||
title: "カスタマイズのヒント"
|
||||
paragraph1: "ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。"
|
||||
paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。"
|
||||
paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。"
|
||||
paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
|
||||
paragraph1: "ホームのカスタマイズやと、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりできんねやわ。"
|
||||
paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>したったら表示を変更できんねやわ。"
|
||||
paragraph3: "ウィジェットを削除するんやったら、ヘッダーの<strong>「ゴミ箱」</strong>と書いたぁるエリアにウィジェットをドラッグ&ドロップしてな。"
|
||||
paragraph4: "カスタマイズを終了するんやったら、右上の「完了」をクリックしてな。"
|
||||
gotit: "Got it!"
|
||||
notification:
|
||||
file-uploaded: "ファイルがアップロードされました"
|
||||
message-from: "{}さんからメッセージ:"
|
||||
reversi-invited: "対局への招待があります"
|
||||
reversi-invited-by: "{}さんから"
|
||||
notified-by: "{}さんから"
|
||||
reply-from: "{}さんから返信:"
|
||||
quoted-by: "{}さんが引用:"
|
||||
file-uploaded: "ファイルがアップロードされたで"
|
||||
message-from: "{}はんからメッセージ:"
|
||||
reversi-invited: "対局への招待がきとるで"
|
||||
reversi-invited-by: "{}はんから"
|
||||
notified-by: "{}はんから"
|
||||
reply-from: "{}はんから返信:"
|
||||
quoted-by: "{}はんが引用:"
|
||||
time:
|
||||
unknown: "なぞのじかん"
|
||||
future: "未来"
|
||||
@ -58,41 +58,41 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
like: "ええやん"
|
||||
love: "好きやねん"
|
||||
laugh: "わろた"
|
||||
hmm: "ふぅ~む"
|
||||
surprise: "わお"
|
||||
congrats: "おめでとう"
|
||||
angry: "おこ"
|
||||
confused: "こまこまのこまり"
|
||||
congrats: "おめでとうさん"
|
||||
angry: "何言うてまんねん"
|
||||
confused: "こまこまのこまりやわぁ"
|
||||
rip: "RIP"
|
||||
pudding: "Pudding"
|
||||
pudding: "アメちゃんちゃうんちゃう?"
|
||||
note-placeholders:
|
||||
a: "今どうしてる?"
|
||||
b: "何かありましたか?"
|
||||
c: "何をお考えですか?"
|
||||
d: "言いたいことは?"
|
||||
e: "ここに書いてください"
|
||||
f: "あなたが書くのを待っています..."
|
||||
a: "今なにしてん?"
|
||||
b: "何かあったんか?"
|
||||
c: "何考えとりますん?"
|
||||
d: "言うときたいことは?"
|
||||
e: "ここに書いてや"
|
||||
f: "あんさんが書くんを待っちょります..."
|
||||
search: "検索"
|
||||
delete: "削除"
|
||||
loading: "読み込み中"
|
||||
ok: "わかった"
|
||||
update-available-title: "更新があります"
|
||||
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
||||
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
ok: "ほい"
|
||||
update-available-title: "更新があんで"
|
||||
update-available: "Misskeyの新しいバージョンがあんで({newer}。現在{current}をつこてるわ)。ページを再度読み込みしたると更新が適用されるわ。"
|
||||
my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。"
|
||||
i-like-sushi: "寿司(のほうがプリンよりむしろ)ウマい、タコ焼きはあらへんけど。"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示や!"
|
||||
verified-user: "アメちゃん付きアカウント"
|
||||
disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
|
||||
reversi:
|
||||
drawn: "引き分け"
|
||||
my-turn: "あなたのターンです"
|
||||
opponent-turn: "相手のターンです"
|
||||
turn-of: "{}のターンです"
|
||||
drawn: "おあいこ"
|
||||
my-turn: "あんさんのターンや"
|
||||
opponent-turn: "相手のターンや"
|
||||
turn-of: "{}のターンや"
|
||||
past-turn-of: "{}のターン"
|
||||
won: "{}の勝ち"
|
||||
won: "{}の勝ちや!"
|
||||
black: "黒"
|
||||
white: "白"
|
||||
total: "合計"
|
||||
@ -123,67 +123,67 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
deck:
|
||||
widgets: "ウィジェット"
|
||||
home: "ホーム"
|
||||
home: "うち"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
swap-right: "右に移動"
|
||||
swap-up: "上に移動"
|
||||
swap-down: "下に移動"
|
||||
remove: "カラムを削除"
|
||||
add-column: "カラムを追加"
|
||||
rename: "名前を変更"
|
||||
stack-left: "左に重ねる"
|
||||
pop-right: "右に出す"
|
||||
swap-left: "左に移動や!"
|
||||
swap-right: "右に移動や!"
|
||||
swap-up: "上に移動!"
|
||||
swap-down: "下に移動!"
|
||||
remove: "カラムを削除や!"
|
||||
add-column: "カラムを追加!"
|
||||
rename: "名前を変更や!"
|
||||
stack-left: "左に重ねんで!"
|
||||
pop-right: "右に出すで!"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
account-read: "アカウントの情報を見る。"
|
||||
account-write: "アカウントの情報を操作する。"
|
||||
note-write: "投稿する。"
|
||||
like-write: "いいねしたりいいね解除する。"
|
||||
following-write: "フォローしたりフォロー解除する。"
|
||||
drive-read: "ドライブを見る。"
|
||||
drive-write: "ドライブを操作する。"
|
||||
notification-read: "通知を見る。"
|
||||
notification-write: "通知を操作する。"
|
||||
cancel: "キャンセル"
|
||||
accept: "アクセスを許可"
|
||||
share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?"
|
||||
permission-ask: "このアプリは次の権限を要求してんで:"
|
||||
account-read: "アカウントの情報を見させてもらうで。"
|
||||
account-write: "アカウントの情報を操作させてもらうで。"
|
||||
note-write: "投稿させてもらうで。"
|
||||
like-write: "いいねしたりいいね解除させてもらうで。"
|
||||
following-write: "フォローしたりフォロー解除させてもらうで。"
|
||||
drive-read: "ドライブを見させてもらうで。"
|
||||
drive-write: "ドライブを操作させてもらうで。"
|
||||
notification-read: "通知を見させてもらうで。"
|
||||
notification-write: "通知を操作させてもらうで。"
|
||||
cancel: "やめとくわ"
|
||||
accept: "アクセスを許可や!"
|
||||
auth/views/index.vue:
|
||||
loading: "読み込み中"
|
||||
denied: "アプリケーションの連携をキャンセルしました。"
|
||||
denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
|
||||
already-authorized: "このアプリは既に連携済みです"
|
||||
allowed: "アプリケーションの連携を許可しました"
|
||||
callback-url: "アプリケーションに戻っています"
|
||||
please-go-back: "アプリケーションに戻って、やっていってください。"
|
||||
error: "セッションが存在しません。"
|
||||
sign-in: "サインインしてください"
|
||||
denied: "アプリケーションの連携をやめといたわ。"
|
||||
denied-paragraph: "このアプリがあんさんのアカウントにアクセスすることは多分あらへん。知らんけど。"
|
||||
already-authorized: "このアプリはもう連携済みやったわ"
|
||||
allowed: "アプリケーションの連携を許可したで"
|
||||
callback-url: "アプリケーションに戻っとります"
|
||||
please-go-back: "アプリケーションに戻って、気張ってってな。"
|
||||
error: "セッションが存在してへん。"
|
||||
sign-in: "サインインしてや"
|
||||
common/views/components/games/reversi/reversi.vue:
|
||||
matching:
|
||||
waiting-for: "{}を待っています"
|
||||
cancel: "キャンセル"
|
||||
waiting-for: "{}を待っとります"
|
||||
cancel: "やめとくわ"
|
||||
common/views/components/games/reversi/reversi.game.vue:
|
||||
surrender: "投了"
|
||||
surrender: "投了や..."
|
||||
surrendered: "投了により"
|
||||
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||
looped-map: "ループマップ"
|
||||
can-put-everywhere: "どこでも置けるモード"
|
||||
can-put-everywhere: "どこに置いてもええモード"
|
||||
common/views/components/games/reversi/reversi.index.vue:
|
||||
title: "Misskey Reversi"
|
||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||
sub-title: "お隣のミスキストはんらとリバーシで対戦や!"
|
||||
invite: "招待"
|
||||
rule: "遊び方"
|
||||
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
|
||||
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてって、最終的に残った石が多い方が勝ちっちゅうボードゲームや。"
|
||||
mode-invite: "招待"
|
||||
mode-invite-desc: "指定したユーザーと対戦するモードです。"
|
||||
invitations: "対局の招待があります!"
|
||||
mode-invite-desc: "指定したユーザーと対戦するモードや。"
|
||||
invitations: "対局の招待がきてんで!"
|
||||
my-games: "自分の対局"
|
||||
all-games: "みんなの対局"
|
||||
enter-username: "ユーザー名を入力してください"
|
||||
enter-username: "ユーザー名を入力してや"
|
||||
game-state:
|
||||
ended: "終了"
|
||||
playing: "進行中"
|
||||
@ -202,7 +202,7 @@ common/views/components/games/reversi/reversi.room.vue:
|
||||
waiting-for-other: "相手の準備が完了するのを待っています"
|
||||
waiting-for-me: "あなたの準備が完了するのを待っています"
|
||||
waiting-for-both: "準備中"
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
ready: "準備完了"
|
||||
cancel-ready: "準備続行"
|
||||
common/views/components/connect-failed.vue:
|
||||
@ -426,16 +426,16 @@ desktop/views/components/calendar.vue:
|
||||
desktop/views/components/choose-file-from-drive-window.vue:
|
||||
choose-file: "ファイル選択中"
|
||||
upload: "PCからドライブにファイルをアップロード"
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
ok: "決定"
|
||||
choose-prompt: "ファイルを選択"
|
||||
desktop/views/components/choose-folder-from-drive-window.vue:
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
ok: "決定"
|
||||
choose-prompt: "フォルダを選択"
|
||||
desktop/views/components/crop-window.vue:
|
||||
skip: "クロップをスキップ"
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
ok: "決定"
|
||||
desktop/views/components/drive-window.vue:
|
||||
used: "使用中"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -522,7 +523,7 @@ desktop/views/components/home.vue:
|
||||
add-widget: "ウィジェットを追加:"
|
||||
add: "追加"
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
ok: "決定"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "メッセージ:"
|
||||
@ -591,7 +592,7 @@ desktop/views/components/progress-dialog.vue:
|
||||
waiting: "待機中"
|
||||
desktop/views/components/renote-form.vue:
|
||||
quote: "引用する..."
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
renote: "Renote"
|
||||
reposting: "しています..."
|
||||
success: "Renoteしました!"
|
||||
@ -858,7 +859,7 @@ desktop/views/pages/note.vue:
|
||||
desktop/views/pages/selectdrive.vue:
|
||||
title: "ファイルを選択してください"
|
||||
ok: "決定"
|
||||
cancel: "キャンセル"
|
||||
cancel: "やめとくわ"
|
||||
upload: "PCからドライブにファイルをアップロード"
|
||||
desktop/views/pages/search.vue:
|
||||
not-available: "検索機能はインスタンスの設定で無効になっています。"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
1412
locales/ja-ks.yml
1412
locales/ja-ks.yml
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,7 @@ common:
|
||||
friday: "금요일"
|
||||
saturday: "토요일"
|
||||
reactions:
|
||||
like: "좋네"
|
||||
like: "ええやん"
|
||||
love: "좋아"
|
||||
laugh: "크크"
|
||||
hmm: "음..."
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "Piątek"
|
||||
saturday: "Sobota"
|
||||
reactions:
|
||||
like: "Lubię"
|
||||
like: "ええやん"
|
||||
love: "Kocham"
|
||||
laugh: "Śmieszne"
|
||||
hmm: "Hmm…?"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Awatar"
|
||||
banner: "Baner"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "Zmień nazwę"
|
||||
mark-as-sensitive: "Oznacz jako zawartość wrażliwą"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "Wybierz plik"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "Wybierz katalog"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "Pobierz"
|
||||
rename: "Zmień nazwę"
|
||||
move: "Przenieś"
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "To jest zawartość NSFW"
|
||||
click-to-show: "Naciśnij aby wyświetlić"
|
1219
locales/pt-PT.yml
Normal file
1219
locales/pt-PT.yml
Normal file
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,7 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
like: "ええやん"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
hmm: "ふぅ~む"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
@ -58,7 +58,7 @@ common:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
like: "ええやん"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
hmm: "ふぅ~む"
|
||||
@ -443,6 +443,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
@ -954,12 +955,15 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
select-file: "ファイルを選択"
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "フォルダーを選択"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "ダウンロード"
|
||||
rename: "名前を変更"
|
||||
move: "移動"
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
19
package.json
19
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "7.0.0",
|
||||
"clientVersion": "1.0.8654",
|
||||
"version": "8.2.0",
|
||||
"clientVersion": "1.0.8818",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -89,6 +89,7 @@
|
||||
"bootstrap-vue": "2.0.0-rc.11",
|
||||
"cafy": "11.3.0",
|
||||
"chalk": "2.4.1",
|
||||
"chart.js": "2.7.2",
|
||||
"commander": "2.17.1",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "1.0.0",
|
||||
@ -126,7 +127,7 @@
|
||||
"gulp-util": "3.0.8",
|
||||
"hard-source-webpack-plugin": "0.12.0",
|
||||
"highlight.js": "9.12.0",
|
||||
"html-minifier": "3.5.19",
|
||||
"html-minifier": "3.5.20",
|
||||
"http-signature": "1.2.0",
|
||||
"insert-text-at-cursor": "0.1.1",
|
||||
"is-root": "2.0.0",
|
||||
@ -157,6 +158,7 @@
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nan": "2.10.0",
|
||||
"nested-property": "0.0.7",
|
||||
"node-sass": "4.9.3",
|
||||
"node-sass-json-importer": "3.3.1",
|
||||
"nprogress": "0.2.0",
|
||||
@ -181,7 +183,7 @@
|
||||
"s-age": "1.1.2",
|
||||
"sass-loader": "7.1.0",
|
||||
"seedrandom": "2.4.4",
|
||||
"sharp": "0.20.5",
|
||||
"sharp": "0.20.7",
|
||||
"showdown": "1.8.6",
|
||||
"showdown-highlightjs-extension": "0.1.2",
|
||||
"single-line-log": "1.1.2",
|
||||
@ -190,7 +192,7 @@
|
||||
"style-loader": "0.22.1",
|
||||
"stylus": "0.54.5",
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.1.3",
|
||||
"summaly": "2.1.4",
|
||||
"systeminformation": "3.42.9",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
@ -205,10 +207,11 @@
|
||||
"uuid": "3.3.2",
|
||||
"v-animate-css": "0.0.2",
|
||||
"vue": "2.5.17",
|
||||
"vue-chartjs": "3.4.0",
|
||||
"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-loader": "15.3.0",
|
||||
"vue-loader": "15.4.0",
|
||||
"vue-router": "3.0.1",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
@ -217,7 +220,7 @@
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"web-push": "3.3.2",
|
||||
"webfinger.js": "2.6.6",
|
||||
"webpack": "4.16.5",
|
||||
"webpack": "4.17.1",
|
||||
"webpack-cli": "3.1.0",
|
||||
"websocket": "1.0.26",
|
||||
"ws": "6.0.0",
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="app">
|
||||
<section>
|
||||
<h2>{{ app.name }}</h2>
|
||||
<p class="nid">{{ app.nameId }}</p>
|
||||
<p class="id">{{ app.id }}</p>
|
||||
<p class="description">{{ app.description }}</p>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -32,16 +32,24 @@
|
||||
//#region Detect app name
|
||||
let app = null;
|
||||
|
||||
if (url.pathname == '/docs' || url.pathname.startsWith('/docs/')) app = 'docs';
|
||||
if (url.pathname == '/dev' || url.pathname.startsWith('/dev/')) app = 'dev';
|
||||
if (url.pathname == '/auth' || url.pathname.startsWith('/auth/')) app = 'auth';
|
||||
if (`${url.pathname}/`.startsWith('/docs/')) app = 'docs';
|
||||
if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev';
|
||||
if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth';
|
||||
//#endregion
|
||||
|
||||
//#region Detect the user language
|
||||
let lang = navigator.language.split('-')[0];
|
||||
let lang = null;
|
||||
|
||||
// The default language is English
|
||||
if (!LANGS.includes(lang)) lang = 'en';
|
||||
if (LANGS.includes(navigator.language)) {
|
||||
lang = navigator.language;
|
||||
} else {
|
||||
lang = LANGS.find(x => x.split('-')[0] == navigator.language);
|
||||
|
||||
if (lang == null) {
|
||||
// Fallback
|
||||
lang = 'en-US';
|
||||
}
|
||||
}
|
||||
|
||||
if (settings) {
|
||||
if (settings.device.lang) lang = settings.device.lang;
|
||||
@ -104,7 +112,7 @@
|
||||
// グローバルにタイマーIDを代入しておく
|
||||
window.mkBootTimer = window.setTimeout(async () => {
|
||||
// Fetch meta
|
||||
const res = await fetch(API + '/meta', {
|
||||
const res = await fetch('/api/meta', {
|
||||
method: 'POST',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
@ -18,11 +18,11 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
<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 }"
|
||||
@click="set(i)"
|
||||
: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 === false" :src="whiteUser.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="white" :class="{ contrast: $store.state.settings.games.reversi.useContrastStones }">
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -421,6 +421,13 @@ root(isDark)
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
&.contrast
|
||||
&[alt="black"]
|
||||
filter brightness(.5)
|
||||
|
||||
&[alt="white"]
|
||||
filter brightness(2)
|
||||
|
||||
> .graph
|
||||
display grid
|
||||
grid-template-columns repeat(61, 1fr)
|
||||
|
@ -6,7 +6,7 @@
|
||||
<i>・</i>
|
||||
<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a>
|
||||
<i>・</i>
|
||||
<a :href="devUrl">%i18n:@develop%</a>
|
||||
<a href="/dev">%i18n:@develop%</a>
|
||||
<i>・</i>
|
||||
<a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a>
|
||||
</span>
|
||||
@ -14,18 +14,21 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { docsUrl, statsUrl, statusUrl, devUrl, repositoryUrl, feedbackUrl, lang } from '../../../config';
|
||||
import { lang } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
aboutUrl: `${docsUrl}/${lang}/about`,
|
||||
statsUrl,
|
||||
statusUrl,
|
||||
devUrl,
|
||||
repositoryUrl: repositoryUrl || `https://github.com/syuilo/misskey`,
|
||||
feedbackUrl: feedbackUrl || `https://github.com/syuilo/misskey/issues/new`
|
||||
aboutUrl: `/docs/${lang}/about`,
|
||||
repositoryUrl: 'https://github.com/syuilo/misskey',
|
||||
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>
|
||||
|
@ -12,13 +12,13 @@
|
||||
</ui-input>
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { apiUrl, host, twitterIntegration } from '../../../config';
|
||||
import { apiUrl, host } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -36,8 +36,7 @@ export default Vue.extend({
|
||||
password: '',
|
||||
token: '',
|
||||
apiUrl,
|
||||
host,
|
||||
twitterIntegration
|
||||
host
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -1,48 +1,50 @@
|
||||
<template>
|
||||
<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>
|
||||
<span>%i18n:@invitation-code%</span>
|
||||
<span slot="prefix">%fa:id-card-alt%</span>
|
||||
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
||||
</ui-input>
|
||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
|
||||
<span>%i18n:@username%</span>
|
||||
<span slot="prefix">@</span>
|
||||
<span slot="suffix">@{{ host }}</span>
|
||||
<p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
|
||||
<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
|
||||
<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
|
||||
<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</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 == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
|
||||
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
|
||||
</ui-input>
|
||||
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
|
||||
<span>%i18n:@password%</span>
|
||||
<span slot="prefix">%fa:lock%</span>
|
||||
<div slot="text">
|
||||
<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
|
||||
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
|
||||
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
||||
</div>
|
||||
</ui-input>
|
||||
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
|
||||
<span>%i18n:@password% (%i18n:@retype%)</span>
|
||||
<span slot="prefix">%fa:lock%</span>
|
||||
<div slot="text">
|
||||
<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
|
||||
<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
|
||||
</div>
|
||||
</ui-input>
|
||||
<div v-if="recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
|
||||
<ui-button type="submit">%i18n:@create%</ui-button>
|
||||
<template v-if="meta">
|
||||
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
|
||||
<span>%i18n:@invitation-code%</span>
|
||||
<span slot="prefix">%fa:id-card-alt%</span>
|
||||
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
||||
</ui-input>
|
||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
|
||||
<span>%i18n:@username%</span>
|
||||
<span slot="prefix">@</span>
|
||||
<span slot="suffix">@{{ host }}</span>
|
||||
<p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
|
||||
<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
|
||||
<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
|
||||
<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</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 == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
|
||||
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
|
||||
</ui-input>
|
||||
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
|
||||
<span>%i18n:@password%</span>
|
||||
<span slot="prefix">%fa:lock%</span>
|
||||
<div slot="text">
|
||||
<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
|
||||
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
|
||||
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
||||
</div>
|
||||
</ui-input>
|
||||
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
|
||||
<span>%i18n:@password% (%i18n:@retype%)</span>
|
||||
<span slot="prefix">%fa:lock%</span>
|
||||
<div slot="text">
|
||||
<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
|
||||
<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
|
||||
</div>
|
||||
</ui-input>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
const getPasswordStrength = require('syuilo-password-strength');
|
||||
import { host, url, recaptchaSitekey } from '../../../config';
|
||||
import { host, url } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -53,7 +55,6 @@ export default Vue.extend({
|
||||
retypedPassword: '',
|
||||
invitationCode: '',
|
||||
url,
|
||||
recaptchaSitekey,
|
||||
usernameState: null,
|
||||
passwordStrength: '',
|
||||
passwordRetypeState: null,
|
||||
@ -73,6 +74,12 @@ export default Vue.extend({
|
||||
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: {
|
||||
onChangeUsername() {
|
||||
if (this.username == '') {
|
||||
@ -123,7 +130,7 @@ export default Vue.extend({
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
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(() => {
|
||||
(this as any).api('signin', {
|
||||
username: this.username,
|
||||
@ -134,19 +141,11 @@ export default Vue.extend({
|
||||
}).catch(() => {
|
||||
alert('%i18n:@some-error%');
|
||||
|
||||
if (recaptchaSitekey != null) {
|
||||
if (this.meta.recaptchaSitekey != null) {
|
||||
(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>
|
||||
|
@ -1,5 +1,7 @@
|
||||
<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">
|
||||
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
|
||||
<a :href="url"></a>
|
||||
@ -46,7 +48,11 @@ export default Vue.extend({
|
||||
thumbnail: null,
|
||||
icon: null,
|
||||
sitename: null,
|
||||
player: null,
|
||||
player: {
|
||||
url: null,
|
||||
width: null,
|
||||
height: null
|
||||
},
|
||||
tweetUrl: null,
|
||||
misskeyUrl
|
||||
};
|
||||
@ -170,9 +176,17 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
iframe
|
||||
.player
|
||||
position relative
|
||||
width 100%
|
||||
|
||||
> iframe
|
||||
height 100%
|
||||
left 0
|
||||
position absolute
|
||||
top 0
|
||||
width 100%
|
||||
|
||||
root(isDark)
|
||||
> a
|
||||
display block
|
||||
|
@ -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 _LANGS_: string;
|
||||
declare const _RECAPTCHA_SITEKEY_: string;
|
||||
declare const _SW_PUBLICKEY_: string;
|
||||
declare const _THEME_COLOR_: string;
|
||||
declare const _COPYRIGHT_: string;
|
||||
declare const _VERSION_: 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_;
|
||||
export const hostname = _HOSTNAME_;
|
||||
export const url = _URL_;
|
||||
export const name = _NAME_;
|
||||
export const description = _DESCRIPTION_;
|
||||
export const apiUrl = _API_URL_;
|
||||
export const wsUrl = _WS_URL_;
|
||||
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_;
|
||||
const address = new URL(location.href);
|
||||
|
||||
export const host = address.host;
|
||||
export const hostname = address.hostname;
|
||||
export const url = address.origin;
|
||||
export const apiUrl = url + '/api';
|
||||
export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||
export const lang = _LANG_;
|
||||
export const langs = _LANGS_;
|
||||
export const recaptchaSitekey = _RECAPTCHA_SITEKEY_;
|
||||
export const swPublickey = _SW_PUBLICKEY_;
|
||||
export const themeColor = _THEME_COLOR_;
|
||||
export const copyright = _COPYRIGHT_;
|
||||
export const version = _VERSION_;
|
||||
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_;
|
||||
|
@ -193,7 +193,7 @@ export default Vue.extend({
|
||||
|
||||
clearNotification() {
|
||||
this.unreadCount = 0;
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
},
|
||||
|
||||
onVisibilitychange() {
|
||||
|
@ -56,8 +56,9 @@
|
||||
<mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
|
||||
<span>%i18n:@show-maps-desc%</span>
|
||||
</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.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 class="web" v-show="page == 'web'">
|
||||
@ -191,12 +192,6 @@
|
||||
<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
|
||||
</details>
|
||||
</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>
|
||||
</template>
|
||||
@ -211,7 +206,7 @@ import XApi from './settings.api.vue';
|
||||
import XApps from './settings.apps.vue';
|
||||
import XSignins from './settings.signins.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 MkTaskManager from './taskmanager.vue';
|
||||
|
||||
@ -230,7 +225,6 @@ export default Vue.extend({
|
||||
return {
|
||||
page: 'profile',
|
||||
meta: null,
|
||||
license,
|
||||
version,
|
||||
langs,
|
||||
latestVersion: undefined,
|
||||
@ -238,10 +232,6 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
licenseUrl(): string {
|
||||
return `${docsUrl}/${lang}/license`;
|
||||
},
|
||||
|
||||
apiViaStream: {
|
||||
get() { return this.$store.state.device.apiViaStream; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); }
|
||||
@ -387,7 +377,13 @@ export default Vue.extend({
|
||||
},
|
||||
onChangeReversiBoardLabels(v) {
|
||||
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
|
||||
});
|
||||
},
|
||||
|
@ -30,10 +30,8 @@
|
||||
<li @click="settings">
|
||||
<p>%fa:cog%<span>%i18n:@settings%</span>%fa:angle-right%</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li @click="signout">
|
||||
<p class="signout">%fa:power-off%<span>%i18n:@signout%</span></p>
|
||||
<li v-if="$store.state.i.isAdmin">
|
||||
<router-link to="/admin">%fa:terminal%<span>%i18n:@admin%</span>%fa:angle-right%</router-link>
|
||||
</li>
|
||||
</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>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li @click="signout">
|
||||
<p class="signout">%fa:power-off%<span>%i18n:@signout%</span></p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
@ -0,0 +1,40 @@
|
||||
import Vue from 'vue';
|
||||
import { Line } from 'vue-chartjs';
|
||||
|
||||
export default Vue.extend({
|
||||
extends: Line,
|
||||
props: {
|
||||
data: {
|
||||
required: true
|
||||
},
|
||||
opts: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data() {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.render();
|
||||
},
|
||||
methods: {
|
||||
render() {
|
||||
this.renderChart(this.data, Object.assign({
|
||||
responsive: false,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
displayFormats: {
|
||||
quarter: 'YYYY/MM/D h:mm'
|
||||
}
|
||||
},
|
||||
distribution: 'series'
|
||||
}]
|
||||
}
|
||||
}, this.opts || {}));
|
||||
}
|
||||
}
|
||||
});
|
195
src/client/app/desktop/views/pages/admin/admin.chart.vue
Normal file
195
src/client/app/desktop/views/pages/admin/admin.chart.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="card gkgckalzgidaygcxnugepioremxvxvpt">
|
||||
<header>
|
||||
<b>%i18n:@title%:</b>
|
||||
<select v-model="chartType">
|
||||
<option value="local-users">%i18n:@local-users%</option>
|
||||
<option value="remote-users">%i18n:@remote-users%</option>
|
||||
<option value="local-users-total">%i18n:@local-users-total%</option>
|
||||
<option value="remote-users-total">%i18n:@remote-users-total%</option>
|
||||
<option value="local-notes">%i18n:@local-notes%</option>
|
||||
<option value="remote-notes">%i18n:@remote-notes%</option>
|
||||
<option value="local-notes-total">%i18n:@local-notes-total%</option>
|
||||
<option value="remote-notes-total">%i18n:@remote-notes-total%</option>
|
||||
<option value="local-drive">%i18n:@local-drive%</option>
|
||||
<option value="remote-drive">%i18n:@remote-drive%</option>
|
||||
<option value="local-drive-total">%i18n:@local-drive-total%</option>
|
||||
<option value="remote-drive-total">%i18n:@remote-drive-total%</option>
|
||||
</select>
|
||||
<div>
|
||||
<a @click="span = 'day'">Per DAY</a> | <a @click="span = 'hour'">Per HOUR</a>
|
||||
</div>
|
||||
</header>
|
||||
<x-chart v-if="chart" :data="data[0]" :opts="data[1]" :width="720" :height="300"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XChart from './admin.chart.chart.ts';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XChart
|
||||
},
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartType: 'local-notes',
|
||||
span: 'hour'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
data(): any {
|
||||
if (this.chart == null) return null;
|
||||
switch (this.chartType) {
|
||||
case 'local-users': return this.usersChart(true, false);
|
||||
case 'remote-users': return this.usersChart(false, false);
|
||||
case 'local-users-total': return this.usersChart(true, true);
|
||||
case 'remote-users-total': return this.usersChart(false, true);
|
||||
case 'local-notes': return this.notesChart(true);
|
||||
case 'remote-notes': return this.notesChart(false);
|
||||
case 'local-notes-total': return this.notesTotalChart(true);
|
||||
case 'remote-notes-total': return this.notesTotalChart(false);
|
||||
case 'local-drive': return this.driveChart(true, false);
|
||||
case 'remote-drive': return this.driveChart(false, false);
|
||||
case 'local-drive-total': return this.driveChart(true, true);
|
||||
case 'remote-drive-total': return this.driveChart(false, true);
|
||||
}
|
||||
},
|
||||
stats(): any[] {
|
||||
return (
|
||||
this.span == 'day' ? this.chart.perDay :
|
||||
this.span == 'hour' ? this.chart.perHour :
|
||||
null
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
notesChart(local: boolean): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
normal: local ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal,
|
||||
reply: local ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply,
|
||||
renote: local ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote,
|
||||
total: local ? x.notes.local.diff : x.notes.remote.diff
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: 'Normal',
|
||||
fill: false,
|
||||
borderColor: '#41ddde',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.normal }))
|
||||
}, {
|
||||
label: 'Replies',
|
||||
fill: false,
|
||||
borderColor: '#f7796c',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.reply }))
|
||||
}, {
|
||||
label: 'Renotes',
|
||||
fill: false,
|
||||
borderColor: '#a1de41',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.renote }))
|
||||
}]
|
||||
}];
|
||||
},
|
||||
notesTotalChart(local: boolean): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
count: local ? x.notes.local.total : x.notes.remote.total,
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: local ? 'Local Notes' : 'Remote Notes',
|
||||
fill: false,
|
||||
borderColor: '#f6584f',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.count }))
|
||||
}]
|
||||
}];
|
||||
},
|
||||
usersChart(local: boolean, total: boolean): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
count: local ?
|
||||
total ? x.users.local.total : x.users.local.diff :
|
||||
total ? x.users.remote.total : x.users.remote.diff
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: local ? 'Local Users' : 'Remote Users',
|
||||
fill: false,
|
||||
borderColor: '#f6584f',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.count }))
|
||||
}]
|
||||
}];
|
||||
},
|
||||
driveChart(local: boolean, total: boolean): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
count: local ?
|
||||
total ? x.drive.local.totalSize : x.drive.local.diffSize :
|
||||
total ? x.drive.remote.totalSize : x.drive.remote.diffSize
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: local ? 'Local Drive Usage' : 'Remote Drive Usage',
|
||||
fill: false,
|
||||
borderColor: '#f6584f',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.count }))
|
||||
}]
|
||||
}, {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
callback: (value) => {
|
||||
return Vue.filter('bytes')(value);
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.gkgckalzgidaygcxnugepioremxvxvpt
|
||||
> header
|
||||
display flex
|
||||
|
||||
> b
|
||||
margin-right 8px
|
||||
|
||||
> *:last-child
|
||||
margin-left auto
|
||||
|
||||
</style>
|
@ -11,6 +11,10 @@
|
||||
<x-cpu-memory :connection="connection"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
|
||||
<span>disableRegistration</span>
|
||||
</label>
|
||||
<button class="ui" @click="invite">%i18n:@invite%</button>
|
||||
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
|
||||
</div>
|
||||
@ -28,6 +32,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
stats: null,
|
||||
disableRegistration: false,
|
||||
inviteCode: null,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
@ -37,6 +42,10 @@ export default Vue.extend({
|
||||
this.connection = (this as any).os.streams.serverStatsStream.getConnection();
|
||||
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.stats = stats;
|
||||
});
|
||||
@ -49,6 +58,11 @@ export default Vue.extend({
|
||||
(this as any).api('admin/invite').then(x => {
|
||||
this.inviteCode = x.code;
|
||||
});
|
||||
},
|
||||
updateMeta() {
|
||||
(this as any).api('admin/update-meta', {
|
||||
disableRegistration: this.disableRegistration
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
||||
<polyline
|
||||
:points="points"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#555"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewBoxX: 365,
|
||||
viewBoxY: 70,
|
||||
points: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.drive.local.totalSize : d.drive.remote.totalSize));
|
||||
|
||||
if (peak != 0) {
|
||||
const data = this.chart.slice().reverse().map(x => ({
|
||||
size: this.type == 'local' ? x.drive.local.totalSize : x.drive.remote.totalSize
|
||||
}));
|
||||
|
||||
this.points = data.map((d, i) => `${i},${(1 - (d.size / peak)) * this.viewBoxY}`).join(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
svg
|
||||
display block
|
||||
padding 10px
|
||||
width 100%
|
||||
|
||||
</style>
|
@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<header>%i18n:@title%</header>
|
||||
<div class="card">
|
||||
<header>%i18n:@local%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="local"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<header>%i18n:@remote%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="remote"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import XChart from "./admin.drive-chart.chart.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XChart
|
||||
},
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
</style>
|
@ -1,76 +0,0 @@
|
||||
<template>
|
||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
||||
<polyline
|
||||
:points="pointsNote"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#41ddde"/>
|
||||
<polyline
|
||||
:points="pointsReply"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#f7796c"/>
|
||||
<polyline
|
||||
:points="pointsRenote"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#a1de41"/>
|
||||
<polyline
|
||||
:points="pointsTotal"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#555"
|
||||
stroke-dasharray="2 2"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewBoxX: 365,
|
||||
viewBoxY: 70,
|
||||
pointsNote: null,
|
||||
pointsReply: null,
|
||||
pointsRenote: null,
|
||||
pointsTotal: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.notes.local.diff : d.notes.remote.diff));
|
||||
|
||||
if (peak != 0) {
|
||||
const data = this.chart.slice().reverse().map(x => ({
|
||||
normal: this.type == 'local' ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal,
|
||||
reply: this.type == 'local' ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply,
|
||||
renote: this.type == 'local' ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote,
|
||||
total: this.type == 'local' ? x.notes.local.diff : x.notes.remote.diff
|
||||
}));
|
||||
|
||||
this.pointsNote = data.map((d, i) => `${i},${(1 - (d.normal / peak)) * this.viewBoxY}`).join(' ');
|
||||
this.pointsReply = data.map((d, i) => `${i},${(1 - (d.reply / peak)) * this.viewBoxY}`).join(' ');
|
||||
this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renote / peak)) * this.viewBoxY}`).join(' ');
|
||||
this.pointsTotal = data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
svg
|
||||
display block
|
||||
padding 10px
|
||||
width 100%
|
||||
|
||||
</style>
|
@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<header>%i18n:@title%</header>
|
||||
<div class="card">
|
||||
<header>%i18n:@local%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="local"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<header>%i18n:@remote%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="remote"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import XChart from "./admin.notes-chart.chart.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XChart
|
||||
},
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
</style>
|
@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
||||
<polyline
|
||||
:points="points"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke="#555"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewBoxX: 365,
|
||||
viewBoxY: 70,
|
||||
points: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.users.local.diff : d.users.remote.diff));
|
||||
|
||||
if (peak != 0) {
|
||||
const data = this.chart.slice().reverse().map(x => ({
|
||||
count: this.type == 'local' ? x.users.local.diff : x.users.remote.diff
|
||||
}));
|
||||
|
||||
this.points = data.map((d, i) => `${i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
svg
|
||||
display block
|
||||
padding 10px
|
||||
width 100%
|
||||
|
||||
</style>
|
@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<header>%i18n:@title%</header>
|
||||
<div class="card">
|
||||
<header>%i18n:@local%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="local"/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<header>%i18n:@remote%</header>
|
||||
<x-chart v-if="chart" :chart="chart" type="remote"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import XChart from "./admin.users-chart.chart.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XChart
|
||||
},
|
||||
props: {
|
||||
chart: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
</style>
|
@ -11,9 +11,7 @@
|
||||
<main>
|
||||
<div v-show="page == 'dashboard'">
|
||||
<x-dashboard/>
|
||||
<x-users-chart :chart="chart"/>
|
||||
<x-notes-chart :chart="chart"/>
|
||||
<x-drive-chart :chart="chart"/>
|
||||
<x-chart :chart="chart"/>
|
||||
</div>
|
||||
<div v-if="page == 'users'">
|
||||
<x-suspend-user/>
|
||||
@ -34,9 +32,7 @@ import XSuspendUser from "./admin.suspend-user.vue";
|
||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
|
||||
import XVerifyUser from "./admin.verify-user.vue";
|
||||
import XUnverifyUser from "./admin.unverify-user.vue";
|
||||
import XUsersChart from "./admin.users-chart.vue";
|
||||
import XNotesChart from "./admin.notes-chart.vue";
|
||||
import XDriveChart from "./admin.drive-chart.vue";
|
||||
import XChart from "./admin.chart.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -45,9 +41,7 @@ export default Vue.extend({
|
||||
XUnsuspendUser,
|
||||
XVerifyUser,
|
||||
XUnverifyUser,
|
||||
XUsersChart,
|
||||
XNotesChart,
|
||||
XDriveChart
|
||||
XChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -4,11 +4,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
mounted() {
|
||||
document.title = `${config.name} - %i18n:@title%`;
|
||||
document.title = `${(this as any).os.instanceName} - %i18n:@title%`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -7,7 +7,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -17,7 +16,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
|
||||
Progress.start();
|
||||
},
|
||||
|
@ -12,12 +12,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
name: config.name,
|
||||
name: (this as any).os.instanceName,
|
||||
posted: false,
|
||||
text: new URLSearchParams(location.search).get('text')
|
||||
};
|
||||
|
@ -5,7 +5,7 @@
|
||||
<template v-if="$store.state.device.darkmode">%fa:moon%</template>
|
||||
<template v-else>%fa:R moon%</template>
|
||||
</button>
|
||||
<div class="body" :style="{ backgroundImage: `url('${ welcomeBgUrl }')` }">
|
||||
<div class="body">
|
||||
<div class="container">
|
||||
<div class="info">
|
||||
<span><b>{{ host }}</b></span>
|
||||
@ -46,22 +46,26 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { host, name, description, copyright, welcomeBgUrl } from '../../../config';
|
||||
import { host, copyright } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
stats: null,
|
||||
copyright,
|
||||
welcomeBgUrl,
|
||||
host,
|
||||
name,
|
||||
description,
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
pointerInterval: null,
|
||||
tags: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
});
|
||||
|
||||
(this as any).api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
|
@ -5,16 +5,6 @@
|
||||
<b-form-group label="アプリケーション名" description="あなたのアプリの名称。">
|
||||
<b-form-input v-model="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required/>
|
||||
</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~z、A~Z、0~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-textarea v-model="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required></b-textarea>
|
||||
</b-form-group>
|
||||
@ -50,47 +40,16 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
nid: '',
|
||||
description: '',
|
||||
cb: '',
|
||||
nidState: null,
|
||||
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: {
|
||||
onSubmit() {
|
||||
(this as any).api('app/create', {
|
||||
name: this.name,
|
||||
nameId: this.nid,
|
||||
description: this.description,
|
||||
callbackUrl: this.cb,
|
||||
permission: this.permission
|
||||
|
@ -70,6 +70,10 @@ export default class MiOS extends EventEmitter {
|
||||
chachedAt: Date;
|
||||
};
|
||||
|
||||
public get instanceName() {
|
||||
return this.meta ? this.meta.data.name : 'Misskey';
|
||||
}
|
||||
|
||||
private isMetaFetching = false;
|
||||
|
||||
public app: Vue;
|
||||
|
@ -99,7 +99,7 @@ export default Vue.extend({
|
||||
cursor pointer
|
||||
padding 0 16px
|
||||
margin 0
|
||||
min-width 150px
|
||||
min-width 100px
|
||||
line-height 36px
|
||||
font-size 14px
|
||||
font-weight bold
|
||||
|
@ -12,6 +12,7 @@ import noteCard from './note-card.vue';
|
||||
import userCard from './user-card.vue';
|
||||
import noteDetail from './note-detail.vue';
|
||||
import followButton from './follow-button.vue';
|
||||
import muteButton from './mute-button.vue';
|
||||
import friendsMaker from './friends-maker.vue';
|
||||
import notification from './notification.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-note-detail', noteDetail);
|
||||
Vue.component('mk-follow-button', followButton);
|
||||
Vue.component('mk-mute-button', muteButton);
|
||||
Vue.component('mk-friends-maker', friendsMaker);
|
||||
Vue.component('mk-notification', notification);
|
||||
Vue.component('mk-notifications', notifications);
|
||||
|
79
src/client/app/mobile/views/components/mute-button.vue
Normal file
79
src/client/app/mobile/views/components/mute-button.vue
Normal 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>
|
@ -38,7 +38,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getNoteSummary from '../../../../../misc/get-note-summary';
|
||||
import * as config from '../../../config';
|
||||
|
||||
const displayLimit = 30;
|
||||
|
||||
@ -190,7 +189,7 @@ export default Vue.extend({
|
||||
|
||||
clearNotification() {
|
||||
this.unreadCount = 0;
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
},
|
||||
|
||||
onVisibilitychange() {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button>
|
||||
<template v-if="hasUnreadNotification || hasUnreadMessagingMessage || hasGameInvitation">%fa:circle%</template>
|
||||
<h1>
|
||||
<slot>config.name</slot>
|
||||
<slot>{{ os.instanceName }}</slot>
|
||||
</h1>
|
||||
<slot name="func"></slot>
|
||||
</div>
|
||||
@ -20,13 +20,11 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as anime from 'animejs';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['func'],
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
hasGameInvitation: false,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
|
@ -30,6 +30,7 @@
|
||||
<ul>
|
||||
<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 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>
|
||||
</ul>
|
||||
</div>
|
||||
@ -41,7 +42,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { docsUrl, lang } from '../../../config';
|
||||
import { lang } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['isOpen'],
|
||||
@ -50,7 +51,7 @@ export default Vue.extend({
|
||||
hasGameInvitation: false,
|
||||
connection: null,
|
||||
connectionId: null,
|
||||
aboutUrl: `${docsUrl}/${lang}/about`
|
||||
aboutUrl: `/docs/${lang}/about`
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -25,7 +25,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -44,7 +43,7 @@ export default Vue.extend({
|
||||
window.addEventListener('popstate', this.onPopState);
|
||||
},
|
||||
mounted() {
|
||||
document.title = `${config.name} Drive`;
|
||||
document.title = `${(this as any).os.instanceName} Drive`;
|
||||
document.documentElement.style.background = '#fff';
|
||||
},
|
||||
beforeDestroy() {
|
||||
@ -64,7 +63,7 @@ export default Vue.extend({
|
||||
(this.$refs as any).browser.openContextMenu();
|
||||
},
|
||||
onMoveRoot(silent) {
|
||||
const title = `${config.name} Drive`;
|
||||
const title = `${(this as any).os.instanceName} Drive`;
|
||||
|
||||
if (!silent) {
|
||||
// Rewrite URL
|
||||
@ -77,7 +76,7 @@ export default Vue.extend({
|
||||
this.folder = null;
|
||||
},
|
||||
onOpenFolder(folder, silent) {
|
||||
const title = `${folder.name} | ${config.name} Drive`;
|
||||
const title = `${folder.name} | ${(this as any).os.instanceName} Drive`;
|
||||
|
||||
if (!silent) {
|
||||
// Rewrite URL
|
||||
@ -90,7 +89,7 @@ export default Vue.extend({
|
||||
this.folder = folder;
|
||||
},
|
||||
onOpenFile(file, silent) {
|
||||
const title = `${file.name} | ${config.name} Drive`;
|
||||
const title = `${file.name} | ${(this as any).os.instanceName} Drive`;
|
||||
|
||||
if (!silent) {
|
||||
// Rewrite URL
|
||||
|
@ -14,7 +14,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -29,7 +28,7 @@ export default Vue.extend({
|
||||
this.fetch();
|
||||
},
|
||||
mounted() {
|
||||
document.title = `${config.name} | %i18n:@notifications%`;
|
||||
document.title = `${(this as any).os.instanceName} | %i18n:@notifications%`;
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
|
@ -21,7 +21,6 @@ import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import getUserName from '../../../../../misc/get-user-name';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -50,7 +49,7 @@ export default Vue.extend({
|
||||
this.user = user;
|
||||
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() {
|
||||
|
@ -20,7 +20,6 @@
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -49,7 +48,7 @@ export default Vue.extend({
|
||||
this.user = user;
|
||||
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() {
|
||||
|
@ -7,11 +7,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as config from '../../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
mounted() {
|
||||
document.title = `${config.name} %i18n:@reversi%`;
|
||||
document.title = `${(this as any).os.instanceName} %i18n:@reversi%`;
|
||||
document.documentElement.style.background = '#fff';
|
||||
},
|
||||
methods: {
|
||||
|
@ -49,7 +49,6 @@
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import XTl from './home.timeline.vue';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -97,7 +96,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
|
||||
Progress.start();
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -48,7 +47,7 @@ export default Vue.extend({
|
||||
this.user = user;
|
||||
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}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,10 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../misc/acct/render';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
mounted() {
|
||||
document.title = `${config.name} %i18n:@messaging%`;
|
||||
document.title = `${(this as any).os.instanceName} %i18n:@messaging%`;
|
||||
},
|
||||
methods: {
|
||||
navigate(user) {
|
||||
|
@ -16,7 +16,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -32,7 +31,7 @@ export default Vue.extend({
|
||||
this.fetch();
|
||||
},
|
||||
mounted() {
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
|
@ -12,7 +12,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import * as config from '../../../config';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
@ -35,7 +34,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.title = `%i18n:@search%: ${this.q} | ${config.name}`;
|
||||
document.title = `%i18n:@search%: ${this.q} | ${(this as any).os.instanceName}`;
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
|
@ -13,8 +13,9 @@
|
||||
<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.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.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>%i18n:@timeline%</div>
|
||||
@ -189,7 +190,14 @@ export default Vue.extend({
|
||||
|
||||
onChangeReversiBoardLabels(v) {
|
||||
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
|
||||
});
|
||||
},
|
||||
|
@ -91,15 +91,15 @@ export default Vue.extend({
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(f => {
|
||||
this.avatarId = f.id;
|
||||
this.avatarUploading = false;
|
||||
})
|
||||
.catch(e => {
|
||||
this.avatarUploading = false;
|
||||
alert('%18n:!@upload-failed%');
|
||||
});
|
||||
.then(response => response.json())
|
||||
.then(f => {
|
||||
this.avatarId = f.id;
|
||||
this.avatarUploading = false;
|
||||
})
|
||||
.catch(e => {
|
||||
this.avatarUploading = false;
|
||||
alert('%18n:@upload-failed%');
|
||||
});
|
||||
},
|
||||
|
||||
onBannerChange([file]) {
|
||||
@ -113,15 +113,15 @@ export default Vue.extend({
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(f => {
|
||||
this.bannerId = f.id;
|
||||
this.bannerUploading = false;
|
||||
})
|
||||
.catch(e => {
|
||||
this.bannerUploading = false;
|
||||
alert('%18n:!@upload-failed%');
|
||||
});
|
||||
.then(response => response.json())
|
||||
.then(f => {
|
||||
this.bannerId = f.id;
|
||||
this.bannerUploading = false;
|
||||
})
|
||||
.catch(e => {
|
||||
this.bannerUploading = false;
|
||||
alert('%18n:@upload-failed%');
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
|
@ -12,12 +12,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
name: config.name,
|
||||
name: (this as any).os.instanceName,
|
||||
posted: false,
|
||||
text: new URLSearchParams(location.search).get('text')
|
||||
};
|
||||
|
@ -11,6 +11,7 @@
|
||||
<a class="avatar">
|
||||
<img :src="user.avatarUrl" alt="avatar"/>
|
||||
</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"/>
|
||||
</div>
|
||||
<div class="title">
|
||||
@ -67,7 +68,6 @@ import * as age from 's-age';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import XHome from './user/home.vue';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -107,7 +107,7 @@ export default Vue.extend({
|
||||
this.fetching = false;
|
||||
|
||||
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-radius 12px
|
||||
|
||||
> .mk-mute-button
|
||||
float right
|
||||
|
||||
> .mk-follow-button
|
||||
float right
|
||||
|
||||
@ -248,7 +251,7 @@ root(isDark)
|
||||
top 47px
|
||||
box-shadow 0 4px 4px isDark ? rgba(#000, 0.3) : rgba(#000, 0.07)
|
||||
background-color $bg
|
||||
z-index 1
|
||||
z-index 2
|
||||
|
||||
> .nav-container
|
||||
display flex
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { apiUrl, copyright, host, name, description } from '../../../config';
|
||||
import { apiUrl, copyright, host } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
@ -39,12 +39,17 @@ export default Vue.extend({
|
||||
copyright,
|
||||
stats: null,
|
||||
host,
|
||||
name,
|
||||
description,
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
tags: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
});
|
||||
|
||||
(this as any).api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
|
@ -53,7 +53,6 @@
|
||||
import Vue from 'vue';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
import * as uuid from 'uuid';
|
||||
import * as config from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -103,7 +102,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.title = config.name;
|
||||
document.title = (this as any).os.instanceName;
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Vuex from 'vuex';
|
||||
import createPersistedState from 'vuex-persistedstate';
|
||||
import * as nestedProperty from 'nested-property';
|
||||
|
||||
import MiOS from './mios';
|
||||
import { hostname } from './config';
|
||||
@ -22,7 +23,12 @@ const defaultSettings = {
|
||||
disableViaMobile: false,
|
||||
memo: null,
|
||||
iLikeSushi: false,
|
||||
reversiBoardLabels: false
|
||||
games: {
|
||||
reversi: {
|
||||
showBoardLabels: false,
|
||||
useContrastStones: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
@ -125,7 +131,7 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
|
||||
mutations: {
|
||||
set(state, x: { key: string; value: any }) {
|
||||
state[x.key] = x.value;
|
||||
nestedProperty.set(state, x.key, x.value);
|
||||
},
|
||||
|
||||
setHome(state, data) {
|
||||
|
@ -82,7 +82,7 @@ props:
|
||||
ja: "フォルダ"
|
||||
en: "The folder of this file"
|
||||
|
||||
sensitive:
|
||||
isSensitive:
|
||||
type: "boolean"
|
||||
optional: true
|
||||
desc:
|
||||
|
10
src/index.ts
10
src/index.ts
@ -14,6 +14,7 @@ import * as portscanner from 'portscanner';
|
||||
import isRoot = require('is-root');
|
||||
import Xev from 'xev';
|
||||
import * as program from 'commander';
|
||||
import mongo from './db/mongodb';
|
||||
|
||||
import Logger from './misc/logger';
|
||||
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 uri = `mongodb://${u && p ? `${u}:****@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
|
||||
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) {
|
||||
|
@ -49,9 +49,6 @@ export default function(html: string): string {
|
||||
text += txt;
|
||||
break;
|
||||
}
|
||||
// メンション以外
|
||||
} else {
|
||||
text += `[${txt}](${node.attrs.find((x: any) => x.name == 'href').value})`;
|
||||
}
|
||||
|
||||
if (node.childNodes) {
|
||||
|
@ -27,10 +27,12 @@ export default class Replacer {
|
||||
let text = texts;
|
||||
|
||||
if (path) {
|
||||
path = path.replace('.ts', '');
|
||||
|
||||
if (text.hasOwnProperty(path)) {
|
||||
text = text[path];
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@ -46,10 +48,10 @@ export default class Replacer {
|
||||
});
|
||||
|
||||
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
|
||||
} 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
|
||||
} else {
|
||||
return text;
|
||||
|
@ -5,8 +5,6 @@ import db from '../db/mongodb';
|
||||
import config from '../config';
|
||||
|
||||
const App = db.get<IApp>('apps');
|
||||
App.createIndex('nameId');
|
||||
App.createIndex('nameIdLower');
|
||||
App.createIndex('secret');
|
||||
export default App;
|
||||
|
||||
@ -16,17 +14,11 @@ export type IApp = {
|
||||
userId: mongo.ObjectID | null;
|
||||
secret: string;
|
||||
name: string;
|
||||
nameId: string;
|
||||
nameIdLower: string;
|
||||
description: string;
|
||||
permission: 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
|
||||
*
|
||||
@ -76,8 +68,6 @@ export const pack = (
|
||||
_app.id = _app._id;
|
||||
delete _app._id;
|
||||
|
||||
delete _app.nameIdLower;
|
||||
|
||||
// Visible by only owner
|
||||
if (!opts.includeSecret) {
|
||||
delete _app.secret;
|
||||
|
@ -10,6 +10,8 @@ export interface IStats {
|
||||
|
||||
date: Date;
|
||||
|
||||
span: 'day' | 'hour';
|
||||
|
||||
/**
|
||||
* ユーザーに関する統計
|
||||
*/
|
||||
|
@ -118,6 +118,7 @@ export interface IRemoteUser extends IUserBase {
|
||||
publicKeyPem: string;
|
||||
};
|
||||
updatedAt: Date;
|
||||
isAdmin: false;
|
||||
}
|
||||
|
||||
export type IUser = ILocalUser | IRemoteUser;
|
||||
|
@ -81,7 +81,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
||||
// 添付メディア
|
||||
// TODO: attachmentは必ずしもImageではない
|
||||
// TODO: attachmentは必ずしも配列ではない
|
||||
// Noteがsensitiveなら添付もsensitiveにする
|
||||
const media = note.attachment
|
||||
.map(attach => attach.sensitive = note.sensitive)
|
||||
? await Promise.all(note.attachment.map(x => resolveImage(actor, x)))
|
||||
: [];
|
||||
|
||||
|
@ -11,6 +11,7 @@ import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type';
|
||||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import Meta from '../../../models/meta';
|
||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||
import { updateUserStats } from '../../../services/update-chart';
|
||||
|
||||
const log = debug('misskey:activitypub');
|
||||
|
||||
@ -130,7 +131,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
||||
endpoints: person.endpoints,
|
||||
uri: person.id,
|
||||
url: person.url,
|
||||
isBot
|
||||
isBot: isBot,
|
||||
isCat: (person as any).isCat === true ? true : false
|
||||
}) as IRemoteUser;
|
||||
} catch (e) {
|
||||
// duplicate key error
|
||||
@ -148,6 +150,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
||||
'stats.usersCount': 1
|
||||
}
|
||||
}, { upsert: true });
|
||||
|
||||
updateUserStats(user, true);
|
||||
//#endregion
|
||||
|
||||
//#region アイコンとヘッダー画像をフェッチ
|
||||
@ -259,7 +263,8 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
|
||||
notesCount,
|
||||
name: person.name,
|
||||
url: person.url,
|
||||
endpoints: person.endpoints
|
||||
endpoints: person.endpoints,
|
||||
isCat: (person as any).isCat === true ? true : false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||
...mentionTags,
|
||||
];
|
||||
|
||||
const files = await promisedFiles;
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note._id}`,
|
||||
type: 'Note',
|
||||
@ -89,7 +91,8 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||
to,
|
||||
cc,
|
||||
inReplyTo,
|
||||
attachment: (await promisedFiles).map(renderDocument),
|
||||
attachment: files.map(renderDocument),
|
||||
sensitive: files.some(file => file.metadata.isSensitive),
|
||||
tag
|
||||
};
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ export default async (user: ILocalUser) => {
|
||||
icon: user.avatarId && renderImage(avatar),
|
||||
image: user.bannerId && renderImage(banner),
|
||||
manuallyApprovesFollowers: user.isLocked,
|
||||
publicKey: renderKey(user)
|
||||
publicKey: renderKey(user),
|
||||
isCat: user.isCat
|
||||
};
|
||||
};
|
||||
|
@ -16,6 +16,7 @@ export interface IObject {
|
||||
image?: any;
|
||||
url?: string;
|
||||
tag?: any[];
|
||||
sensitive?: boolean;
|
||||
}
|
||||
|
||||
export interface IActivity extends IObject {
|
||||
|
@ -15,7 +15,7 @@ export default async (username: string, _host: string, option?: any): Promise<IU
|
||||
const host = toUnicode(hostAscii);
|
||||
|
||||
if (config.host == host) {
|
||||
return await User.findOne({ usernameLower });
|
||||
return await User.findOne({ usernameLower, host: null });
|
||||
}
|
||||
|
||||
let user = await User.findOne({ usernameLower, host }, option);
|
||||
|
@ -41,10 +41,20 @@ function inbox(ctx: Router.IRouterContext) {
|
||||
}
|
||||
|
||||
function isActivityPubReq(ctx: Router.IRouterContext) {
|
||||
ctx.response.vary('Accept');
|
||||
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json');
|
||||
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
|
||||
router.post('/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();
|
||||
|
||||
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) {
|
||||
@ -62,7 +73,8 @@ router.get('/notes/:note', async (ctx, next) => {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = pack(await renderNote(note));
|
||||
ctx.body = pack(await renderNote(note, false));
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
// outbox
|
||||
@ -90,6 +102,7 @@ router.get('/users/:user/publickey', async ctx => {
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
ctx.body = pack(renderKey(user));
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
ctx.status = 400;
|
||||
}
|
||||
@ -103,6 +116,7 @@ async function userInfo(ctx: Router.IRouterContext, user: IUser) {
|
||||
}
|
||||
|
||||
ctx.body = pack(await renderPerson(user as ILocalUser));
|
||||
setResponseType(ctx);
|
||||
}
|
||||
|
||||
router.get('/users/:user', async ctx => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as Koa from 'koa';
|
||||
import * as Router from 'koa-router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
@ -8,8 +8,9 @@ import pack from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
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);
|
||||
|
||||
// Get 'cursor' parameter
|
||||
@ -72,9 +73,11 @@ export default async (ctx: Koa.Context) => {
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as Koa from 'koa';
|
||||
import * as Router from 'koa-router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
@ -8,8 +8,9 @@ import pack from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
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);
|
||||
|
||||
// Get 'cursor' parameter
|
||||
@ -72,9 +73,11 @@ export default async (ctx: Koa.Context) => {
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
}
|
||||
};
|
||||
|
@ -1,16 +1,17 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as Koa from 'koa';
|
||||
import * as Router from 'koa-router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
import { setResponseType } from '../activitypub';
|
||||
|
||||
import Note from '../../models/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);
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
@ -83,7 +84,7 @@ export default async (ctx: Koa.Context) => {
|
||||
|
||||
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(
|
||||
`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
|
||||
user.notesCount, renderedNotes, partOf,
|
||||
@ -92,6 +93,7 @@ export default async (ctx: Koa.Context) => {
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.notesCount,
|
||||
@ -99,5 +101,6 @@ export default async (ctx: Koa.Context) => {
|
||||
`${partOf}?page=true&since_id=000000000000000000000000`
|
||||
);
|
||||
ctx.body = pack(rendered);
|
||||
setResponseType(ctx);
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { performance } from 'perf_hooks';
|
||||
import limitter from './limitter';
|
||||
import { IUser, isLocalUser } from '../../models/user';
|
||||
import { IUser } from '../../models/user';
|
||||
import { IApp } from '../../models/app';
|
||||
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');
|
||||
}
|
||||
|
||||
if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
|
||||
if (ep.meta.requireAdmin && !user.isAdmin) {
|
||||
return rej('YOU_ARE_NOT_ADMIN');
|
||||
}
|
||||
|
||||
|
@ -8,94 +8,129 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default (params: any) => new Promise(async (res, rej) => {
|
||||
const daysRange = 90;
|
||||
const hoursRange = 24;
|
||||
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
const d = now.getDate();
|
||||
const h = now.getHours();
|
||||
|
||||
const stats = await Stats.find({
|
||||
date: {
|
||||
$gt: new Date(y - 1, m, d)
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
date: -1
|
||||
},
|
||||
fields: {
|
||||
_id: 0
|
||||
}
|
||||
});
|
||||
const [statsPerDay, statsPerHour] = await Promise.all([
|
||||
Stats.find({
|
||||
span: 'day',
|
||||
date: {
|
||||
$gt: new Date(y, m, d - daysRange)
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
date: -1
|
||||
},
|
||||
fields: {
|
||||
_id: 0
|
||||
}
|
||||
}),
|
||||
Stats.find({
|
||||
span: 'hour',
|
||||
date: {
|
||||
$gt: new Date(y, m, d, h - hoursRange)
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
date: -1
|
||||
},
|
||||
fields: {
|
||||
_id: 0
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
const chart: Array<Omit<IStats, '_id'>> = [];
|
||||
const format = (src: IStats[], span: 'day' | 'hour') => {
|
||||
const chart: Array<Omit<Omit<IStats, '_id'>, 'span'>> = [];
|
||||
|
||||
for (let i = 364; i >= 0; i--) {
|
||||
const day = new Date(y, m, d - i);
|
||||
const range =
|
||||
span == 'day' ? daysRange :
|
||||
span == 'hour' ? hoursRange :
|
||||
null;
|
||||
|
||||
const stat = stats.find(s => s.date.getTime() == day.getTime());
|
||||
for (let i = (range - 1); i >= 0; i--) {
|
||||
const current =
|
||||
span == 'day' ? new Date(y, m, d - i) :
|
||||
span == 'hour' ? new Date(y, m, d, h - i) :
|
||||
null;
|
||||
|
||||
if (stat) {
|
||||
chart.unshift(stat);
|
||||
} else { // 隙間埋め
|
||||
const mostRecent = stats.find(s => s.date.getTime() < day.getTime());
|
||||
if (mostRecent) {
|
||||
chart.unshift(Object.assign({}, mostRecent, {
|
||||
date: day
|
||||
}));
|
||||
} else {
|
||||
chart.unshift({
|
||||
date: day,
|
||||
users: {
|
||||
local: {
|
||||
total: 0,
|
||||
diff: 0
|
||||
},
|
||||
remote: {
|
||||
total: 0,
|
||||
diff: 0
|
||||
}
|
||||
},
|
||||
notes: {
|
||||
local: {
|
||||
total: 0,
|
||||
diff: 0,
|
||||
diffs: {
|
||||
normal: 0,
|
||||
reply: 0,
|
||||
renote: 0
|
||||
const stat = src.find(s => s.date.getTime() == current.getTime());
|
||||
|
||||
if (stat) {
|
||||
chart.unshift(stat);
|
||||
} else { // 隙間埋め
|
||||
const mostRecent = src.find(s => s.date.getTime() < current.getTime());
|
||||
if (mostRecent) {
|
||||
chart.unshift(Object.assign({}, mostRecent, {
|
||||
date: current
|
||||
}));
|
||||
} else {
|
||||
chart.unshift({
|
||||
date: current,
|
||||
users: {
|
||||
local: {
|
||||
total: 0,
|
||||
diff: 0
|
||||
},
|
||||
remote: {
|
||||
total: 0,
|
||||
diff: 0
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
total: 0,
|
||||
diff: 0,
|
||||
diffs: {
|
||||
normal: 0,
|
||||
reply: 0,
|
||||
renote: 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
|
||||
}
|
||||
}
|
||||
},
|
||||
drive: {
|
||||
local: {
|
||||
totalCount: 0,
|
||||
totalSize: 0,
|
||||
diffCount: 0,
|
||||
diffSize: 0
|
||||
},
|
||||
remote: {
|
||||
totalCount: 0,
|
||||
totalSize: 0,
|
||||
diffCount: 0,
|
||||
diffSize: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chart.forEach(x => {
|
||||
delete x.date;
|
||||
chart.forEach(x => {
|
||||
delete (x as any).span;
|
||||
});
|
||||
|
||||
return chart;
|
||||
};
|
||||
|
||||
res({
|
||||
perDay: format(statsPerDay, 'day'),
|
||||
perHour: format(statsPerHour, 'hour')
|
||||
});
|
||||
|
||||
res(chart);
|
||||
});
|
||||
|
@ -34,6 +34,10 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||
return rej('user not found');
|
||||
}
|
||||
|
||||
if (user.isAdmin) {
|
||||
return rej('cannot suspend admin');
|
||||
}
|
||||
|
||||
await User.findOneAndUpdate({
|
||||
_id: user._id
|
||||
}, {
|
||||
|
37
src/server/api/endpoints/admin/update-meta.ts
Normal file
37
src/server/api/endpoints/admin/update-meta.ts
Normal 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();
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import rndstr from 'rndstr';
|
||||
import $ from 'cafy';
|
||||
import App, { isValidNameId, pack } from '../../../../models/app';
|
||||
import App, { pack } from '../../../../models/app';
|
||||
import { ILocalUser } from '../../../../models/user';
|
||||
|
||||
export const meta = {
|
||||
@ -11,10 +11,6 @@ export const meta = {
|
||||
* Create an app
|
||||
*/
|
||||
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
|
||||
const [name, nameErr] = $.str.get(params.name);
|
||||
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(),
|
||||
userId: user && user._id,
|
||||
name: name,
|
||||
nameId: nameId,
|
||||
nameIdLower: nameId.toLowerCase(),
|
||||
description: description,
|
||||
permission: permission,
|
||||
callbackUrl: callbackUrl,
|
||||
@ -49,5 +43,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
|
||||
});
|
||||
|
||||
// Response
|
||||
res(await pack(app));
|
||||
res(await pack(app, null, {
|
||||
includeSecret: true
|
||||
}));
|
||||
});
|
||||
|
@ -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
|
||||
});
|
||||
});
|
@ -9,21 +9,11 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
|
||||
const isSecure = user != null && app == null;
|
||||
|
||||
// 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');
|
||||
|
||||
// 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
|
||||
const ap = appId !== undefined
|
||||
? await App.findOne({ _id: appId })
|
||||
: await App.findOne({ nameIdLower: nameId.toLowerCase() });
|
||||
const ap = await App.findOne({ _id: appId });
|
||||
|
||||
if (ap === null) {
|
||||
return rej('app not found');
|
||||
|
@ -34,9 +34,12 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
|
||||
const query = {
|
||||
'metadata.userId': user._id
|
||||
'metadata.userId': user._id,
|
||||
'metadata.deletedAt': { $exists: false }
|
||||
} as any;
|
||||
|
||||
if (sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
@ -47,6 +50,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
||||
$lt: untilId
|
||||
};
|
||||
}
|
||||
|
||||
if (type) {
|
||||
query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`);
|
||||
}
|
||||
@ -59,6 +63,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
||||
});
|
||||
|
||||
// Serialize
|
||||
res(await Promise.all(files.map(async file =>
|
||||
await pack(file))));
|
||||
res(await Promise.all(files.map(file => pack(file))));
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user