Compare commits
250 Commits
Author | SHA1 | Date | |
---|---|---|---|
1fc9206c6d | |||
a8fb0d477f | |||
0c372b68d4 | |||
e7d9018944 | |||
ce16884587 | |||
3345733d00 | |||
f91cccb6b1 | |||
6183262037 | |||
1c7a194950 | |||
286e15b967 | |||
744d366874 | |||
afa62d3d44 | |||
270c7997c6 | |||
fa95641f88 | |||
aa9fe38c25 | |||
0d33cbbbbb | |||
bf077da72f | |||
d8f8e19d06 | |||
3c3d3e4c0c | |||
0efbeb36df | |||
73c396cb39 | |||
8e75f8a125 | |||
e8a7e95c65 | |||
70a6889fe5 | |||
6aff7375f6 | |||
d2ef95a8c3 | |||
1ae51df74a | |||
3098c6a915 | |||
ab3f8fd10c | |||
4bdef3720c | |||
c02cecc9e5 | |||
08b431723a | |||
83dcfec053 | |||
69ac7b739f | |||
9ccf9a2496 | |||
78563ef9a0 | |||
019d157b92 | |||
14de35e3f5 | |||
a860479e88 | |||
b351b3fae5 | |||
89918a9f79 | |||
cadd020915 | |||
e80933b8ae | |||
4c0832884f | |||
e4ca940979 | |||
3fd9be3967 | |||
79ec7aba1d | |||
42b1f7eddd | |||
889a73caa4 | |||
c34a89e962 | |||
2b4bf681e5 | |||
d1d59e3557 | |||
668c21830c | |||
7c37ed07f8 | |||
a135d8fd59 | |||
0db9aae162 | |||
1875c362af | |||
9b36dd9565 | |||
69452a27de | |||
6aa5c5895f | |||
12f20c67b1 | |||
66b8e5647d | |||
188a23fec7 | |||
b408b45000 | |||
c53cb94250 | |||
ba2eeabe38 | |||
259fac224e | |||
aad5440c9e | |||
5c40f0010f | |||
fb1d181424 | |||
12cbd8ef5b | |||
cfc4385d1f | |||
880ef024d0 | |||
c6c6edbc0a | |||
83ba951bf9 | |||
9c22b1a68a | |||
cd6a1d3446 | |||
4a9fc0c8ed | |||
fada899b30 | |||
22b099fa8a | |||
e0bc0d2830 | |||
90768d30aa | |||
177c549493 | |||
5e1ee68189 | |||
175f6303bc | |||
cf9f2a5562 | |||
f04526baca | |||
f085ecedb3 | |||
0986301788 | |||
fe418d8d9a | |||
6009be34dc | |||
01a0a54a2c | |||
cfcaf77e21 | |||
3b37bdc0b9 | |||
ec07112f94 | |||
464faf2673 | |||
bde20a1a65 | |||
86c7276da9 | |||
fec988bb79 | |||
0702d0974b | |||
f443d36dbb | |||
5477f0a865 | |||
dc02168f33 | |||
cc5c32b4d2 | |||
a35680a838 | |||
770cba73a6 | |||
401fc758fd | |||
e8503e6351 | |||
de23753409 | |||
4857d86cdd | |||
dc4a072678 | |||
c6ee5ccd88 | |||
68b630cb37 | |||
94c0238d3a | |||
dbea387433 | |||
d35f62d0e4 | |||
09b8e81a77 | |||
3b38979a34 | |||
0fd8c86c24 | |||
58d0ed1a2e | |||
8939452036 | |||
a2931d6f7e | |||
38b75ad977 | |||
dc4f585954 | |||
1fbe5365f7 | |||
04257db938 | |||
ba08d1aa53 | |||
c29cb5bfb9 | |||
fb1e2efbdd | |||
131a454e7c | |||
92e5cff285 | |||
b24e32e14e | |||
943805bdcd | |||
ba9340a26b | |||
00119328f2 | |||
a73c65da07 | |||
9021bb5694 | |||
a3cf63823f | |||
f15878cc6f | |||
33469ff87a | |||
23b0723168 | |||
fda1ab3e05 | |||
490c05a869 | |||
f0137daebe | |||
b9fc0e6d71 | |||
979efee412 | |||
f079041827 | |||
4edd9efc0b | |||
2913c7ccfb | |||
e1f460f90f | |||
70f927ea43 | |||
80d343bb0b | |||
9e41fddea3 | |||
8384efc8c7 | |||
7797c86581 | |||
4da8cc478f | |||
285deeec52 | |||
2916e49422 | |||
41e5b9134b | |||
d0d853dcb2 | |||
be46c7e4c5 | |||
8e0f41d608 | |||
a7b438072c | |||
99958e2fce | |||
b82843d359 | |||
4dfc2dfa89 | |||
50c945607f | |||
01f28b21dd | |||
2cb39a8882 | |||
6ddb6bc160 | |||
92befbb4cc | |||
ab701bb93e | |||
7f9a88fd1c | |||
c5073b33ef | |||
765b922a8b | |||
9b7d6274fa | |||
26b384aef1 | |||
0b1e5e3e08 | |||
5b7506756e | |||
1f28a0dfeb | |||
f56ec82f6b | |||
8ccbabf5ca | |||
7c763600b7 | |||
499491003b | |||
3a77d871d5 | |||
4ee6d0b549 | |||
64aa733b16 | |||
c8c4ec6ad4 | |||
c9ee737078 | |||
ebc2cca0b4 | |||
fc94df06eb | |||
190a03103e | |||
0d75ae9d9a | |||
3129f8f073 | |||
7f751d3f20 | |||
0b5b834f8b | |||
0f649f7d37 | |||
a1b100d412 | |||
dd4ee1627e | |||
1da0fdcf78 | |||
800eec73b8 | |||
166cb5e179 | |||
4a5e145048 | |||
be68f42220 | |||
7942aa677f | |||
2fcf9288a5 | |||
9bc17974f2 | |||
19c872a1f3 | |||
9252c59d90 | |||
a8f142096c | |||
0b154ac7ba | |||
d4eb0c8df9 | |||
5ba6f20701 | |||
695a082582 | |||
1b9f293959 | |||
7289d5b401 | |||
5bdbf98f8c | |||
4e915e96a5 | |||
e148f6ce5e | |||
652d7d2c05 | |||
0b0111fe23 | |||
5d097fb29d | |||
f29fe986af | |||
0c774979c0 | |||
f0bc2ed1d7 | |||
b174e5e57a | |||
ae50b71c07 | |||
b2eb50f260 | |||
790c7f2249 | |||
6a0f34c283 | |||
d151445db7 | |||
18a3007273 | |||
bea8c3c65c | |||
391ee01fe3 | |||
be7cf9f731 | |||
b92c2aa40e | |||
45ebcbf785 | |||
5f32484be0 | |||
e245122f12 | |||
21570e2111 | |||
63e1165a01 | |||
89b5a69127 | |||
1152a9d03a | |||
10092d4570 | |||
429b4bec64 | |||
99f96583b6 | |||
5adb765f85 | |||
0eb787e0d0 | |||
4493c856a9 | |||
59d3d4a749 |
@ -50,8 +50,11 @@ remoteDriveCapacityMb: 8
|
|||||||
# If enabled:
|
# If enabled:
|
||||||
# Server will not cache remote files (Using direct link instead).
|
# Server will not cache remote files (Using direct link instead).
|
||||||
# You can save your storage.
|
# You can save your storage.
|
||||||
# Users cannot see remote images when they turn off "Show media from a remote server" setting.
|
#
|
||||||
preventCache: false
|
# NOTE:
|
||||||
|
# * Users cannot see remote images when they turn off "Show media from a remote server" setting.
|
||||||
|
# * Since thumbnails are not provided, traffic increases.
|
||||||
|
preventCacheRemoteFiles: false
|
||||||
|
|
||||||
drive:
|
drive:
|
||||||
storage: 'db'
|
storage: 'db'
|
||||||
@ -64,7 +67,7 @@ drive:
|
|||||||
# config:
|
# config:
|
||||||
# endPoint:
|
# endPoint:
|
||||||
# port:
|
# port:
|
||||||
# secure:
|
# useSSL:
|
||||||
# accessKey:
|
# accessKey:
|
||||||
# secretKey:
|
# secretKey:
|
||||||
|
|
||||||
@ -75,7 +78,7 @@ drive:
|
|||||||
# config:
|
# config:
|
||||||
# endPoint: s3-us-west-2.amazonaws.com
|
# endPoint: s3-us-west-2.amazonaws.com
|
||||||
# region: us-west-2
|
# region: us-west-2
|
||||||
# secure: true
|
# useSSL: true
|
||||||
# accessKey: XXX
|
# accessKey: XXX
|
||||||
# secretKey: YYY
|
# secretKey: YYY
|
||||||
|
|
||||||
@ -87,7 +90,7 @@ drive:
|
|||||||
# config:
|
# config:
|
||||||
# endPoint: s3-us-west-2.amazonaws.com
|
# endPoint: s3-us-west-2.amazonaws.com
|
||||||
# region: us-west-2
|
# region: us-west-2
|
||||||
# secure: true
|
# useSSL: true
|
||||||
# accessKey: XXX
|
# accessKey: XXX
|
||||||
# secretKey: YYY
|
# secretKey: YYY
|
||||||
|
|
||||||
@ -123,6 +126,7 @@ drive:
|
|||||||
# google_maps_api_key: example-google-maps-api-key
|
# google_maps_api_key: example-google-maps-api-key
|
||||||
|
|
||||||
# Twitter integration
|
# Twitter integration
|
||||||
|
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/tw/cb
|
||||||
# twitter:
|
# twitter:
|
||||||
# consumer_key: example-twitter-consumer-key
|
# consumer_key: example-twitter-consumer-key
|
||||||
# consumer_secret: example-twitter-consumer-secret-key
|
# consumer_secret: example-twitter-consumer-secret-key
|
||||||
|
@ -22,7 +22,6 @@ addons:
|
|||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
packages:
|
packages:
|
||||||
- g++-4.8
|
- g++-4.8
|
||||||
- graphicsmagick
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
4
.vsls.json
Normal file
4
.vsls.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/vsls",
|
||||||
|
"gitignore": "exclude"
|
||||||
|
}
|
@ -5,6 +5,15 @@ ChangeLog
|
|||||||
|
|
||||||
This document describes breaking changes only.
|
This document describes breaking changes only.
|
||||||
|
|
||||||
|
6.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
|
||||||
|
オブジェクトストレージを使用している場合、設定ファイルの`drive.config.secure`を`drive.config.useSSL`にリネームしてください。
|
||||||
|
|
||||||
|
If you use object storage, please rename `drive.config.secure` to `drive.config.useSSL` in config.
|
||||||
|
|
||||||
5.0.0
|
5.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
31
README.md
31
README.md
@ -43,9 +43,34 @@ If you want to...
|
|||||||
|
|
||||||
:heart: Backers & Sponsors
|
:heart: Backers & Sponsors
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
| <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"> | <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"> |
|
<table>
|
||||||
|:-:|:-:|:-:|:-:|
|
<tr>
|
||||||
| [Gargron](https://www.patreon.com/mastodon) | [39ff](https://www.patreon.com/user/creators?u=12378075) | [dansup](https://www.patreon.com/dansup) | [Takashi Shibuya](https://www.patreon.com/user/creators?u=12531784) |
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"></td>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"></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"></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"></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"></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"></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"></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"></td>
|
||||||
|
<td><img src="https://c8.patreon.com/2/100/12718187"></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"></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"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/user/creators?u=12378075">39ff</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/user/creators?u=12531784">Takashi Shibuya</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</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=12731202">negao</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=12931605">Reiju</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
:four_leaf_clover: Copyright
|
:four_leaf_clover: Copyright
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
FROM base/archlinux
|
|
||||||
|
|
||||||
MAINTAINER Aya Morisawa
|
|
||||||
|
|
||||||
RUN rm /etc/pacman.d/mirrorlist
|
|
||||||
RUN echo 'Server = http://ftp.jaist.ac.jp/pub/Linux/ArchLinux/$repo/os/$arch' >> /etc/pacman.d/mirrorlist
|
|
||||||
RUN echo 'Server = http://ftp.tsukuba.wide.ad.jp/Linux/archlinux/$repo/os/$arch' >> /etc/pacman.d/mirrorlist
|
|
||||||
|
|
||||||
RUN rm /etc/localtime
|
|
||||||
RUN ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
|
|
||||||
|
|
||||||
RUN pacman -Sy --noconfirm
|
|
||||||
RUN pacman -S --noconfirm pacman
|
|
||||||
RUN pacman-db-upgrade
|
|
||||||
RUN pacman -S --noconfirm archlinux-keyring
|
|
||||||
RUN pacman -Syyu --noconfirm
|
|
||||||
RUN pacman -S --noconfirm git nodejs npm mongodb redis
|
|
||||||
|
|
||||||
COPY misskey.sh /root/misskey.sh
|
|
||||||
RUN chmod u+x /root/misskey.sh
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 443
|
|
||||||
EXPOSE 27017
|
|
||||||
|
|
||||||
CMD ["/root/misskey.sh"]
|
|
@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
redis-server --daemonize yes
|
|
||||||
mongod > /dev/null &
|
|
||||||
cd /root/misskey
|
|
||||||
npm start
|
|
||||||
tail -f /dev/null
|
|
@ -1,29 +0,0 @@
|
|||||||
Setup with Docker :whale:
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
Ensure that the working directory is the repository root directory.
|
|
||||||
|
|
||||||
To create misskey image:
|
|
||||||
``` console
|
|
||||||
$ sudo docker build -t misskey ./docker
|
|
||||||
```
|
|
||||||
|
|
||||||
To run misskey:
|
|
||||||
``` console
|
|
||||||
$ sudo docker run --rm -i -t -p $PORT:80 -v $(pwd):/root/misskey -v $DBPATH:/data/db misskey
|
|
||||||
```
|
|
||||||
|
|
||||||
where `$PORT` is the port used to access Misskey Web from host browser
|
|
||||||
and `$DBPATH` is the path of MongoDB database on the host for data persistence.
|
|
||||||
|
|
||||||
ex:
|
|
||||||
``` console
|
|
||||||
$ sudo docker run --rm -i -t -p 80:80 -v $(pwd):/root/misskey -v /data/db:/data/db misskey
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to run misskey in production mode, add `--env NODE_ENV=production` like this:
|
|
||||||
``` console
|
|
||||||
$ sudo docker run --rm -i -t -p 80:80 -v $(pwd):/root/misskey -v /data/db:/data/db --env NODE_ENV=production misskey
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `$(pwd)` is the working directory.
|
|
@ -62,6 +62,13 @@ npm install web-push -g
|
|||||||
web-push generate-vapid-keys
|
web-push generate-vapid-keys
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*(optional)* Create a twitter application
|
||||||
|
----------------------------------------------------------------
|
||||||
|
If you want to enable the twitter integration, you need to create a twitter app at [https://developer.twitter.com/en/apply/user](https://developer.twitter.com/en/apply/user).
|
||||||
|
|
||||||
|
In the app you need to set the oauth callback url as : https://misskey-instance/api/tw/cb
|
||||||
|
|
||||||
|
|
||||||
*5.* Make configuration file
|
*5.* Make configuration file
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "Glückwunsch!"
|
congrats: "Glückwunsch!"
|
||||||
angry: "Wütend"
|
angry: "Wütend"
|
||||||
confused: "Verwirrt"
|
confused: "Verwirrt"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "Was machst du gerade?"
|
a: "Was machst du gerade?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
183
locales/en.yml
183
locales/en.yml
@ -8,17 +8,17 @@ common:
|
|||||||
about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within the Fediverse (a universe where various social media platforms are organized), it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
|
about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within the Fediverse (a universe where various social media platforms are organized), it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
|
||||||
adblock:
|
adblock:
|
||||||
detected: "Please disable ad blocker."
|
detected: "Please disable ad blocker."
|
||||||
warning: "<strong>Misskey is not running ads</strong>, but some features may be unavailable or malfunctioning if ad blocking features are enabled."
|
warning: "Some features may be unavailable or cause malfunctions if ad blocking features are enabled. <strong>Misskey is not running ads</strong>."
|
||||||
application-authorization: "Application authorizations."
|
application-authorization: "Application authorizations"
|
||||||
close: "Close"
|
close: "Close"
|
||||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
|
||||||
got-it: "Got it!"
|
got-it: "Got it!"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
title: "Customization tips"
|
title: "Customization tips"
|
||||||
paragraph1: "Home customization allows you to add/delete, drag and drop and rearrange widgets."
|
paragraph1: "Home customization allows you to add/delete, drag and drop and rearrange widgets."
|
||||||
paragraph2: "You can change the display by <strong>right clicking</strong> on some widgets."
|
paragraph2: "You can change the display by <strong>right clicking</strong> on some widgets."
|
||||||
paragraph3: "To delete a widget, <strong>drag and drop the widget onto the area labeled \"Trash\"</strong> in the header."
|
paragraph3: "To delete a widget, drag and drop the widget onto <strong>the area labeled \"Trash\"</strong> in the header."
|
||||||
paragraph4: "To finish the customization, click \"Finish\" in the upper right."
|
paragraph4: "To finish the customization, click \"Finish\" on the upper right."
|
||||||
gotit: "Got it!"
|
gotit: "Got it!"
|
||||||
notification:
|
notification:
|
||||||
file-uploaded: "File uploaded!"
|
file-uploaded: "File uploaded!"
|
||||||
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "Congrats!"
|
congrats: "Congrats!"
|
||||||
angry: "Angry"
|
angry: "Angry"
|
||||||
confused: "Confused"
|
confused: "Confused"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "What are you doing?"
|
a: "What are you doing?"
|
||||||
@ -83,7 +84,7 @@ common:
|
|||||||
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
||||||
i-like-sushi: "I prefer sushi rather than pudding"
|
i-like-sushi: "I prefer sushi rather than pudding"
|
||||||
show-reversi-board-labels: "Show row and column labels in Reversi"
|
show-reversi-board-labels: "Show row and column labels in Reversi"
|
||||||
verified-user: "Verified user"
|
verified-user: "Authorized User"
|
||||||
disable-animated-mfm: "Disable animated texts in a post"
|
disable-animated-mfm: "Disable animated texts in a post"
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Draw"
|
drawn: "Draw"
|
||||||
@ -150,7 +151,7 @@ auth/views/form.vue:
|
|||||||
notification-read: "Read your notifications."
|
notification-read: "Read your notifications."
|
||||||
notification-write: "Manage your notifications."
|
notification-write: "Manage your notifications."
|
||||||
cancel: "Cancel"
|
cancel: "Cancel"
|
||||||
accept: "Grant access."
|
accept: "Allow access."
|
||||||
auth/views/index.vue:
|
auth/views/index.vue:
|
||||||
loading: "Loading"
|
loading: "Loading"
|
||||||
denied: "Application authorization denied."
|
denied: "Application authorization denied."
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "Surrender"
|
surrender: "Surrender"
|
||||||
surrendered: "By surrender"
|
surrendered: "By surrender"
|
||||||
|
is-llotheo: "The lesser one wins"
|
||||||
|
looped-map: "Looped map"
|
||||||
|
can-put-everywhere: "Can put everywhere"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "Play reversi with your friends!"
|
sub-title: "Play reversi with your friends!"
|
||||||
@ -175,13 +179,13 @@ common/views/components/games/reversi/reversi.index.vue:
|
|||||||
rule: "How to play"
|
rule: "How to play"
|
||||||
rule-desc: "Reversi is a strategy board game for two players, played on an 8×8 uncheckered board. There are sixty-four identical game pieces called disks (often spelled \"discs\"), which are light on one side and dark on the other. Players take turns placing disks on the board with their assigned color facing up. During a play, any disks of the opponent's color that are in a straight line and bounded by the disk just placed and another disk of the current player's color are turned over to the current player's color. The object of the game is to have the majority of disks turned to display your color when the last playable empty square is filled."
|
rule-desc: "Reversi is a strategy board game for two players, played on an 8×8 uncheckered board. There are sixty-four identical game pieces called disks (often spelled \"discs\"), which are light on one side and dark on the other. Players take turns placing disks on the board with their assigned color facing up. During a play, any disks of the opponent's color that are in a straight line and bounded by the disk just placed and another disk of the current player's color are turned over to the current player's color. The object of the game is to have the majority of disks turned to display your color when the last playable empty square is filled."
|
||||||
mode-invite: "Invite"
|
mode-invite: "Invite"
|
||||||
mode-invite-desc: "Invite to the game a user."
|
mode-invite-desc: "Game with a specified user."
|
||||||
invitations: "You received invitation!"
|
invitations: "You’ve got an invitation!"
|
||||||
my-games: "My games"
|
my-games: "My game"
|
||||||
all-games: "All games"
|
all-games: "All games"
|
||||||
enter-username: "Enter username"
|
enter-username: "Enter a username"
|
||||||
game-state:
|
game-state:
|
||||||
ended: "Ended"
|
ended: "Finished"
|
||||||
playing: "In Progress"
|
playing: "In Progress"
|
||||||
common/views/components/games/reversi/reversi.room.vue:
|
common/views/components/games/reversi/reversi.room.vue:
|
||||||
settings-of-the-game: "Game settings"
|
settings-of-the-game: "Game settings"
|
||||||
@ -190,14 +194,14 @@ common/views/components/games/reversi/reversi.room.vue:
|
|||||||
black-or-white: "Black/White"
|
black-or-white: "Black/White"
|
||||||
black-is: "Black is {}"
|
black-is: "Black is {}"
|
||||||
rules: "Rules"
|
rules: "Rules"
|
||||||
is-llotheo: "The lesser one wins"
|
is-llotheo: "The lesser side wins"
|
||||||
looped-map: "Looped map"
|
looped-map: "Looped map"
|
||||||
can-put-everywhere: "Can put everywhere"
|
can-put-everywhere: "Can put everywhere"
|
||||||
settings-of-the-bot: "Bot settings"
|
settings-of-the-bot: "Bot settings"
|
||||||
this-game-is-started-soon: "The game will begin soon"
|
this-game-is-started-soon: "The game will begin in seconds"
|
||||||
waiting-for-other: "Waiting for the other party's preparation"
|
waiting-for-other: "Waiting for the opponent"
|
||||||
waiting-for-me: "Waiting for the your preparation"
|
waiting-for-me: "Waiting for you"
|
||||||
waiting-for-both: "Waiting for yours"
|
waiting-for-both: "Prepareing"
|
||||||
cancel: "Cancel"
|
cancel: "Cancel"
|
||||||
ready: "Ready"
|
ready: "Ready"
|
||||||
cancel-ready: "Cancel \"Ready\""
|
cancel-ready: "Cancel \"Ready\""
|
||||||
@ -235,13 +239,13 @@ common/views/components/messaging-room.vue:
|
|||||||
no-history: "There is no further history"
|
no-history: "There is no further history"
|
||||||
resize-form: "Drag to resize"
|
resize-form: "Drag to resize"
|
||||||
new-message: "New message"
|
new-message: "New message"
|
||||||
only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
|
only-one-file-attached: "Only ONE file can be attached to a message."
|
||||||
common/views/components/messaging-room.form.vue:
|
common/views/components/messaging-room.form.vue:
|
||||||
input-message-here: "Enter message here"
|
input-message-here: "Enter message here"
|
||||||
send: "Send"
|
send: "Send"
|
||||||
attach-from-local: "Attach files from your device"
|
attach-from-local: "Attach files from your device"
|
||||||
attach-from-drive: "Attach files from your Drive"
|
attach-from-drive: "Attach files from your Drive"
|
||||||
only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
|
only-one-file-attached: "Only one file can be attached to the message."
|
||||||
common/views/components/messaging-room.message.vue:
|
common/views/components/messaging-room.message.vue:
|
||||||
is-read: "Read"
|
is-read: "Read"
|
||||||
deleted: "This message has been deleted"
|
deleted: "This message has been deleted"
|
||||||
@ -256,9 +260,9 @@ common/views/components/nav.vue:
|
|||||||
feedback: "Feedback"
|
feedback: "Feedback"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
favorite: "Favorite this note"
|
favorite: "Favorite this note"
|
||||||
pin: "Pin to your profile page"
|
pin: "Pin to your profile"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete-confirm: "Are you sure you want to delete this post?"
|
delete-confirm: "Delete this post?"
|
||||||
remote: "Show original note"
|
remote: "Show original note"
|
||||||
common/views/components/poll.vue:
|
common/views/components/poll.vue:
|
||||||
vote-to: "Vote for '{}'"
|
vote-to: "Vote for '{}'"
|
||||||
@ -268,9 +272,9 @@ common/views/components/poll.vue:
|
|||||||
show-result: "Show results"
|
show-result: "Show results"
|
||||||
voted: "Voted"
|
voted: "Voted"
|
||||||
common/views/components/poll-editor.vue:
|
common/views/components/poll-editor.vue:
|
||||||
no-only-one-choice: "At least two choices are required for this survey."
|
no-only-one-choice: "At least two choices are required"
|
||||||
choice-n: "Choice {}"
|
choice-n: "Choice {}"
|
||||||
remove: "Delete this choice"
|
remove: "Delete the choice"
|
||||||
add: "+ Add a choice"
|
add: "+ Add a choice"
|
||||||
destroy: "Discard the poll"
|
destroy: "Discard the poll"
|
||||||
common/views/components/reaction-picker.vue:
|
common/views/components/reaction-picker.vue:
|
||||||
@ -285,15 +289,15 @@ common/views/components/signin.vue:
|
|||||||
signin-with-twitter: "Log in with Twitter"
|
signin-with-twitter: "Log in with Twitter"
|
||||||
common/views/components/signup.vue:
|
common/views/components/signup.vue:
|
||||||
username: "Username"
|
username: "Username"
|
||||||
checking: "Checking..."
|
checking: "Confirming..."
|
||||||
available: "Available"
|
available: "Available"
|
||||||
unavailable: "Unavailable"
|
unavailable: "Unavailable"
|
||||||
error: "Network error"
|
error: "Network error"
|
||||||
invalid-format: "Only use letters, numbers and -."
|
invalid-format: "letters, numbers and _ are acceptable."
|
||||||
too-short: "Please enter at least 1 character!"
|
too-short: "Should not be blank!"
|
||||||
too-long: "Please enter up to 20 characters."
|
too-long: "Enter within 20 characters."
|
||||||
password: "Password"
|
password: "Password"
|
||||||
password-placeholder: "We recommend more than 8 characters."
|
password-placeholder: "More than 8 characters are recommended."
|
||||||
weak-password: "Weak password"
|
weak-password: "Weak password"
|
||||||
normal-password: "Fair password"
|
normal-password: "Fair password"
|
||||||
strong-password: "Strong password"
|
strong-password: "Strong password"
|
||||||
@ -323,9 +327,9 @@ common/views/components/uploader.vue:
|
|||||||
common/views/components/visibility-chooser.vue:
|
common/views/components/visibility-chooser.vue:
|
||||||
public: "Public"
|
public: "Public"
|
||||||
home: "Home"
|
home: "Home"
|
||||||
home-desc: "Post to the home timeline only"
|
home-desc: "Post to Home only"
|
||||||
followers: "Followers"
|
followers: "Followers"
|
||||||
followers-desc: "Post to followers only"
|
followers-desc: "Post to Followers only"
|
||||||
specified: "Direct"
|
specified: "Direct"
|
||||||
specified-desc: "Post to specified users only"
|
specified-desc: "Post to specified users only"
|
||||||
private: "Private"
|
private: "Private"
|
||||||
@ -336,8 +340,8 @@ common/views/widgets/broadcast.vue:
|
|||||||
next: "Next"
|
next: "Next"
|
||||||
common/views/widgets/calendar.vue:
|
common/views/widgets/calendar.vue:
|
||||||
year: "Year {}"
|
year: "Year {}"
|
||||||
month: "Month {}"
|
month: "{},"
|
||||||
day: "Day {}"
|
day: "{}"
|
||||||
today: "Today: "
|
today: "Today: "
|
||||||
this-month: "This month: "
|
this-month: "This month: "
|
||||||
this-year: "This year: "
|
this-year: "This year: "
|
||||||
@ -396,13 +400,13 @@ desktop:
|
|||||||
banner-crop-title: "Crop the part that appears as a banner"
|
banner-crop-title: "Crop the part that appears as a banner"
|
||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
uploading-banner: "Uploading a new banner"
|
uploading-banner: "Uploading a new banner"
|
||||||
banner-updated: "Updated the banner"
|
banner-updated: "Successfully updated the banner"
|
||||||
choose-banner: "Choose the banner"
|
choose-banner: "Choose the banner"
|
||||||
avatar-crop-title: "Crop the part that appears as an avatar"
|
avatar-crop-title: "Crop the part that appears as an avatar"
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
uploading-avatar: "Uploading a new avatar"
|
uploading-avatar: "Uploading a new avatar"
|
||||||
avatar-updated: "Updated the avatar"
|
avatar-updated: "Successfully updated the avatar"
|
||||||
choose-avatar: "Choose an avatar image"
|
choose-avatar: "Select an image for the avatar"
|
||||||
desktop/views/components/activity.chart.vue:
|
desktop/views/components/activity.chart.vue:
|
||||||
total: "Black ... Total"
|
total: "Black ... Total"
|
||||||
notes: "Blue ... Notes"
|
notes: "Blue ... Notes"
|
||||||
@ -450,7 +454,7 @@ desktop/views/components/drive.file.vue:
|
|||||||
rename-file: "Rename file"
|
rename-file: "Rename file"
|
||||||
input-new-file-name: "Enter new name"
|
input-new-file-name: "Enter new name"
|
||||||
copied: "Copied"
|
copied: "Copied"
|
||||||
copied-url-to-clipboard: "Copied URL to clipboard"
|
copied-url-to-clipboard: "URL has been copied to clipboard"
|
||||||
desktop/views/components/drive.folder.vue:
|
desktop/views/components/drive.folder.vue:
|
||||||
unable-to-process: "The operation could not be completed."
|
unable-to-process: "The operation could not be completed."
|
||||||
circular-reference-detected: "The destination folder is a subfolder of the folder you wish to move."
|
circular-reference-detected: "The destination folder is a subfolder of the folder you wish to move."
|
||||||
@ -484,7 +488,7 @@ desktop/views/components/drive.vue:
|
|||||||
upload: "Upload a file"
|
upload: "Upload a file"
|
||||||
url-upload: "Upload from a URL"
|
url-upload: "Upload from a URL"
|
||||||
desktop/views/components/media-image.vue:
|
desktop/views/components/media-image.vue:
|
||||||
sensitive: "The content is NSFW"
|
sensitive: "NSFW"
|
||||||
click-to-show: "Click to show"
|
click-to-show: "Click to show"
|
||||||
desktop/views/components/media-video.vue:
|
desktop/views/components/media-video.vue:
|
||||||
sensitive: "The content is NSFW"
|
sensitive: "The content is NSFW"
|
||||||
@ -523,8 +527,8 @@ desktop/views/components/messaging-window.vue:
|
|||||||
title: "Messaging"
|
title: "Messaging"
|
||||||
desktop/views/components/note-detail.vue:
|
desktop/views/components/note-detail.vue:
|
||||||
more: "Load more conversations"
|
more: "Load more conversations"
|
||||||
private: "This post is private"
|
private: "Post is private"
|
||||||
deleted: "This post has been removed"
|
deleted: "Post has been removed"
|
||||||
reposted-by: "Reposted by {}"
|
reposted-by: "Reposted by {}"
|
||||||
location: "Location"
|
location: "Location"
|
||||||
renote: "Repost"
|
renote: "Repost"
|
||||||
@ -535,8 +539,8 @@ desktop/views/components/notes.note.vue:
|
|||||||
renote: "Repost"
|
renote: "Repost"
|
||||||
add-reaction: "Add a reaction"
|
add-reaction: "Add a reaction"
|
||||||
detail: "Show details"
|
detail: "Show details"
|
||||||
private: "This post is private"
|
private: "Post is private"
|
||||||
deleted: "The post has been deleted"
|
deleted: "Post has been deleted"
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
see-more: "See more"
|
see-more: "See more"
|
||||||
desktop/views/components/notes.vue:
|
desktop/views/components/notes.vue:
|
||||||
@ -550,15 +554,15 @@ desktop/views/components/post-form.vue:
|
|||||||
add-visible-user: "+Add a user"
|
add-visible-user: "+Add a user"
|
||||||
attach-location-information: "Attach location information"
|
attach-location-information: "Attach location information"
|
||||||
hide-contents: "Hide contents"
|
hide-contents: "Hide contents"
|
||||||
reply-placeholder: "Reply to this note..."
|
reply-placeholder: "Reply to this Post..."
|
||||||
quote-placeholder: "Quote this note..."
|
quote-placeholder: "Quote this Post..."
|
||||||
submit: "Post"
|
submit: "Post"
|
||||||
reply: "Reply"
|
reply: "Reply"
|
||||||
renote: "Repost"
|
renote: "Repost"
|
||||||
posted: "Posted!"
|
posted: "Posted!"
|
||||||
replied: "Replied!"
|
replied: "Replied!"
|
||||||
reposted: "Reposted!"
|
reposted: "Reposted!"
|
||||||
note-failed: "Failed to post the note"
|
note-failed: "Failed to post"
|
||||||
reply-failed: "Failed to reply"
|
reply-failed: "Failed to reply"
|
||||||
renote-failed: "Failed to repost"
|
renote-failed: "Failed to repost"
|
||||||
posting: "Posting"
|
posting: "Posting"
|
||||||
@ -571,12 +575,12 @@ desktop/views/components/post-form.vue:
|
|||||||
recent-tags: "Recent"
|
recent-tags: "Recent"
|
||||||
click-to-tagging: "Click to tagging"
|
click-to-tagging: "Click to tagging"
|
||||||
visibility: "Visibility"
|
visibility: "Visibility"
|
||||||
geolocation-alert: "Your device does not support geolocalization."
|
geolocation-alert: "Your device can not measure location infomation"
|
||||||
error: "Error"
|
error: "Error"
|
||||||
enter-username: "Please enter a username..."
|
enter-username: "Please enter a username..."
|
||||||
annotations: "内容への注釈 (オプション)"
|
annotations: "Annotations for the post (optional)"
|
||||||
desktop/views/components/post-form-window.vue:
|
desktop/views/components/post-form-window.vue:
|
||||||
note: "New note"
|
note: "New Post"
|
||||||
reply: "Reply"
|
reply: "Reply"
|
||||||
attaches: "{} media attached"
|
attaches: "{} media attached"
|
||||||
uploading-media: "Uploading {} media"
|
uploading-media: "Uploading {} media"
|
||||||
@ -588,9 +592,9 @@ desktop/views/components/renote-form.vue:
|
|||||||
renote: "Repost"
|
renote: "Repost"
|
||||||
reposting: "Reposting..."
|
reposting: "Reposting..."
|
||||||
success: "Reposted!"
|
success: "Reposted!"
|
||||||
failure: "Repost failed"
|
failure: "Failed to Repost"
|
||||||
desktop/views/components/renote-form-window.vue:
|
desktop/views/components/renote-form-window.vue:
|
||||||
title: "Are you sure you want to repost this?"
|
title: "Do you want to Repost it?"
|
||||||
desktop/views/components/settings-window.vue:
|
desktop/views/components/settings-window.vue:
|
||||||
settings: "Settings"
|
settings: "Settings"
|
||||||
desktop/views/components/settings.vue:
|
desktop/views/components/settings.vue:
|
||||||
@ -685,11 +689,11 @@ desktop/views/components/settings.2fa.vue:
|
|||||||
submit: "Submit"
|
submit: "Submit"
|
||||||
success: "Settings saved!"
|
success: "Settings saved!"
|
||||||
failed: "Failed to setup. Please ensure that the token is correct."
|
failed: "Failed to setup. Please ensure that the token is correct."
|
||||||
info: "From now on, enter the token that is displayed on your device in addition to your password when signing-in to Misskey."
|
info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password."
|
||||||
desktop/views/components/settings.api.vue:
|
desktop/views/components/settings.api.vue:
|
||||||
intro: "To access the API, set this token as the key 'i' of request parameters."
|
intro: "To access the API, set this token as the key 'i' of request parameters."
|
||||||
caution: "Please do not show this token to third parties (do not enter it somewhere else other than here) otherwise your account could get compromised."
|
caution: "Do not enter this token to any apps nor tell this token to others otherwise your account may get compromised."
|
||||||
regeneration-of-token: "In the unlikely event that this token leaks out, you can regenerate it."
|
regeneration-of-token: "In case this token (may) leaks out, you want to regenerate it so that you’ll be safe."
|
||||||
regenerate-token: "Regenerate the token"
|
regenerate-token: "Regenerate the token"
|
||||||
token: "Token:"
|
token: "Token:"
|
||||||
enter-password: "Please enter the password"
|
enter-password: "Please enter the password"
|
||||||
@ -709,18 +713,18 @@ desktop/views/components/settings.password.vue:
|
|||||||
changed: "Password updated"
|
changed: "Password updated"
|
||||||
desktop/views/components/settings.profile.vue:
|
desktop/views/components/settings.profile.vue:
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
choice-avatar: "Choose an image"
|
choice-avatar: "Select an image"
|
||||||
name: "Name"
|
name: "Name"
|
||||||
location: "Location"
|
location: "Location"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
birthday: "Birthday"
|
birthday: "Birthday"
|
||||||
save: "Update profile"
|
save: "Update profile"
|
||||||
locked-account: "Protect your account"
|
locked-account: "Protect your account"
|
||||||
is-locked: "Make a note private"
|
is-locked: "Make your posts private"
|
||||||
other: "Other"
|
other: "Other"
|
||||||
is-bot: "This account is a Bot"
|
is-bot: "This account is a Bot"
|
||||||
is-cat: "This account is a Cat"
|
is-cat: "This account is a Cat"
|
||||||
profile-updated: "Profile updated"
|
profile-updated: "Profile has successfully updated"
|
||||||
desktop/views/components/sub-note-content.vue:
|
desktop/views/components/sub-note-content.vue:
|
||||||
private: "This post is private"
|
private: "This post is private"
|
||||||
deleted: "This post has been deleted"
|
deleted: "This post has been deleted"
|
||||||
@ -736,7 +740,7 @@ desktop/views/components/timeline.vue:
|
|||||||
list: "Lists"
|
list: "Lists"
|
||||||
desktop/views/components/ui.header.vue:
|
desktop/views/components/ui.header.vue:
|
||||||
welcome-back: "Welcome back,"
|
welcome-back: "Welcome back,"
|
||||||
adjective: "さん"
|
adjective: "Sir "
|
||||||
desktop/views/components/ui.header.account.vue:
|
desktop/views/components/ui.header.account.vue:
|
||||||
profile: "Your profile"
|
profile: "Your profile"
|
||||||
drive: "Media storage"
|
drive: "Media storage"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "Pop-out"
|
popout: "Pop-out"
|
||||||
close: "Close"
|
close: "Close"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "Dashboard"
|
||||||
|
drive: "Drive"
|
||||||
|
users: "Users"
|
||||||
|
update: "Updates"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "Dashboard"
|
||||||
|
all-users: "All Users"
|
||||||
|
original-users: "Users on this instance"
|
||||||
|
all-notes: "All Posts"
|
||||||
|
original-notes: "Posts on this instance"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "Suspend a user"
|
||||||
|
suspend: "Suspend"
|
||||||
|
suspended: "Successfully suspended."
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "Unsuspend users"
|
||||||
|
unsuspend: "Unsuspend"
|
||||||
|
unsuspended: "The user has successfully unsuspended."
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "Only media posts"
|
is-media-only: "Only media posts"
|
||||||
is-media-view: "Media view"
|
is-media-view: "Media view"
|
||||||
@ -793,7 +816,7 @@ desktop/views/pages/welcome.vue:
|
|||||||
gotit: "Got it!"
|
gotit: "Got it!"
|
||||||
signin: "Log In"
|
signin: "Log In"
|
||||||
signup: "Sign up"
|
signup: "Sign up"
|
||||||
signin-button: "Log in"
|
signin-button: "Logging in..."
|
||||||
signup-button: "Sign up"
|
signup-button: "Sign up"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
powered-by-misskey: "Powered by <b>Misskey</b>."
|
powered-by-misskey: "Powered by <b>Misskey</b>."
|
||||||
@ -804,8 +827,8 @@ desktop/views/pages/favorites.vue:
|
|||||||
desktop/views/pages/home-customize.vue:
|
desktop/views/pages/home-customize.vue:
|
||||||
title: "Customize home layout"
|
title: "Customize home layout"
|
||||||
desktop/views/pages/note.vue:
|
desktop/views/pages/note.vue:
|
||||||
prev: "Previous note"
|
prev: "Previous post"
|
||||||
next: "Next note"
|
next: "Next post"
|
||||||
desktop/views/pages/selectdrive.vue:
|
desktop/views/pages/selectdrive.vue:
|
||||||
title: "Choose file(s)"
|
title: "Choose file(s)"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
@ -817,22 +840,22 @@ desktop/views/pages/search.vue:
|
|||||||
desktop/views/pages/share.vue:
|
desktop/views/pages/share.vue:
|
||||||
share-with: "Share with {}."
|
share-with: "Share with {}."
|
||||||
desktop/views/pages/tag.vue:
|
desktop/views/pages/tag.vue:
|
||||||
no-posts-found: "No posts \"{}\" found."
|
no-posts-found: "No posts contains \"{}\" found."
|
||||||
desktop/views/pages/user-list.users.vue:
|
desktop/views/pages/user-list.users.vue:
|
||||||
users: "User"
|
users: "User"
|
||||||
add-user: "Add a user"
|
add-user: "Add a user"
|
||||||
username: "Username"
|
username: "Username"
|
||||||
desktop/views/pages/user/user.followers-you-know.vue:
|
desktop/views/pages/user/user.followers-you-know.vue:
|
||||||
title: "Followers you know"
|
title: "Followers you may know"
|
||||||
loading: "Loading"
|
loading: "Loading"
|
||||||
no-users: "No followers you know"
|
no-users: "No followers you know"
|
||||||
desktop/views/pages/user/user.friends.vue:
|
desktop/views/pages/user/user.friends.vue:
|
||||||
title: "Frequent mentions"
|
title: "Frequent mentions"
|
||||||
loading: "Loading"
|
loading: "Loading"
|
||||||
no-users: "No users"
|
no-users: "No frequent mentions"
|
||||||
desktop/views/pages/user/user.vue:
|
desktop/views/pages/user/user.vue:
|
||||||
is-suspended: "This account has been suspended."
|
is-suspended: "This account has been suspended."
|
||||||
is-remote: "This user is a remote user, so the information about them that you see here is not complete. "
|
is-remote: "The user is a remote user. Information about them that you see here may not complete."
|
||||||
view-remote: "See their complete profile"
|
view-remote: "See their complete profile"
|
||||||
desktop/views/pages/user/user.home.vue:
|
desktop/views/pages/user/user.home.vue:
|
||||||
last-used-at: "Last active:"
|
last-used-at: "Last active:"
|
||||||
@ -849,7 +872,7 @@ desktop/views/pages/user/user.profile.vue:
|
|||||||
muted: "Muting"
|
muted: "Muting"
|
||||||
unmute: "Unmute"
|
unmute: "Unmute"
|
||||||
push-to-a-list: "Add to list"
|
push-to-a-list: "Add to list"
|
||||||
list-pushed: "You added {user} to {list}."
|
list-pushed: "Successfully added {user} to {list}."
|
||||||
desktop/views/pages/user/user.header.vue:
|
desktop/views/pages/user/user.header.vue:
|
||||||
posts: "Notes"
|
posts: "Notes"
|
||||||
following: "Following"
|
following: "Following"
|
||||||
@ -857,18 +880,18 @@ desktop/views/pages/user/user.header.vue:
|
|||||||
is-bot: "This account is a Bot"
|
is-bot: "This account is a Bot"
|
||||||
desktop/views/pages/user/user.timeline.vue:
|
desktop/views/pages/user/user.timeline.vue:
|
||||||
default: "Posts"
|
default: "Posts"
|
||||||
with-replies: "Notes and replies"
|
with-replies: "Posts and replies"
|
||||||
with-media: "Media"
|
with-media: "Media"
|
||||||
empty: "This user doesn't seem to have posted anything yet."
|
empty: "This user doesn't seem to have posted anything yet."
|
||||||
desktop/views/widgets/messaging.vue:
|
desktop/views/widgets/messaging.vue:
|
||||||
title: "Messaging"
|
title: "Message"
|
||||||
desktop/views/widgets/notifications.vue:
|
desktop/views/widgets/notifications.vue:
|
||||||
title: "Notifications"
|
title: "Notifications"
|
||||||
settings: "Settings"
|
settings: "Settings"
|
||||||
desktop/views/widgets/polls.vue:
|
desktop/views/widgets/polls.vue:
|
||||||
title: "Polls"
|
title: "Polls"
|
||||||
refresh: "refresh"
|
refresh: "refresh"
|
||||||
nothing: "Nothing"
|
nothing: "No polls found!"
|
||||||
desktop/views/widgets/post-form.vue:
|
desktop/views/widgets/post-form.vue:
|
||||||
title: "Post"
|
title: "Post"
|
||||||
note: "Post"
|
note: "Post"
|
||||||
@ -878,11 +901,11 @@ desktop/views/widgets/profile.vue:
|
|||||||
desktop/views/widgets/trends.vue:
|
desktop/views/widgets/trends.vue:
|
||||||
title: "Trend"
|
title: "Trend"
|
||||||
refresh: "refresh"
|
refresh: "refresh"
|
||||||
nothing: "Nothing"
|
nothing: "No trends found!"
|
||||||
desktop/views/widgets/users.vue:
|
desktop/views/widgets/users.vue:
|
||||||
title: "Recommended users"
|
title: "Recommended users"
|
||||||
refresh: "refresh"
|
refresh: "refresh"
|
||||||
no-one: "No one"
|
no-one: "Anyone!"
|
||||||
mobile/views/components/drive.vue:
|
mobile/views/components/drive.vue:
|
||||||
drive: "Media storage"
|
drive: "Media storage"
|
||||||
used: "used"
|
used: "used"
|
||||||
@ -912,7 +935,7 @@ mobile/views/components/drive.file-detail.vue:
|
|||||||
hash: "Hash (md5)"
|
hash: "Hash (md5)"
|
||||||
exif: "EXIF"
|
exif: "EXIF"
|
||||||
mobile/views/components/media-image.vue:
|
mobile/views/components/media-image.vue:
|
||||||
sensitive: "The content is NSFW"
|
sensitive: "NSFW"
|
||||||
click-to-show: "Click to show"
|
click-to-show: "Click to show"
|
||||||
mobile/views/components/media-video.vue:
|
mobile/views/components/media-video.vue:
|
||||||
sensitive: "The content is NSFW"
|
sensitive: "The content is NSFW"
|
||||||
@ -923,7 +946,7 @@ mobile/views/components/follow-button.vue:
|
|||||||
request-pending: "Pending follow request"
|
request-pending: "Pending follow request"
|
||||||
follow-request: "Follow request"
|
follow-request: "Follow request"
|
||||||
mobile/views/components/friends-maker.vue:
|
mobile/views/components/friends-maker.vue:
|
||||||
title: "Let's follow users"
|
title: "Let's follow them"
|
||||||
empty: "Featured user was not found."
|
empty: "Featured user was not found."
|
||||||
fetching: "Loading"
|
fetching: "Loading"
|
||||||
refresh: "See more"
|
refresh: "See more"
|
||||||
@ -954,7 +977,7 @@ mobile/views/components/notes.vue:
|
|||||||
failed: "Failed to load"
|
failed: "Failed to load"
|
||||||
retry: "Retry"
|
retry: "Retry"
|
||||||
mobile/views/components/notifications.vue:
|
mobile/views/components/notifications.vue:
|
||||||
more: "More"
|
more: "See more"
|
||||||
empty: "No notifications"
|
empty: "No notifications"
|
||||||
mobile/views/components/post-form.vue:
|
mobile/views/components/post-form.vue:
|
||||||
add-visible-user: "Add a user"
|
add-visible-user: "Add a user"
|
||||||
@ -963,7 +986,7 @@ mobile/views/components/post-form.vue:
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
quote-placeholder: "Quote this post... (optional)"
|
quote-placeholder: "Quote this post... (optional)"
|
||||||
reply-placeholder: "Reply to this note..."
|
reply-placeholder: "Reply to this note..."
|
||||||
cw-placeholder: "Comments about content (optional)"
|
cw-placeholder: "Comments for the post (optional)"
|
||||||
location-alert: "Your device does not provide location services"
|
location-alert: "Your device does not provide location services"
|
||||||
error: "Error"
|
error: "Error"
|
||||||
username-prompt: "Enter user name"
|
username-prompt: "Enter user name"
|
||||||
@ -977,7 +1000,7 @@ mobile/views/components/timeline.vue:
|
|||||||
load-more: "More"
|
load-more: "More"
|
||||||
mobile/views/components/ui.header.vue:
|
mobile/views/components/ui.header.vue:
|
||||||
welcome-back: "Welcome back, "
|
welcome-back: "Welcome back, "
|
||||||
adjective: "さん"
|
adjective: "Sir"
|
||||||
mobile/views/components/ui.nav.vue:
|
mobile/views/components/ui.nav.vue:
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
@ -994,7 +1017,7 @@ mobile/views/components/ui.nav.vue:
|
|||||||
about: "About Misskey"
|
about: "About Misskey"
|
||||||
mobile/views/components/user-timeline.vue:
|
mobile/views/components/user-timeline.vue:
|
||||||
no-notes: "It seems this user hasn't posted anything yet."
|
no-notes: "It seems this user hasn't posted anything yet."
|
||||||
no-notes-with-media: "There are no notes with media attachments"
|
no-notes-with-media: "There are no posts attaching media"
|
||||||
load-more: "More"
|
load-more: "More"
|
||||||
mobile/views/components/users-list.vue:
|
mobile/views/components/users-list.vue:
|
||||||
all: "All"
|
all: "All"
|
||||||
@ -1009,7 +1032,7 @@ mobile/views/pages/drive.vue:
|
|||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
more: "Load more"
|
more: "Load more"
|
||||||
mobile/views/pages/signup.vue:
|
mobile/views/pages/signup.vue:
|
||||||
lets-start: "Let's start! 📦"
|
lets-start: "Your account is now ready! 📦"
|
||||||
mobile/views/pages/followers.vue:
|
mobile/views/pages/followers.vue:
|
||||||
followers-of: "Followers of {}"
|
followers-of: "Followers of {}"
|
||||||
mobile/views/pages/following.vue:
|
mobile/views/pages/following.vue:
|
||||||
@ -1066,7 +1089,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
|||||||
mobile/views/pages/search.vue:
|
mobile/views/pages/search.vue:
|
||||||
search: "Search"
|
search: "Search"
|
||||||
empty: "No posts were found for '{}'"
|
empty: "No posts were found for '{}'"
|
||||||
not-found: "「{}」に関する投稿は見つかりませんでした。"
|
not-found: "No posts were found for \"{}\"."
|
||||||
mobile/views/pages/selectdrive.vue:
|
mobile/views/pages/selectdrive.vue:
|
||||||
select-file: "Choose files"
|
select-file: "Choose files"
|
||||||
mobile/views/pages/settings.vue:
|
mobile/views/pages/settings.vue:
|
||||||
@ -1078,7 +1101,7 @@ mobile/views/pages/settings.vue:
|
|||||||
specify-language: "Select your language"
|
specify-language: "Select your language"
|
||||||
design: "Design and display"
|
design: "Design and display"
|
||||||
dark-mode: "Dark Mode"
|
dark-mode: "Dark Mode"
|
||||||
i-am-under-limited-internet: "I'm under limited internet"
|
i-am-under-limited-internet: "I'm in limited bandwidth"
|
||||||
circle-icons: "Use circle icons"
|
circle-icons: "Use circle icons"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
show-reply-target: "Show reply target"
|
show-reply-target: "Show reply target"
|
||||||
@ -1090,7 +1113,7 @@ mobile/views/pages/settings.vue:
|
|||||||
behavior: "Behavior"
|
behavior: "Behavior"
|
||||||
fetch-on-scroll: "Endless loading on scroll"
|
fetch-on-scroll: "Endless loading on scroll"
|
||||||
disable-via-mobile: "Don't mark the post as 'from mobile'"
|
disable-via-mobile: "Don't mark the post as 'from mobile'"
|
||||||
load-raw-images: "Show attached images in high-quality"
|
load-raw-images: "Show attached images in original quality"
|
||||||
load-remote-media: "Show media from a remote server"
|
load-remote-media: "Show media from a remote server"
|
||||||
twitter: "Twitter integration"
|
twitter: "Twitter integration"
|
||||||
twitter-connect: "Connect to your Twitter account"
|
twitter-connect: "Connect to your Twitter account"
|
||||||
@ -1111,7 +1134,7 @@ mobile/views/pages/user.vue:
|
|||||||
follows-you: "Follows you"
|
follows-you: "Follows you"
|
||||||
following: "Following"
|
following: "Following"
|
||||||
followers: "Followers"
|
followers: "Followers"
|
||||||
notes: "Notes"
|
notes: "Posts"
|
||||||
overview: "Overview"
|
overview: "Overview"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
media: "Media"
|
media: "Media"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "felicidades"
|
congrats: "felicidades"
|
||||||
angry: "enfadado"
|
angry: "enfadado"
|
||||||
confused: "confundido"
|
confused: "confundido"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Chafado"
|
pudding: "Chafado"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "¿Qué haces?"
|
a: "¿Qué haces?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "Rendirse"
|
surrender: "Rendirse"
|
||||||
surrendered: "Por rendirse"
|
surrendered: "Por rendirse"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "¡Juega Reversi con tus amigos!"
|
sub-title: "¡Juega Reversi con tus amigos!"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -11,7 +11,7 @@ common:
|
|||||||
warning: "<strong>Misskey n'utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
|
warning: "<strong>Misskey n'utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
|
||||||
application-authorization: "Permissions de l'application"
|
application-authorization: "Permissions de l'application"
|
||||||
close: "Fermer"
|
close: "Fermer"
|
||||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte peut être compromis."
|
||||||
got-it: "J'ai compris !"
|
got-it: "J'ai compris !"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
title: "Conseils de personnalisation"
|
title: "Conseils de personnalisation"
|
||||||
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "Félicitations !"
|
congrats: "Félicitations !"
|
||||||
angry: "En colère"
|
angry: "En colère"
|
||||||
confused: "Confus"
|
confused: "Confus"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "Que faîtes vous maintenant ?"
|
a: "Que faîtes vous maintenant ?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "Se rendre"
|
surrender: "Se rendre"
|
||||||
surrendered: "Par abandon"
|
surrendered: "Par abandon"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "Carte en boucle"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "Jouer à Reversi avec vos amis·es !"
|
sub-title: "Jouer à Reversi avec vos amis·es !"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "Fermer"
|
close: "Fermer"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "Les publications médias uniquement"
|
is-media-only: "Les publications médias uniquement"
|
||||||
is-media-view: "Vue média"
|
is-media-view: "Vue média"
|
||||||
@ -817,7 +840,7 @@ desktop/views/pages/search.vue:
|
|||||||
desktop/views/pages/share.vue:
|
desktop/views/pages/share.vue:
|
||||||
share-with: "Partager avec {}"
|
share-with: "Partager avec {}"
|
||||||
desktop/views/pages/tag.vue:
|
desktop/views/pages/tag.vue:
|
||||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
no-posts-found: "Pas de message avec un hashtag {} trouvé."
|
||||||
desktop/views/pages/user-list.users.vue:
|
desktop/views/pages/user-list.users.vue:
|
||||||
users: "Utilisateurs"
|
users: "Utilisateurs"
|
||||||
add-user: "Ajouter un utilisateur"
|
add-user: "Ajouter un utilisateur"
|
||||||
@ -842,7 +865,7 @@ desktop/views/pages/user/user.photos.vue:
|
|||||||
no-photos: "Pas de photos"
|
no-photos: "Pas de photos"
|
||||||
desktop/views/pages/user/user.profile.vue:
|
desktop/views/pages/user/user.profile.vue:
|
||||||
follows-you: "Vous suis"
|
follows-you: "Vous suis"
|
||||||
stalk: "ストークする"
|
stalk: "Traquer"
|
||||||
stalking: "ストーキングしています"
|
stalking: "ストーキングしています"
|
||||||
unstalk: "ストーク解除"
|
unstalk: "ストーク解除"
|
||||||
mute: "Mettre en sourdine"
|
mute: "Mettre en sourdine"
|
||||||
@ -892,11 +915,11 @@ mobile/views/components/drive.vue:
|
|||||||
load-more: "Charger plus"
|
load-more: "Charger plus"
|
||||||
nothing-in-drive: "Rien"
|
nothing-in-drive: "Rien"
|
||||||
folder-is-empty: "Ce dossier est vide"
|
folder-is-empty: "Ce dossier est vide"
|
||||||
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
|
prompt: "Que veux-tu faire ? (Entrez un nombre): <1 → Télécharger le fichier | 2 → Télécharger le fichier avec l'URL | 3 → Créer le dossier | 4 → Modifier le nom du dossier | 5 → Déplacer ce dossier | 6 → Supprimer ce dossier >"
|
||||||
deletion-alert: "Désolé ! La suppression d’un dossier n’est pas encore implémentée."
|
deletion-alert: "Désolé ! La suppression d’un dossier n’est pas encore implémentée."
|
||||||
folder-name: "Nom du dossier"
|
folder-name: "Nom du dossier"
|
||||||
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
|
root-rename-alert: "L'emplacement actuel est la racine, pas le dossier, vous ne pouvez donc pas le renommer. Veuillez vous déplacer dans le dossier dont vous souhaitez modifier le nom."
|
||||||
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
|
root-move-alert: "L'emplacement actuel est la racine, ce n'est pas un dossier et il ne peut pas être déplacé. Veuillez vous déplacer dans le dossier que vous souhaitez déplacer."
|
||||||
url-prompt: "URL du fichier que vous souhaitez téléverser"
|
url-prompt: "URL du fichier que vous souhaitez téléverser"
|
||||||
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
||||||
mobile/views/components/drive-file-detail.vue:
|
mobile/views/components/drive-file-detail.vue:
|
||||||
@ -976,7 +999,7 @@ mobile/views/components/timeline.vue:
|
|||||||
empty: "Pas de notes"
|
empty: "Pas de notes"
|
||||||
load-more: "Afficher plus"
|
load-more: "Afficher plus"
|
||||||
mobile/views/components/ui.header.vue:
|
mobile/views/components/ui.header.vue:
|
||||||
welcome-back: "Bon retour parmi nous !"
|
welcome-back: "Content de vous revoir ! "
|
||||||
adjective: "さん"
|
adjective: "さん"
|
||||||
mobile/views/components/ui.nav.vue:
|
mobile/views/components/ui.nav.vue:
|
||||||
timeline: "Fil d'actualité"
|
timeline: "Fil d'actualité"
|
||||||
@ -1020,7 +1043,7 @@ mobile/views/pages/home.vue:
|
|||||||
hybrid: "Social"
|
hybrid: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
mobile/views/pages/tag.vue:
|
mobile/views/pages/tag.vue:
|
||||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
no-posts-found: "Pas de message avec un hashtag {} trouvé."
|
||||||
mobile/views/pages/welcome.vue:
|
mobile/views/pages/welcome.vue:
|
||||||
signup: "S'enregistrer"
|
signup: "S'enregistrer"
|
||||||
mobile/views/pages/widgets.vue:
|
mobile/views/pages/widgets.vue:
|
||||||
@ -1066,7 +1089,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
|||||||
mobile/views/pages/search.vue:
|
mobile/views/pages/search.vue:
|
||||||
search: "Chercher"
|
search: "Chercher"
|
||||||
empty: "Aucun message trouvé pour '{}' "
|
empty: "Aucun message trouvé pour '{}' "
|
||||||
not-found: "「{}」に関する投稿は見つかりませんでした。"
|
not-found: "Aucun post pour {} n'a été trouvé."
|
||||||
mobile/views/pages/selectdrive.vue:
|
mobile/views/pages/selectdrive.vue:
|
||||||
select-file: "Choisissez un fichier"
|
select-file: "Choisissez un fichier"
|
||||||
mobile/views/pages/settings.vue:
|
mobile/views/pages/settings.vue:
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
|
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
@ -181,6 +182,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
|
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
@ -893,6 +897,29 @@ desktop/views/components/window.vue:
|
|||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
|
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "받으세요"
|
congrats: "받으세요"
|
||||||
angry: "화냈어"
|
angry: "화냈어"
|
||||||
confused: "곤란하고 있어"
|
confused: "곤란하고 있어"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "지금 어떻게하고있어?"
|
a: "지금 어떻게하고있어?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -39,7 +39,7 @@ common:
|
|||||||
weeks_ago: "{} tyg. temu"
|
weeks_ago: "{} tyg. temu"
|
||||||
months_ago: "{} mies. temu"
|
months_ago: "{} mies. temu"
|
||||||
years_ago: "{} lat temu"
|
years_ago: "{} lat temu"
|
||||||
month-and-day: "{month}月 {day}日"
|
month-and-day: "{month}-{day}"
|
||||||
trash: "Kosz"
|
trash: "Kosz"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
sunday: "N"
|
sunday: "N"
|
||||||
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "Gratuluję!"
|
congrats: "Gratuluję!"
|
||||||
angry: "Wściekły"
|
angry: "Wściekły"
|
||||||
confused: "Zmieszany"
|
confused: "Zmieszany"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "Co robisz?"
|
a: "Co robisz?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "Zagraj w Reversi ze znajomymi!"
|
sub-title: "Zagraj w Reversi ze znajomymi!"
|
||||||
@ -394,15 +398,15 @@ common/views/pages/follow.vue:
|
|||||||
follow-request: "Poproś o śledzenie"
|
follow-request: "Poproś o śledzenie"
|
||||||
desktop:
|
desktop:
|
||||||
banner-crop-title: "バナーとして表示する部分を選択"
|
banner-crop-title: "バナーとして表示する部分を選択"
|
||||||
banner: "バナー"
|
banner: "Baner"
|
||||||
uploading-banner: "新しいバナーをアップロードしています"
|
uploading-banner: "Wysyłanie baneru"
|
||||||
banner-updated: "バナーを更新しました"
|
banner-updated: "Zmieniono baner"
|
||||||
choose-banner: "バナーにする画像を選択"
|
choose-banner: "Wybierz baner"
|
||||||
avatar-crop-title: "アバターとして表示する部分を選択"
|
avatar-crop-title: "Wybierz część obrazu, która zostanie użyta jako awatar"
|
||||||
avatar: "アバター"
|
avatar: "Awatar"
|
||||||
uploading-avatar: "新しいアバターをアップロードしています"
|
uploading-avatar: "Wysyłanie awatara"
|
||||||
avatar-updated: "アバターを更新しました"
|
avatar-updated: "Wysłano awatar"
|
||||||
choose-avatar: "アバターにする画像を選択"
|
choose-avatar: "Wybierz awatar"
|
||||||
desktop/views/components/activity.chart.vue:
|
desktop/views/components/activity.chart.vue:
|
||||||
total: "Czarny … Łącznie"
|
total: "Czarny … Łącznie"
|
||||||
notes: "Niebieski … Wpisy"
|
notes: "Niebieski … Wpisy"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "Pop-out"
|
popout: "Pop-out"
|
||||||
close: "Zamknij"
|
close: "Zamknij"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "Tylko wpisy z zawartością multimedialną"
|
is-media-only: "Tylko wpisy z zawartością multimedialną"
|
||||||
is-media-view: "Widok multimediów"
|
is-media-view: "Widok multimediów"
|
||||||
@ -1162,4 +1185,4 @@ docs:
|
|||||||
type: "Rodzaj"
|
type: "Rodzaj"
|
||||||
description: "Opis"
|
description: "Opis"
|
||||||
dev/views/index.vue:
|
dev/views/index.vue:
|
||||||
manage-apps: "アプリの管理"
|
manage-apps: "Zarządzaj aplikacjami"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
@ -66,6 +66,7 @@ common:
|
|||||||
congrats: "おめでとう"
|
congrats: "おめでとう"
|
||||||
angry: "おこ"
|
angry: "おこ"
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "投了"
|
surrender: "投了"
|
||||||
surrendered: "投了により"
|
surrendered: "投了により"
|
||||||
|
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||||
|
looped-map: "ループマップ"
|
||||||
|
can-put-everywhere: "どこでも置けるモード"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
@ -780,6 +784,25 @@ desktop/views/components/users-list-item.vue:
|
|||||||
desktop/views/components/window.vue:
|
desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
desktop/views/pages/admin/admin.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
drive: "ドライブ"
|
||||||
|
users: "ユーザー"
|
||||||
|
update: "更新"
|
||||||
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
all-users: "全てのユーザー"
|
||||||
|
original-users: "このインスタンスのユーザー"
|
||||||
|
all-notes: "全てのノート"
|
||||||
|
original-notes: "このインスタンスのノート"
|
||||||
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
|
suspend-user: "ユーザーの凍結"
|
||||||
|
suspend: "凍結"
|
||||||
|
suspended: "凍結しました"
|
||||||
|
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
||||||
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
|
unsuspend: "凍結の解除"
|
||||||
|
unsuspended: "凍結を解除しました"
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
is-media-only: "メディア投稿のみ"
|
is-media-only: "メディア投稿のみ"
|
||||||
is-media-view: "メディアビュー"
|
is-media-view: "メディアビュー"
|
||||||
|
31
package.json
31
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "5.21.0",
|
"version": "6.0.0",
|
||||||
"clientVersion": "1.0.8117",
|
"clientVersion": "1.0.8367",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -31,6 +31,7 @@
|
|||||||
"@types/dateformat": "1.0.1",
|
"@types/dateformat": "1.0.1",
|
||||||
"@types/debug": "0.0.30",
|
"@types/debug": "0.0.30",
|
||||||
"@types/deep-equal": "1.0.1",
|
"@types/deep-equal": "1.0.1",
|
||||||
|
"@types/double-ended-queue": "2.1.0",
|
||||||
"@types/elasticsearch": "5.0.25",
|
"@types/elasticsearch": "5.0.25",
|
||||||
"@types/file-type": "5.2.1",
|
"@types/file-type": "5.2.1",
|
||||||
"@types/gulp": "3.8.36",
|
"@types/gulp": "3.8.36",
|
||||||
@ -57,9 +58,9 @@
|
|||||||
"@types/minio": "6.0.2",
|
"@types/minio": "6.0.2",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.3",
|
"@types/mocha": "5.2.3",
|
||||||
"@types/mongodb": "3.1.3",
|
"@types/mongodb": "3.1.4",
|
||||||
"@types/ms": "0.7.30",
|
"@types/ms": "0.7.30",
|
||||||
"@types/node": "10.5.8",
|
"@types/node": "10.7.1",
|
||||||
"@types/portscanner": "2.1.0",
|
"@types/portscanner": "2.1.0",
|
||||||
"@types/pug": "2.0.4",
|
"@types/pug": "2.0.4",
|
||||||
"@types/qrcode": "1.2.0",
|
"@types/qrcode": "1.2.0",
|
||||||
@ -76,10 +77,10 @@
|
|||||||
"@types/systeminformation": "3.23.0",
|
"@types/systeminformation": "3.23.0",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
"@types/uuid": "3.4.3",
|
"@types/uuid": "3.4.3",
|
||||||
"@types/webpack": "4.4.9",
|
"@types/webpack": "4.4.10",
|
||||||
"@types/webpack-stream": "3.2.10",
|
"@types/webpack-stream": "3.2.10",
|
||||||
"@types/websocket": "0.0.39",
|
"@types/websocket": "0.0.39",
|
||||||
"@types/ws": "5.1.2",
|
"@types/ws": "6.0.0",
|
||||||
"animejs": "2.2.0",
|
"animejs": "2.2.0",
|
||||||
"autosize": "4.0.2",
|
"autosize": "4.0.2",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
@ -97,6 +98,7 @@
|
|||||||
"deepcopy": "0.6.3",
|
"deepcopy": "0.6.3",
|
||||||
"diskusage": "0.2.4",
|
"diskusage": "0.2.4",
|
||||||
"dompurify": "1.0.5",
|
"dompurify": "1.0.5",
|
||||||
|
"double-ended-queue": "2.1.0-0",
|
||||||
"elasticsearch": "15.1.1",
|
"elasticsearch": "15.1.1",
|
||||||
"element-ui": "2.4.6",
|
"element-ui": "2.4.6",
|
||||||
"emojilib": "2.3.0",
|
"emojilib": "2.3.0",
|
||||||
@ -145,8 +147,9 @@
|
|||||||
"koa-slow": "2.1.0",
|
"koa-slow": "2.1.0",
|
||||||
"koa-views": "6.1.4",
|
"koa-views": "6.1.4",
|
||||||
"loader-utils": "1.1.0",
|
"loader-utils": "1.1.0",
|
||||||
|
"lodash.assign": "4.2.0",
|
||||||
"mecab-async": "0.1.2",
|
"mecab-async": "0.1.2",
|
||||||
"minio": "6.0.0",
|
"minio": "7.0.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"moji": "0.5.1",
|
"moji": "0.5.1",
|
||||||
@ -160,7 +163,7 @@
|
|||||||
"object-assign-deep": "0.4.0",
|
"object-assign-deep": "0.4.0",
|
||||||
"on-build-webpack": "0.1.0",
|
"on-build-webpack": "0.1.0",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"parse5": "5.0.0",
|
"parse5": "5.1.0",
|
||||||
"portscanner": "2.2.0",
|
"portscanner": "2.2.0",
|
||||||
"progress-bar-webpack-plugin": "1.11.0",
|
"progress-bar-webpack-plugin": "1.11.0",
|
||||||
"promise-sequential": "1.1.1",
|
"promise-sequential": "1.1.1",
|
||||||
@ -177,7 +180,7 @@
|
|||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sass-loader": "7.1.0",
|
"sass-loader": "7.1.0",
|
||||||
"seedrandom": "2.4.3",
|
"seedrandom": "2.4.4",
|
||||||
"sharp": "0.20.5",
|
"sharp": "0.20.5",
|
||||||
"showdown": "1.8.6",
|
"showdown": "1.8.6",
|
||||||
"showdown-highlightjs-extension": "0.1.2",
|
"showdown-highlightjs-extension": "0.1.2",
|
||||||
@ -187,27 +190,27 @@
|
|||||||
"style-loader": "0.22.1",
|
"style-loader": "0.22.1",
|
||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.2",
|
"stylus-loader": "3.0.2",
|
||||||
"summaly": "2.1.2",
|
"summaly": "2.1.3",
|
||||||
"systeminformation": "3.42.9",
|
"systeminformation": "3.42.9",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"ts-loader": "4.4.1",
|
"ts-loader": "4.4.1",
|
||||||
"ts-node": "7.0.0",
|
"ts-node": "7.0.1",
|
||||||
"tslint": "5.10.0",
|
"tslint": "5.10.0",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"typescript-eslint-parser": "18.0.0",
|
"typescript-eslint-parser": "18.0.0",
|
||||||
"uglify-es": "3.3.9",
|
"uglify-es": "3.3.9",
|
||||||
"url-loader": "1.0.1",
|
"url-loader": "1.1.0",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"v-animate-css": "0.0.2",
|
"v-animate-css": "0.0.2",
|
||||||
"vue": "2.5.17",
|
"vue": "2.5.17",
|
||||||
"vue-cropperjs": "2.2.1",
|
"vue-cropperjs": "2.2.1",
|
||||||
"vue-js-modal": "1.3.16",
|
"vue-js-modal": "1.3.17",
|
||||||
"vue-json-tree-view": "2.1.4",
|
"vue-json-tree-view": "2.1.4",
|
||||||
"vue-loader": "15.3.0",
|
"vue-loader": "15.3.0",
|
||||||
"vue-router": "3.0.1",
|
"vue-router": "3.0.1",
|
||||||
"vue-style-loader": "4.1.1",
|
"vue-style-loader": "4.1.2",
|
||||||
"vue-template-compiler": "2.5.17",
|
"vue-template-compiler": "2.5.17",
|
||||||
"vuedraggable": "2.16.0",
|
"vuedraggable": "2.16.0",
|
||||||
"vuex": "3.0.1",
|
"vuex": "3.0.1",
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
export default () => [
|
const kaos = [
|
||||||
'(=^・・^=)',
|
'(=^・・^=)',
|
||||||
'v(\'ω\')v',
|
'v(\'ω\')v',
|
||||||
'🐡( \'-\' 🐡 )フグパンチ!!!!'
|
'🐡( \'-\' 🐡 )フグパンチ!!!!',
|
||||||
][Math.floor(Math.random() * 3)];
|
'🖕(´・_・`)🖕',
|
||||||
|
'(。>﹏<。)'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default () => kaos[Math.floor(Math.random() * kaos.length)];
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="mk-avatar" :title="user | acct" :style="style" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick"></span>
|
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
|
||||||
<span class="mk-avatar" :title="user | acct" :style="style" v-else-if="disableLink && disablePreview" @click="onClick"></span>
|
<span class="inner" :style="style"></span>
|
||||||
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id"></router-link>
|
</span>
|
||||||
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && disablePreview"></router-link>
|
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
|
||||||
|
<span class="inner" :style="style"></span>
|
||||||
|
</span>
|
||||||
|
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
|
||||||
|
<span class="inner" :style="style"></span>
|
||||||
|
</router-link>
|
||||||
|
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
|
||||||
|
<span class="inner" :style="style"></span>
|
||||||
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -30,14 +38,17 @@ export default Vue.extend({
|
|||||||
lightmode(): boolean {
|
lightmode(): boolean {
|
||||||
return this.$store.state.device.lightmode;
|
return this.$store.state.device.lightmode;
|
||||||
},
|
},
|
||||||
|
cat(): boolean {
|
||||||
|
return this.user.isCat && this.$store.state.settings.circleIcons;
|
||||||
|
},
|
||||||
style(): any {
|
style(): any {
|
||||||
return {
|
return {
|
||||||
backgroundColor: this.lightmode
|
backgroundColor: this.lightmode
|
||||||
? `rgb(${ this.user.avatarColor.slice(0, 3).join(',') })`
|
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
|
||||||
: this.user.avatarColor && this.user.avatarColor.length == 3
|
: this.user.avatarColor && this.user.avatarColor.length == 3
|
||||||
? `rgb(${ this.user.avatarColor.join(',') })`
|
? `rgb(${this.user.avatarColor.join(',')})`
|
||||||
: null,
|
: null,
|
||||||
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`,
|
backgroundImage: this.lightmode ? null : `url(${this.user.avatarUrl})`,
|
||||||
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -51,10 +62,47 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-avatar
|
|
||||||
|
root(isDark)
|
||||||
display inline-block
|
display inline-block
|
||||||
vertical-align bottom
|
vertical-align bottom
|
||||||
background-size cover
|
|
||||||
background-position center center
|
&:not(.cat)
|
||||||
transition border-radius 1s ease
|
overflow hidden
|
||||||
|
border-radius 8px
|
||||||
|
|
||||||
|
&.cat::before,
|
||||||
|
&.cat::after
|
||||||
|
background #df548f
|
||||||
|
border solid 4px isDark ? #e0eefd : #202224
|
||||||
|
box-sizing border-box
|
||||||
|
content ''
|
||||||
|
display inline-block
|
||||||
|
height 50%
|
||||||
|
width 50%
|
||||||
|
|
||||||
|
&.cat::before
|
||||||
|
border-radius 0 75% 75%
|
||||||
|
transform rotate(37.5deg) skew(30deg)
|
||||||
|
|
||||||
|
&.cat::after
|
||||||
|
border-radius 75% 0 75% 75%
|
||||||
|
transform rotate(-37.5deg) skew(-30deg)
|
||||||
|
|
||||||
|
.inner
|
||||||
|
background-position center center
|
||||||
|
background-size cover
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
position absolute
|
||||||
|
right 0
|
||||||
|
top 0
|
||||||
|
transition border-radius 1s ease
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
.mk-avatar[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.mk-avatar:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
</style>
|
</style>
|
||||||
|
@ -60,6 +60,12 @@
|
|||||||
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
|
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<p v-if="game.settings.isLlotheo">%i18n:@is-llotheo%</p>
|
||||||
|
<p v-if="game.settings.loopedBoard">%i18n:@looped-map%</p>
|
||||||
|
<p v-if="game.settings.canPutEverywhere">%i18n:@can-put-everywhere%</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
|
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
|
||||||
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
|
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
|
||||||
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
|
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
|
||||||
<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:bookmark%</span>
|
<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span>
|
||||||
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
|
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
|
||||||
<span class="is-bot" v-if="note.user.isBot">bot</span>
|
<span class="is-bot" v-if="note.user.isBot">bot</span>
|
||||||
<span class="is-cat" v-if="note.user.isCat">cat</span>
|
<span class="is-cat" v-if="note.user.isCat">cat</span>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
|
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
|
||||||
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
|
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
|
||||||
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
|
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
|
||||||
|
<img v-if="reaction == 'rip'" src="/assets/reactions/rip.png" alt="%i18n:common.reactions.rip%">
|
||||||
<template v-if="reaction == 'pudding'">
|
<template v-if="reaction == 'pudding'">
|
||||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
|
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
|
||||||
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
||||||
|
@ -10,9 +10,10 @@
|
|||||||
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.hmm%"><mk-reaction-icon reaction='hmm'/></button>
|
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.hmm%"><mk-reaction-icon reaction='hmm'/></button>
|
||||||
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.surprise%"><mk-reaction-icon reaction='surprise'/></button>
|
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.surprise%"><mk-reaction-icon reaction='surprise'/></button>
|
||||||
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.congrats%"><mk-reaction-icon reaction='congrats'/></button>
|
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.congrats%"><mk-reaction-icon reaction='congrats'/></button>
|
||||||
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.angry%"><mk-reaction-icon reaction='angry'/></button>
|
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" title="%i18n:common.reactions.angry%"><mk-reaction-icon reaction='angry'/></button>
|
||||||
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.confused%"><mk-reaction-icon reaction='confused'/></button>
|
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" title="%i18n:common.reactions.confused%"><mk-reaction-icon reaction='confused'/></button>
|
||||||
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.pudding%"><mk-reaction-icon reaction='pudding'/></button>
|
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" title="%i18n:common.reactions.rip%"><mk-reaction-icon reaction='rip'/></button>
|
||||||
|
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" title="%i18n:common.reactions.pudding%"><mk-reaction-icon reaction='pudding'/></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-reactions-viewer">
|
<div class="mk-reactions-viewer">
|
||||||
<template v-if="reactions">
|
<template v-if="reactions">
|
||||||
<span v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
|
<span :class="{notReacted}" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
|
||||||
<span v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
|
<span :class="{notReacted}" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
|
||||||
<span v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
|
<span :class="{notReacted}" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
|
||||||
<span v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
|
<span :class="{notReacted}" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
|
||||||
<span v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
|
<span :class="{notReacted}" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
|
||||||
<span v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
|
<span :class="{notReacted}" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
|
||||||
<span v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
|
<span :class="{notReacted}" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
|
||||||
<span v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
|
<span :class="{notReacted}" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
|
||||||
<span v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
|
<span :class="{notReacted}" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
|
||||||
|
<span :class="{notReacted}" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -21,6 +22,17 @@ export default Vue.extend({
|
|||||||
computed: {
|
computed: {
|
||||||
reactions(): number {
|
reactions(): number {
|
||||||
return this.note.reactionCounts;
|
return this.note.reactionCounts;
|
||||||
|
},
|
||||||
|
notReacted(): boolean {
|
||||||
|
return this.note.myReaction == null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
react(reaction: string) {
|
||||||
|
(this as any).api('notes/reactions/create', {
|
||||||
|
noteId: this.note.id,
|
||||||
|
reaction: reaction
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -39,6 +51,9 @@ root(isDark)
|
|||||||
> span
|
> span
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
|
|
||||||
|
&.notReacted
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
> .mk-reaction-icon
|
> .mk-reaction-icon
|
||||||
font-size 1.4em
|
font-size 1.4em
|
||||||
|
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
|
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
|
||||||
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
||||||
<p style="margin: 8px 0;">%i18n:@or%<a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
<p style="margin: 8px 0;" v-if="twitterIntegration">%i18n:@or%<a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { apiUrl, host } from '../../../config';
|
import { apiUrl, host, twitterIntegration } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
@ -36,7 +36,8 @@ export default Vue.extend({
|
|||||||
password: '',
|
password: '',
|
||||||
token: '',
|
token: '',
|
||||||
apiUrl,
|
apiUrl,
|
||||||
host
|
host,
|
||||||
|
twitterIntegration
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<iframe v-if="youtubeId" type="text/html" height="250"
|
<iframe v-if="player" :src="player" heigth="250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
|
||||||
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
|
|
||||||
frameborder="0"/>
|
|
||||||
<iframe v-else-if="spotifyId"
|
|
||||||
:src="`https://open.spotify.com/embed/track/${spotifyId}`"
|
|
||||||
frameborder="0" allowtransparency="true" allow="encrypted-media" />
|
|
||||||
<iframe v-else-if="nicovideoId"
|
|
||||||
:src="`https://embed.nicovideo.jp/watch/${nicovideoId}?oldScript=1&referer=${misskeyUrl}&from=${position || '0'}&allowProgrammaticFullScreen=1`"
|
|
||||||
frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
|
|
||||||
<div v-else-if="tweetUrl && detail" class="twitter">
|
<div v-else-if="tweetUrl && detail" class="twitter">
|
||||||
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
|
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
|
||||||
<a :href="url"></a>
|
<a :href="url"></a>
|
||||||
@ -54,10 +46,7 @@ export default Vue.extend({
|
|||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
icon: null,
|
icon: null,
|
||||||
sitename: null,
|
sitename: null,
|
||||||
youtubeId: null,
|
player: null,
|
||||||
spotifyId: null,
|
|
||||||
nicovideoId: null,
|
|
||||||
position: null,
|
|
||||||
tweetUrl: null,
|
tweetUrl: null,
|
||||||
misskeyUrl
|
misskeyUrl
|
||||||
};
|
};
|
||||||
@ -65,23 +54,7 @@ export default Vue.extend({
|
|||||||
created() {
|
created() {
|
||||||
const url = new URL(this.url);
|
const url = new URL(this.url);
|
||||||
|
|
||||||
if (url.hostname == 'www.youtube.com') {
|
if (this.detail && url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
|
||||||
this.youtubeId = url.searchParams.get('v');
|
|
||||||
return;
|
|
||||||
} else if (url.hostname == 'youtu.be') {
|
|
||||||
this.youtubeId = url.pathname;
|
|
||||||
return;
|
|
||||||
} else if (url.hostname == 'open.spotify.com') {
|
|
||||||
this.spotifyId = url.pathname.split('/').reverse().filter(x => x !== '')[0];
|
|
||||||
return;
|
|
||||||
} else if (['nicovideo.jp', 'www.nicovideo.jp', 'nico.ms'].includes(url.hostname)) {
|
|
||||||
const id = url.pathname.split('/').reverse().filter(x => x !== '')[0];
|
|
||||||
if (['sm', 'nm', 'ax', 'ca', 'cd', 'cw', 'fx', 'ig', 'na', 'om', 'sd', 'sk', 'yk', 'yo', 'za', 'zb', 'zc', 'zd', 'ze', 'nl', 'so', ...Array(10).keys()].some(x => id.startsWith(x)) {
|
|
||||||
this.nicovideoId = id;
|
|
||||||
this.position = url.searchParams.get('from');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (this.detail && url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
|
|
||||||
this.tweetUrl = url;
|
this.tweetUrl = url;
|
||||||
const twttr = (window as any).twttr || {};
|
const twttr = (window as any).twttr || {};
|
||||||
const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
|
const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
|
||||||
@ -104,16 +77,95 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
|
fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
|
||||||
res.json().then(info => {
|
res.json().then(info => {
|
||||||
this.title = info.title;
|
if (info.url != null) {
|
||||||
this.description = info.description;
|
this.title = info.title;
|
||||||
this.thumbnail = info.thumbnail;
|
this.description = info.description;
|
||||||
this.icon = info.icon;
|
this.thumbnail = info.thumbnail;
|
||||||
this.sitename = info.sitename;
|
this.icon = info.icon;
|
||||||
|
this.sitename = info.sitename;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
if ([ // THIS IS THE WHITELIST FOR THE EMBED PLAYER
|
||||||
});
|
'afreecatv.com',
|
||||||
}
|
'aparat.com',
|
||||||
|
'applemusic.com',
|
||||||
|
'amazon.com',
|
||||||
|
'awa.fm',
|
||||||
|
'bandcamp.com',
|
||||||
|
'bbc.co.uk',
|
||||||
|
'beatport.com',
|
||||||
|
'bilibili.com',
|
||||||
|
'boomstream.com',
|
||||||
|
'breakers.tv',
|
||||||
|
'cam4.com',
|
||||||
|
'cavelis.net',
|
||||||
|
'chaturbate.com',
|
||||||
|
'cnn.com',
|
||||||
|
'cybergame.tv',
|
||||||
|
'dailymotion.com',
|
||||||
|
'deezer.com',
|
||||||
|
'djlive.pl',
|
||||||
|
'e-onkyo.com',
|
||||||
|
'eventials.com',
|
||||||
|
'facebook.com',
|
||||||
|
'fc2.com',
|
||||||
|
'gameplank.tv',
|
||||||
|
'goodgame.ru',
|
||||||
|
'google.com',
|
||||||
|
'hardtunes.com',
|
||||||
|
'instagram.com',
|
||||||
|
'johnnylooch.com',
|
||||||
|
'kexp.org',
|
||||||
|
'lahzenegar.com',
|
||||||
|
'liveedu.tv',
|
||||||
|
'livetube.cc',
|
||||||
|
'livestream.com',
|
||||||
|
'meridix.com',
|
||||||
|
'mixcloud.com',
|
||||||
|
'mixer.com',
|
||||||
|
'mobcrush.com',
|
||||||
|
'mylive.in.th',
|
||||||
|
'myspace.com',
|
||||||
|
'netflix.com',
|
||||||
|
'newretrowave.com',
|
||||||
|
'nhk.or.jp',
|
||||||
|
'nicovideo.jp',
|
||||||
|
'nico.ms',
|
||||||
|
'noisetrade.com',
|
||||||
|
'nood.tv',
|
||||||
|
'npr.org',
|
||||||
|
'openrec.tv',
|
||||||
|
'pandora.com',
|
||||||
|
'pandora.tv',
|
||||||
|
'picarto.tv',
|
||||||
|
'pscp.tv',
|
||||||
|
'restream.io',
|
||||||
|
'reverbnation.com',
|
||||||
|
'sermonaudio.com',
|
||||||
|
'smashcast.tv',
|
||||||
|
'songkick.com',
|
||||||
|
'soundcloud.com',
|
||||||
|
'spinninrecords.com',
|
||||||
|
'spotify.com',
|
||||||
|
'stitcher.com',
|
||||||
|
'stream.me',
|
||||||
|
'switchboard.live',
|
||||||
|
'tunein.com',
|
||||||
|
'twitcasting.tv',
|
||||||
|
'twitch.tv',
|
||||||
|
'twitter.com',
|
||||||
|
'vaughnlive.tv',
|
||||||
|
'veoh.com',
|
||||||
|
'vimeo.com',
|
||||||
|
'watchpeoplecode.com',
|
||||||
|
'web.tv',
|
||||||
|
'youtube.com',
|
||||||
|
'youtu.be'
|
||||||
|
].some(x => x == url.hostname || url.hostname.endsWith(`.${x}`)))
|
||||||
|
this.player = info.player;
|
||||||
|
} // info.url
|
||||||
|
}) // json
|
||||||
|
}); // fetch
|
||||||
|
} // created
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ declare const _CODENAME_: string;
|
|||||||
declare const _LICENSE_: string;
|
declare const _LICENSE_: string;
|
||||||
declare const _GOOGLE_MAPS_API_KEY_: string;
|
declare const _GOOGLE_MAPS_API_KEY_: string;
|
||||||
declare const _WELCOME_BG_URL_: string;
|
declare const _WELCOME_BG_URL_: string;
|
||||||
|
declare const _TWITTER_INTEGRATION_: boolean;
|
||||||
|
|
||||||
export const host = _HOST_;
|
export const host = _HOST_;
|
||||||
export const hostname = _HOSTNAME_;
|
export const hostname = _HOSTNAME_;
|
||||||
@ -47,3 +48,4 @@ export const codename = _CODENAME_;
|
|||||||
export const license = _LICENSE_;
|
export const license = _LICENSE_;
|
||||||
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
|
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
|
||||||
export const welcomeBgUrl = _WELCOME_BG_URL_;
|
export const welcomeBgUrl = _WELCOME_BG_URL_;
|
||||||
|
export const twitterIntegration = _TWITTER_INTEGRATION_;
|
||||||
|
@ -24,6 +24,7 @@ import updateBanner from './api/update-banner';
|
|||||||
|
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkIndex from './views/pages/index.vue';
|
||||||
import MkDeck from './views/pages/deck/deck.vue';
|
import MkDeck from './views/pages/deck/deck.vue';
|
||||||
|
import MkAdmin from './views/pages/admin/admin.vue';
|
||||||
import MkUser from './views/pages/user/user.vue';
|
import MkUser from './views/pages/user/user.vue';
|
||||||
import MkFavorites from './views/pages/favorites.vue';
|
import MkFavorites from './views/pages/favorites.vue';
|
||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||||
@ -55,6 +56,7 @@ init(async (launch) => {
|
|||||||
routes: [
|
routes: [
|
||||||
{ path: '/', name: 'index', component: MkIndex },
|
{ path: '/', name: 'index', component: MkIndex },
|
||||||
{ path: '/deck', name: 'deck', component: MkDeck },
|
{ path: '/deck', name: 'deck', component: MkDeck },
|
||||||
|
{ path: '/admin', name: 'admin', component: MkAdmin },
|
||||||
{ path: '/i/customize-home', component: MkHomeCustomize },
|
{ path: '/i/customize-home', component: MkHomeCustomize },
|
||||||
{ path: '/i/favorites', component: MkFavorites },
|
{ path: '/i/favorites', component: MkFavorites },
|
||||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<p>%i18n:@banner%</p>
|
<p>%i18n:@banner%</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
||||||
<img :src="file.url" alt="" @load="onThumbnailLoaded"/>
|
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
|
||||||
</div>
|
</div>
|
||||||
<p class="name">
|
<p class="name">
|
||||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||||
@ -99,7 +99,7 @@ export default Vue.extend({
|
|||||||
text: '%i18n:@contextmenu.set-as-banner%',
|
text: '%i18n:@contextmenu.set-as-banner%',
|
||||||
action: this.setAsBanner
|
action: this.setAsBanner
|
||||||
}]
|
}]
|
||||||
}, {
|
}, /*{
|
||||||
type: 'nest',
|
type: 'nest',
|
||||||
text: '%i18n:@contextmenu.open-in-app%',
|
text: '%i18n:@contextmenu.open-in-app%',
|
||||||
menu: [{
|
menu: [{
|
||||||
@ -107,11 +107,11 @@ export default Vue.extend({
|
|||||||
text: '%i18n:@contextmenu.add-app%...',
|
text: '%i18n:@contextmenu.add-app%...',
|
||||||
action: this.addApp
|
action: this.addApp
|
||||||
}]
|
}]
|
||||||
}], {
|
}*/], {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
this.isContextmenuShowing = false;
|
this.isContextmenuShowing = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragstart(e) {
|
onDragstart(e) {
|
||||||
|
@ -67,16 +67,16 @@ export default Vue.extend({
|
|||||||
text: '%i18n:@contextmenu.rename%',
|
text: '%i18n:@contextmenu.rename%',
|
||||||
icon: '%fa:i-cursor%',
|
icon: '%fa:i-cursor%',
|
||||||
action: this.rename
|
action: this.rename
|
||||||
}, null, {
|
}/*, null, {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
text: '%i18n:common.delete%',
|
text: '%i18n:common.delete%',
|
||||||
icon: '%fa:R trash-alt%',
|
icon: '%fa:R trash-alt%',
|
||||||
action: this.deleteFolder
|
action: this.deleteFolder
|
||||||
}], {
|
}*/], {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
this.isContextmenuShowing = false;
|
this.isContextmenuShowing = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseover() {
|
onMouseover() {
|
||||||
|
@ -567,6 +567,7 @@ export default Vue.extend({
|
|||||||
// ファイル一覧取得
|
// ファイル一覧取得
|
||||||
(this as any).api('drive/files', {
|
(this as any).api('drive/files', {
|
||||||
folderId: this.folder ? this.folder.id : null,
|
folderId: this.folder ? this.folder.id : null,
|
||||||
|
untilId: this.files[this.files.length - 1].id,
|
||||||
limit: max + 1
|
limit: max + 1
|
||||||
}).then(files => {
|
}).then(files => {
|
||||||
if (files.length == max + 1) {
|
if (files.length == max + 1) {
|
||||||
|
@ -37,7 +37,7 @@ export default Vue.extend({
|
|||||||
style(): any {
|
style(): any {
|
||||||
return {
|
return {
|
||||||
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
|
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
|
||||||
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})`
|
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.thumbnailUrl})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -155,10 +155,15 @@ root(isDark)
|
|||||||
max-width 1300px
|
max-width 1300px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
|
||||||
|
> *
|
||||||
|
position absolute
|
||||||
|
height 48px
|
||||||
|
|
||||||
> .center
|
> .center
|
||||||
margin auto
|
right 0
|
||||||
|
|
||||||
> .icon
|
> .icon
|
||||||
|
margin auto
|
||||||
display block
|
display block
|
||||||
width 48px
|
width 48px
|
||||||
height 48px
|
height 48px
|
||||||
@ -169,11 +174,12 @@ root(isDark)
|
|||||||
opacity 0.3
|
opacity 0.3
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
> .left
|
> .left,
|
||||||
height 48px
|
> .center
|
||||||
|
left 0
|
||||||
|
|
||||||
> .right
|
> .right
|
||||||
height 48px
|
right 0
|
||||||
|
|
||||||
> *
|
> *
|
||||||
display inline-block
|
display inline-block
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
<div class="description">{{ u.description }}</div>
|
<div class="description">{{ u.description }}</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<div>
|
<div>
|
||||||
<p>%i18n:@notes%</p><a>{{ u.notesCount }}</a>
|
<p>%i18n:@notes%</p><span>{{ u.notesCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>%i18n:@following%</p><a>{{ u.followingCount }}</a>
|
<p>%i18n:@following%</p><span>{{ u.followingCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
|
<p>%i18n:@followers%</p><span>{{ u.followersCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/>
|
<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/>
|
||||||
@ -149,7 +149,7 @@ root(isDark)
|
|||||||
font-size 0.7em
|
font-size 0.7em
|
||||||
color #aaa
|
color #aaa
|
||||||
|
|
||||||
> a
|
> span
|
||||||
font-size 1em
|
font-size 1em
|
||||||
color $theme-color
|
color $theme-color
|
||||||
|
|
||||||
|
37
src/client/app/desktop/views/pages/admin/admin.dashboard.vue
Normal file
37
src/client/app/desktop/views/pages/admin/admin.dashboard.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>%i18n:@dashboard%</h1>
|
||||||
|
<div v-if="stats">
|
||||||
|
<p><b>%i18n:@all-users%</b>: <span>{{ stats.usersCount | number }}</span></p>
|
||||||
|
<p><b>%i18n:@original-users%</b>: <span>{{ stats.originalUsersCount | number }}</span></p>
|
||||||
|
<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p>
|
||||||
|
<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stats: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
(this as any).api('stats').then(stats => {
|
||||||
|
this.stats = stats;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
h1
|
||||||
|
margin 0 0 1em 0
|
||||||
|
padding 0 0 8px 0
|
||||||
|
font-size 1em
|
||||||
|
color #555
|
||||||
|
border-bottom solid 1px #eee
|
||||||
|
</style>
|
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<header>%i18n:@suspend-user%</header>
|
||||||
|
<input v-model="username" type="text" class="ui"/>
|
||||||
|
<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import parseAcct from "../../../../../../misc/acct/parse";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: null,
|
||||||
|
suspending: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async suspendUser() {
|
||||||
|
this.suspending = true;
|
||||||
|
|
||||||
|
const user = await (this as any).os.api(
|
||||||
|
"users/show",
|
||||||
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
|
await (this as any).os.api("admin/suspend-user", {
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
this.suspending = false;
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
header
|
||||||
|
margin 10px 0
|
||||||
|
|
||||||
|
|
||||||
|
button
|
||||||
|
margin 16px 0
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<header>%i18n:@unsuspend-user%</header>
|
||||||
|
<input v-model="username" type="text" class="ui"/>
|
||||||
|
<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import parseAcct from "../../../../../../misc/acct/parse";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: null,
|
||||||
|
unsuspending: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async unsuspendUser() {
|
||||||
|
this.unsuspending = true;
|
||||||
|
|
||||||
|
const user = await (this as any).os.api(
|
||||||
|
"users/show",
|
||||||
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
|
await (this as any).os.api("admin/unsuspend-user", {
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
this.unsuspending = false;
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
header
|
||||||
|
margin 10px 0
|
||||||
|
|
||||||
|
|
||||||
|
button
|
||||||
|
margin 16px 0
|
||||||
|
|
||||||
|
</style>
|
102
src/client/app/desktop/views/pages/admin/admin.vue
Normal file
102
src/client/app/desktop/views/pages/admin/admin.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mk-admin">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
|
||||||
|
<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
||||||
|
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
|
||||||
|
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<div v-if="page == 'dashboard'">
|
||||||
|
<x-dashboard/>
|
||||||
|
</div>
|
||||||
|
<div v-if="page == 'users'">
|
||||||
|
<x-suspend-user/>
|
||||||
|
<x-unsuspend-user/>
|
||||||
|
</div>
|
||||||
|
<div v-if="page == 'drive'"></div>
|
||||||
|
<div v-if="page == 'update'"></div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import XDashboard from "./admin.dashboard.vue";
|
||||||
|
import XSuspendUser from "./admin.suspend-user.vue";
|
||||||
|
import XUnsuspendUser from "./admin.unsuspend-user.vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XDashboard,
|
||||||
|
XSuspendUser,
|
||||||
|
XUnsuspendUser
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 'dashboard'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
nav(page: string) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
.mk-admin
|
||||||
|
display flex
|
||||||
|
height 100%
|
||||||
|
margin 32px
|
||||||
|
|
||||||
|
> nav
|
||||||
|
flex 0 0 250px
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
padding 16px 0 0 0
|
||||||
|
overflow auto
|
||||||
|
border-right solid 1px #ddd
|
||||||
|
|
||||||
|
> ul
|
||||||
|
list-style none
|
||||||
|
|
||||||
|
> li
|
||||||
|
display block
|
||||||
|
padding 10px 16px
|
||||||
|
margin 0
|
||||||
|
color #666
|
||||||
|
cursor pointer
|
||||||
|
user-select none
|
||||||
|
transition margin-left 0.2s ease
|
||||||
|
|
||||||
|
> [data-fa]
|
||||||
|
margin-right 4px
|
||||||
|
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color #555
|
||||||
|
|
||||||
|
&.active
|
||||||
|
margin-left 8px
|
||||||
|
color $theme-color !important
|
||||||
|
|
||||||
|
> main
|
||||||
|
width 100%
|
||||||
|
padding 16px 32px
|
||||||
|
|
||||||
|
header
|
||||||
|
margin 10px 0
|
||||||
|
|
||||||
|
|
||||||
|
button
|
||||||
|
margin 16px 0
|
||||||
|
position absolute
|
||||||
|
right 0
|
||||||
|
|
||||||
|
</style>
|
@ -176,6 +176,10 @@ root(isDark)
|
|||||||
height 120px
|
height 120px
|
||||||
box-shadow 1px 1px 3px rgba(#000, 0.2)
|
box-shadow 1px 1px 3px rgba(#000, 0.2)
|
||||||
|
|
||||||
|
> &.cat::before,
|
||||||
|
> &.cat::after
|
||||||
|
border-width 8px
|
||||||
|
|
||||||
> .body
|
> .body
|
||||||
padding 16px 16px 16px 154px
|
padding 16px 16px 16px 154px
|
||||||
color isDark ? #c5ced6 : #555
|
color isDark ? #c5ced6 : #555
|
||||||
|
@ -43,7 +43,7 @@ export default Vue.extend({
|
|||||||
thumbnail(): any {
|
thumbnail(): any {
|
||||||
return {
|
return {
|
||||||
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
||||||
'background-image': `url(${this.file.url})`
|
'background-image': `url(${this.file.thumbnailUrl})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -27,7 +27,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style(): any {
|
style(): any {
|
||||||
let url = `url(${this.image.url})`;
|
let url = `url(${this.image.thumbnailUrl})`;
|
||||||
|
|
||||||
if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
|
if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
|
||||||
url = null;
|
url = null;
|
||||||
|
BIN
src/client/assets/reactions/rip.png
Normal file
BIN
src/client/assets/reactions/rip.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"copyright": "Copyright (c) 2014-2018 syuilo",
|
"copyright": "Copyright (c) 2014-2018 syuilo",
|
||||||
"themeColor": "#f66e4f",
|
"themeColor": "#f6584f",
|
||||||
"themeColorForeground": "#fff"
|
"themeColorForeground": "#fff"
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
|
import * as Deque from 'double-ended-queue';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const log: any[] = [];
|
const log = new Deque<any>();
|
||||||
|
|
||||||
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
|
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ export default function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ev.on('requestNotesStatsLog', id => {
|
ev.on('requestNotesStatsLog', id => {
|
||||||
ev.emit('notesStatsLog:' + id, log);
|
ev.emit('notesStatsLog:' + id, log.toArray());
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', code => {
|
process.on('exit', code => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as sysUtils from 'systeminformation';
|
import * as sysUtils from 'systeminformation';
|
||||||
import * as diskusage from 'diskusage';
|
import * as diskusage from 'diskusage';
|
||||||
|
import * as Deque from 'double-ended-queue';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
const osUtils = require('os-utils');
|
const osUtils = require('os-utils');
|
||||||
|
|
||||||
@ -12,10 +13,10 @@ const interval = 1000;
|
|||||||
* Report server stats regularly
|
* Report server stats regularly
|
||||||
*/
|
*/
|
||||||
export default function() {
|
export default function() {
|
||||||
const log: any[] = [];
|
const log = new Deque<any>();
|
||||||
|
|
||||||
ev.on('requestServerStatsLog', id => {
|
ev.on('requestServerStatsLog', id => {
|
||||||
ev.emit('serverStatsLog:' + id, log);
|
ev.emit('serverStatsLog:' + id, log.toArray());
|
||||||
});
|
});
|
||||||
|
|
||||||
async function tick() {
|
async function tick() {
|
||||||
|
@ -3,9 +3,7 @@ import config from '../config';
|
|||||||
const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null;
|
const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null;
|
||||||
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
|
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
|
||||||
|
|
||||||
const uri = u && p
|
const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
|
||||||
? `mongodb://${u}:${p}@${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`
|
|
||||||
: `mongodb://${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* monk
|
* monk
|
||||||
|
11
src/index.ts
11
src/index.ts
@ -48,7 +48,7 @@ main();
|
|||||||
* Init process
|
* Init process
|
||||||
*/
|
*/
|
||||||
function main() {
|
function main() {
|
||||||
process.title = `Misskey (${ cluster.isMaster ? 'master' : 'worker' })`;
|
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
|
||||||
|
|
||||||
if (cluster.isMaster || program.disableClustering) {
|
if (cluster.isMaster || program.disableClustering) {
|
||||||
masterMain();
|
masterMain();
|
||||||
@ -154,11 +154,10 @@ async function init(): Promise<Config> {
|
|||||||
|
|
||||||
function checkMongoDb(config: Config) {
|
function checkMongoDb(config: Config) {
|
||||||
const mongoDBLogger = new Logger('MongoDB');
|
const mongoDBLogger = new Logger('MongoDB');
|
||||||
mongoDBLogger.info(`Host: ${config.mongodb.host}`);
|
const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null;
|
||||||
mongoDBLogger.info(`Port: ${config.mongodb.port}`);
|
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
|
||||||
mongoDBLogger.info(`DB: ${config.mongodb.db}`);
|
const uri = `mongodb://${u && p ? `${u}:****@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
|
||||||
if (config.mongodb.user) mongoDBLogger.info(`User: ${config.mongodb.user}`);
|
mongoDBLogger.info(`Connecting to ${uri}`);
|
||||||
if (config.mongodb.pass) mongoDBLogger.info(`Pass: ****`);
|
|
||||||
require('./db/mongodb');
|
require('./db/mongodb');
|
||||||
mongoDBLogger.succ('Connectivity confirmed');
|
mongoDBLogger.succ('Connectivity confirmed');
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,6 @@ export default function(text: string) {
|
|||||||
return {
|
return {
|
||||||
type: 'bold',
|
type: 'bold',
|
||||||
content: bold,
|
content: bold,
|
||||||
bold: bold.substr(2, bold.length - 4)
|
bold: match[1]
|
||||||
} as TextElementBold;
|
} as TextElementBold;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export default function(text: string) {
|
|||||||
return {
|
return {
|
||||||
type: 'code',
|
type: 'code',
|
||||||
content: code,
|
content: code,
|
||||||
code: code.substr(3, code.length - 6).trim(),
|
code: match[1],
|
||||||
html: genHtml(code.substr(3, code.length - 6).trim())
|
html: genHtml(match[1].trim())
|
||||||
} as TextElementCode;
|
} as TextElementCode;
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,12 @@ export type TextElementEmoji = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function(text: string) {
|
export default function(text: string) {
|
||||||
const match = text.match(/^:[a-zA-Z0-9+-_]+:/);
|
const match = text.match(/^:([a-zA-Z0-9+-_]+):/);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
const emoji = match[0];
|
const emoji = match[0];
|
||||||
return {
|
return {
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
content: emoji,
|
content: emoji,
|
||||||
emoji: emoji.substr(1, emoji.length - 2)
|
emoji: match[1]
|
||||||
} as TextElementEmoji;
|
} as TextElementEmoji;
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,12 @@ export type TextElementInlineCode = {
|
|||||||
export default function(text: string) {
|
export default function(text: string) {
|
||||||
const match = text.match(/^`(.+?)`/);
|
const match = text.match(/^`(.+?)`/);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
|
if (match[1].includes('´')) return null;
|
||||||
const code = match[0];
|
const code = match[0];
|
||||||
return {
|
return {
|
||||||
type: 'inline-code',
|
type: 'inline-code',
|
||||||
content: code,
|
content: code,
|
||||||
code: code.substr(1, code.length - 2).trim(),
|
code: match[1],
|
||||||
html: genHtml(code.substr(1, code.length - 2).trim())
|
html: genHtml(match[1])
|
||||||
} as TextElementInlineCode;
|
} as TextElementInlineCode;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,6 @@ export default function(text: string) {
|
|||||||
return {
|
return {
|
||||||
type: 'quote',
|
type: 'quote',
|
||||||
content: quote,
|
content: quote,
|
||||||
quote: quote.substr(1, quote.length - 2).trim(),
|
quote: match[1].trim(),
|
||||||
} as TextElementQuote;
|
} as TextElementQuote;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export type TextElementSearch = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function(text: string) {
|
export default function(text: string) {
|
||||||
const match = text.match(/^(.+?) (検索|Search)(\n|$)/i);
|
const match = text.match(/^(.+?)( | )(検索|\[検索\]|Search|\[Search\])(\n|$)/i);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
return {
|
return {
|
||||||
type: 'search',
|
type: 'search',
|
||||||
|
@ -8,6 +8,7 @@ export default function(reaction: string): string {
|
|||||||
case 'congrats': return '🎉';
|
case 'congrats': return '🎉';
|
||||||
case 'angry': return '💢';
|
case 'angry': return '💢';
|
||||||
case 'confused': return '😥';
|
case 'confused': return '😥';
|
||||||
|
case 'rip': return '😇';
|
||||||
case 'pudding': return '🍮';
|
case 'pudding': return '🍮';
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export default App;
|
|||||||
export type IApp = {
|
export type IApp = {
|
||||||
_id: mongo.ObjectID;
|
_id: mongo.ObjectID;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
userId: mongo.ObjectID;
|
userId: mongo.ObjectID | null;
|
||||||
secret: string;
|
secret: string;
|
||||||
name: string;
|
name: string;
|
||||||
nameId: string;
|
nameId: string;
|
||||||
|
@ -10,7 +10,7 @@ import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumb
|
|||||||
|
|
||||||
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
||||||
DriveFile.createIndex('md5');
|
DriveFile.createIndex('md5');
|
||||||
DriveFile.createIndex(['metadata.uri', 'metadata.userId'], { sparse: true, unique: true });
|
DriveFile.createIndex('metadata.uri');
|
||||||
export default DriveFile;
|
export default DriveFile;
|
||||||
|
|
||||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
||||||
@ -31,6 +31,7 @@ export type IMetadata = {
|
|||||||
comment: string;
|
comment: string;
|
||||||
uri?: string;
|
uri?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
thumbnailUrl?: string;
|
||||||
src?: string;
|
src?: string;
|
||||||
deletedAt?: Date;
|
deletedAt?: Date;
|
||||||
withoutChunks?: boolean;
|
withoutChunks?: boolean;
|
||||||
@ -164,6 +165,7 @@ export const pack = (
|
|||||||
_target = Object.assign(_target, _file.metadata);
|
_target = Object.assign(_target, _file.metadata);
|
||||||
|
|
||||||
_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
|
_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
|
||||||
|
_target.thumbnailUrl = _file.metadata.thumbnailUrl ? _file.metadata.thumbnailUrl : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}?thumbnail`;
|
||||||
_target.isRemote = _file.metadata.isRemote;
|
_target.isRemote = _file.metadata.isRemote;
|
||||||
|
|
||||||
if (_target.properties == null) _target.properties = {};
|
if (_target.properties == null) _target.properties = {};
|
||||||
|
@ -26,6 +26,7 @@ export const validateReaction = $.str.or([
|
|||||||
'congrats',
|
'congrats',
|
||||||
'angry',
|
'angry',
|
||||||
'confused',
|
'confused',
|
||||||
|
'rip',
|
||||||
'pudding'
|
'pudding'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ export const pack = async (
|
|||||||
_note = await rap(_note);
|
_note = await rap(_note);
|
||||||
|
|
||||||
if (_note.user.isCat && _note.text) {
|
if (_note.user.isCat && _note.text) {
|
||||||
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ');
|
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide) {
|
if (hide) {
|
||||||
|
@ -69,12 +69,13 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||||
if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||||
visibility = 'home';
|
visibility = 'home';
|
||||||
|
} else if (note.to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき?
|
||||||
|
visibility = 'followers';
|
||||||
} else {
|
} else {
|
||||||
visibility = 'specified';
|
visibility = 'specified';
|
||||||
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
|
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (note.cc.length == 0) visibility = 'followers';
|
|
||||||
//#endergion
|
//#endergion
|
||||||
|
|
||||||
// 添付メディア
|
// 添付メディア
|
||||||
|
@ -4,5 +4,5 @@ import { IDriveFile } from '../../../models/drive-file';
|
|||||||
export default (file: IDriveFile) => ({
|
export default (file: IDriveFile) => ({
|
||||||
type: 'Document',
|
type: 'Document',
|
||||||
mediaType: file.contentType,
|
mediaType: file.contentType,
|
||||||
url: `${config.drive_url}/${file._id}`
|
url: file.metadata.url || `${config.drive_url}/${file._id}`
|
||||||
});
|
});
|
||||||
|
16
src/remote/activitypub/renderer/follow-user.ts
Normal file
16
src/remote/activitypub/renderer/follow-user.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import config from '../../../config';
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import User, { isLocalUser } from '../../../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert (local|remote)(Follower|Followee)ID to URL
|
||||||
|
* @param id Follower|Followee ID
|
||||||
|
*/
|
||||||
|
export default async function renderFollowUser(id: mongo.ObjectID): Promise<any> {
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
return isLocalUser(user) ? `${config.url}/users/${user._id}` : user.uri;
|
||||||
|
}
|
@ -3,6 +3,6 @@ import { IDriveFile } from '../../../models/drive-file';
|
|||||||
|
|
||||||
export default (file: IDriveFile) => ({
|
export default (file: IDriveFile) => ({
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
url: `${config.drive_url}/${file._id}`,
|
url: file.metadata.url || `${config.drive_url}/${file._id}`,
|
||||||
sensitive: file.metadata.isSensitive
|
sensitive: file.metadata.isSensitive
|
||||||
});
|
});
|
||||||
|
@ -50,9 +50,21 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||||||
? note.mentionedRemoteUsers.map(x => x.uri)
|
? note.mentionedRemoteUsers.map(x => x.uri)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const cc = ['public', 'home', 'followers'].includes(note.visibility)
|
let to: string[] = [];
|
||||||
? [`${attributedTo}/followers`].concat(mentions)
|
let cc: string[] = [];
|
||||||
: [];
|
|
||||||
|
if (note.visibility == 'public') {
|
||||||
|
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||||
|
cc = [`${attributedTo}/followers`].concat(mentions);
|
||||||
|
} else if (note.visibility == 'home') {
|
||||||
|
to = [`${attributedTo}/followers`];
|
||||||
|
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
|
||||||
|
} else if (note.visibility == 'followers') {
|
||||||
|
to = [`${attributedTo}/followers`];
|
||||||
|
cc = mentions;
|
||||||
|
} else {
|
||||||
|
to = mentions;
|
||||||
|
}
|
||||||
|
|
||||||
const mentionedUsers = note.mentions ? await User.find({
|
const mentionedUsers = note.mentions ? await User.find({
|
||||||
_id: {
|
_id: {
|
||||||
@ -74,7 +86,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||||||
summary: note.cw,
|
summary: note.cw,
|
||||||
content: toHtml(note),
|
content: toHtml(note),
|
||||||
published: note.createdAt.toISOString(),
|
published: note.createdAt.toISOString(),
|
||||||
to: 'https://www.w3.org/ns/activitystreams#Public',
|
to,
|
||||||
cc,
|
cc,
|
||||||
inReplyTo,
|
inReplyTo,
|
||||||
attachment: (await promisedFiles).map(renderDocument),
|
attachment: (await promisedFiles).map(renderDocument),
|
||||||
|
23
src/remote/activitypub/renderer/ordered-collection-page.ts
Normal file
23
src/remote/activitypub/renderer/ordered-collection-page.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Render OrderedCollectionPage
|
||||||
|
* @param id URL of self
|
||||||
|
* @param totalItems Number of total items
|
||||||
|
* @param orderedItems Items
|
||||||
|
* @param partOf URL of base
|
||||||
|
* @param prev URL of prev page (optional)
|
||||||
|
* @param next URL of next page (optional)
|
||||||
|
*/
|
||||||
|
export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) {
|
||||||
|
const page = {
|
||||||
|
id,
|
||||||
|
partOf,
|
||||||
|
type: 'OrderedCollectionPage',
|
||||||
|
totalItems,
|
||||||
|
orderedItems
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (prev) page.prev = prev;
|
||||||
|
if (next) page.next = next;
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
@ -1,6 +1,19 @@
|
|||||||
export default (id: string, totalItems: any, orderedItems: any) => ({
|
/**
|
||||||
id,
|
* Render OrderedCollection
|
||||||
type: 'OrderedCollection',
|
* @param id URL of self
|
||||||
totalItems,
|
* @param totalItems Total number of items
|
||||||
orderedItems
|
* @param first URL of first page (optional)
|
||||||
});
|
* @param last URL of last page (optional)
|
||||||
|
*/
|
||||||
|
export default function(id: string, totalItems: any, first: string, last: string) {
|
||||||
|
const page: any = {
|
||||||
|
id,
|
||||||
|
type: 'OrderedCollection',
|
||||||
|
totalItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (first) page.first = first;
|
||||||
|
if (last) page.last = last;
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
@ -19,6 +19,8 @@ export default async (user: ILocalUser) => {
|
|||||||
id,
|
id,
|
||||||
inbox: `${id}/inbox`,
|
inbox: `${id}/inbox`,
|
||||||
outbox: `${id}/outbox`,
|
outbox: `${id}/outbox`,
|
||||||
|
followers: `${id}/followers`,
|
||||||
|
following: `${id}/following`,
|
||||||
sharedInbox: `${config.url}/inbox`,
|
sharedInbox: `${config.url}/inbox`,
|
||||||
url: `${config.url}/@${user.username}`,
|
url: `${config.url}/@${user.username}`,
|
||||||
preferredUsername: user.username,
|
preferredUsername: user.username,
|
||||||
|
@ -10,8 +10,9 @@ import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
|
|||||||
import renderNote from '../remote/activitypub/renderer/note';
|
import renderNote from '../remote/activitypub/renderer/note';
|
||||||
import renderKey from '../remote/activitypub/renderer/key';
|
import renderKey from '../remote/activitypub/renderer/key';
|
||||||
import renderPerson from '../remote/activitypub/renderer/person';
|
import renderPerson from '../remote/activitypub/renderer/person';
|
||||||
import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection';
|
import Outbox from './activitypub/outbox';
|
||||||
import config from '../config';
|
import Followers from './activitypub/followers';
|
||||||
|
import Following from './activitypub/following';
|
||||||
|
|
||||||
// Init router
|
// Init router
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
@ -64,30 +65,14 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||||||
ctx.body = pack(await renderNote(note));
|
ctx.body = pack(await renderNote(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
// outbot
|
// outbox
|
||||||
router.get('/users/:user/outbox', async ctx => {
|
router.get('/users/:user/outbox', Outbox);
|
||||||
const userId = new mongo.ObjectID(ctx.params.user);
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
// followers
|
||||||
_id: userId,
|
router.get('/users/:user/followers', Followers);
|
||||||
host: null
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user === null) {
|
// following
|
||||||
ctx.status = 404;
|
router.get('/users/:user/following', Following);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notes = await Note.find({ userId: user._id }, {
|
|
||||||
limit: 10,
|
|
||||||
sort: { _id: -1 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
|
|
||||||
const rendered = renderOrderedCollection(`${config.url}/users/${userId}/inbox`, user.notesCount, renderedNotes);
|
|
||||||
|
|
||||||
ctx.body = pack(rendered);
|
|
||||||
});
|
|
||||||
|
|
||||||
// publickey
|
// publickey
|
||||||
router.get('/users/:user/publickey', async ctx => {
|
router.get('/users/:user/publickey', async ctx => {
|
||||||
|
80
src/server/activitypub/followers.ts
Normal file
80
src/server/activitypub/followers.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import * as Koa from 'koa';
|
||||||
|
import config from '../../config';
|
||||||
|
import $ from 'cafy'; import ID from '../../misc/cafy-id';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import Following from '../../models/following';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export default async (ctx: Koa.Context) => {
|
||||||
|
const userId = new mongo.ObjectID(ctx.params.user);
|
||||||
|
|
||||||
|
// Get 'cursor' parameter
|
||||||
|
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
|
||||||
|
|
||||||
|
// Get 'page' parameter
|
||||||
|
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
|
||||||
|
const page: boolean = ctx.request.query.page === 'true';
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (cursorErr || pageErr) {
|
||||||
|
ctx.status = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify user
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: userId,
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
ctx.status = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = 10;
|
||||||
|
const partOf = `${config.url}/users/${userId}/followers`;
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
// Construct query
|
||||||
|
const query = {
|
||||||
|
followeeId: user._id
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// カーソルが指定されている場合
|
||||||
|
if (cursor) {
|
||||||
|
query._id = {
|
||||||
|
$lt: cursor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get followers
|
||||||
|
const followings = await Following
|
||||||
|
.find(query, {
|
||||||
|
limit: limit + 1,
|
||||||
|
sort: { _id: -1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 「次のページ」があるかどうか
|
||||||
|
const inStock = followings.length === limit + 1;
|
||||||
|
if (inStock) followings.pop();
|
||||||
|
|
||||||
|
const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId)));
|
||||||
|
const rendered = renderOrderedCollectionPage(
|
||||||
|
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
|
||||||
|
user.followersCount, renderedFollowers, partOf,
|
||||||
|
null,
|
||||||
|
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
} else {
|
||||||
|
// index page
|
||||||
|
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
}
|
||||||
|
};
|
80
src/server/activitypub/following.ts
Normal file
80
src/server/activitypub/following.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import * as Koa from 'koa';
|
||||||
|
import config from '../../config';
|
||||||
|
import $ from 'cafy'; import ID from '../../misc/cafy-id';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import Following from '../../models/following';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export default async (ctx: Koa.Context) => {
|
||||||
|
const userId = new mongo.ObjectID(ctx.params.user);
|
||||||
|
|
||||||
|
// Get 'cursor' parameter
|
||||||
|
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
|
||||||
|
|
||||||
|
// Get 'page' parameter
|
||||||
|
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
|
||||||
|
const page: boolean = ctx.request.query.page === 'true';
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (cursorErr || pageErr) {
|
||||||
|
ctx.status = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify user
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: userId,
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
ctx.status = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = 10;
|
||||||
|
const partOf = `${config.url}/users/${userId}/following`;
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
// Construct query
|
||||||
|
const query = {
|
||||||
|
followerId: user._id
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// カーソルが指定されている場合
|
||||||
|
if (cursor) {
|
||||||
|
query._id = {
|
||||||
|
$lt: cursor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get followings
|
||||||
|
const followings = await Following
|
||||||
|
.find(query, {
|
||||||
|
limit: limit + 1,
|
||||||
|
sort: { _id: -1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 「次のページ」があるかどうか
|
||||||
|
const inStock = followings.length === limit + 1;
|
||||||
|
if (inStock) followings.pop();
|
||||||
|
|
||||||
|
const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId)));
|
||||||
|
const rendered = renderOrderedCollectionPage(
|
||||||
|
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
|
||||||
|
user.followingCount, renderedFollowees, partOf,
|
||||||
|
null,
|
||||||
|
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
} else {
|
||||||
|
// index page
|
||||||
|
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
}
|
||||||
|
};
|
103
src/server/activitypub/outbox.ts
Normal file
103
src/server/activitypub/outbox.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import * as Koa from 'koa';
|
||||||
|
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 Note from '../../models/note';
|
||||||
|
import renderNote from '../../remote/activitypub/renderer/note';
|
||||||
|
|
||||||
|
export default async (ctx: Koa.Context) => {
|
||||||
|
const userId = new mongo.ObjectID(ctx.params.user);
|
||||||
|
|
||||||
|
// Get 'sinceId' parameter
|
||||||
|
const [sinceId, sinceIdErr] = $.type(ID).optional.get(ctx.request.query.since_id);
|
||||||
|
|
||||||
|
// Get 'untilId' parameter
|
||||||
|
const [untilId, untilIdErr] = $.type(ID).optional.get(ctx.request.query.until_id);
|
||||||
|
|
||||||
|
// Get 'page' parameter
|
||||||
|
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
|
||||||
|
const page: boolean = ctx.request.query.page === 'true';
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) {
|
||||||
|
ctx.status = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify user
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: userId,
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
ctx.status = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = 20;
|
||||||
|
const partOf = `${config.url}/users/${userId}/outbox`;
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
//#region Construct query
|
||||||
|
const sort = {
|
||||||
|
_id: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
userId: user._id,
|
||||||
|
$and: [{
|
||||||
|
$or: [ { visibility: 'public' }, { visibility: 'home' } ]
|
||||||
|
}, { // exclude renote, but include quote
|
||||||
|
$or: [{
|
||||||
|
text: { $ne: null }
|
||||||
|
}, {
|
||||||
|
mediaIds: { $ne: [] }
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (sinceId) {
|
||||||
|
sort._id = 1;
|
||||||
|
query._id = {
|
||||||
|
$gt: sinceId
|
||||||
|
};
|
||||||
|
} else if (untilId) {
|
||||||
|
query._id = {
|
||||||
|
$lt: untilId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// Issue query
|
||||||
|
const notes = await Note
|
||||||
|
.find(query, {
|
||||||
|
limit: limit,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sinceId) notes.reverse();
|
||||||
|
|
||||||
|
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
|
||||||
|
const rendered = renderOrderedCollectionPage(
|
||||||
|
`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
|
||||||
|
user.notesCount, renderedNotes, partOf,
|
||||||
|
notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null,
|
||||||
|
notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
} else {
|
||||||
|
// index page
|
||||||
|
const rendered = renderOrderedCollection(partOf, user.notesCount,
|
||||||
|
`${partOf}?page=true`,
|
||||||
|
`${partOf}?page=true&since_id=000000000000000000000000`
|
||||||
|
);
|
||||||
|
ctx.body = pack(rendered);
|
||||||
|
}
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import limitter from './limitter';
|
import limitter from './limitter';
|
||||||
import { IUser } from '../../models/user';
|
import { IUser, isLocalUser } from '../../models/user';
|
||||||
import { IApp } from '../../models/app';
|
import { IApp } from '../../models/app';
|
||||||
import endpoints from './endpoints';
|
import endpoints from './endpoints';
|
||||||
|
|
||||||
@ -21,6 +21,10 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
|
|||||||
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
|
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
|
||||||
|
return rej('YOU_ARE_NOT_ADMIN');
|
||||||
|
}
|
||||||
|
|
||||||
if (app && ep.meta.kind) {
|
if (app && ep.meta.kind) {
|
||||||
if (!app.permission.some(p => p === ep.meta.kind)) {
|
if (!app.permission.some(p => p === ep.meta.kind)) {
|
||||||
return rej('PERMISSION_DENIED');
|
return rej('PERMISSION_DENIED');
|
||||||
@ -53,7 +57,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
|
|||||||
const time = after - before;
|
const time = after - before;
|
||||||
|
|
||||||
if (time > 1000) {
|
if (time > 1000) {
|
||||||
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`);
|
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rej(e);
|
rej(e);
|
||||||
|
@ -14,6 +14,11 @@ export interface IEndpointMeta {
|
|||||||
*/
|
*/
|
||||||
requireCredential?: boolean;
|
requireCredential?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理者のみ使えるエンドポイントか否か
|
||||||
|
*/
|
||||||
|
requireAdmin?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* エンドポイントのリミテーションに関するやつ
|
* エンドポイントのリミテーションに関するやつ
|
||||||
* 省略した場合はリミテーションは無いものとして解釈されます。
|
* 省略した場合はリミテーションは無いものとして解釈されます。
|
||||||
|
46
src/server/api/endpoints/admin/suspend-user.ts
Normal file
46
src/server/api/endpoints/admin/suspend-user.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import $ from 'cafy';
|
||||||
|
import ID from '../../../../misc/cafy-id';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
import User from '../../../../models/user';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
ja: '指定したユーザーを凍結します。',
|
||||||
|
en: 'Suspend a user.'
|
||||||
|
},
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireAdmin: true,
|
||||||
|
|
||||||
|
params: {
|
||||||
|
userId: $.type(ID).note({
|
||||||
|
desc: {
|
||||||
|
ja: '対象のユーザーID',
|
||||||
|
en: 'The user ID which you want to suspend'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (params: any) => new Promise(async (res, rej) => {
|
||||||
|
const [ps, psErr] = getParams(meta, params);
|
||||||
|
if (psErr) return rej(psErr);
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: ps.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return rej('user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await User.findOneAndUpdate({
|
||||||
|
_id: user._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
isSuspended: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res();
|
||||||
|
});
|
46
src/server/api/endpoints/admin/unsuspend-user.ts
Normal file
46
src/server/api/endpoints/admin/unsuspend-user.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import $ from 'cafy';
|
||||||
|
import ID from '../../../../misc/cafy-id';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
import User from '../../../../models/user';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
ja: '指定したユーザーの凍結を解除します。',
|
||||||
|
en: 'Unsuspend a user.'
|
||||||
|
},
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireAdmin: true,
|
||||||
|
|
||||||
|
params: {
|
||||||
|
userId: $.type(ID).note({
|
||||||
|
desc: {
|
||||||
|
ja: '対象のユーザーID',
|
||||||
|
en: 'The user ID which you want to unsuspend'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (params: any) => new Promise(async (res, rej) => {
|
||||||
|
const [ps, psErr] = getParams(meta, params);
|
||||||
|
if (psErr) return rej(psErr);
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: ps.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return rej('user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await User.findOneAndUpdate({
|
||||||
|
_id: user._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
isSuspended: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res();
|
||||||
|
});
|
@ -4,7 +4,7 @@ import App, { isValidNameId, pack } from '../../../../models/app';
|
|||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true
|
requireCredential: false
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +38,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
|
|||||||
// Create account
|
// Create account
|
||||||
const app = await App.insert({
|
const app = await App.insert({
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
userId: user._id,
|
userId: user && user._id,
|
||||||
name: name,
|
name: name,
|
||||||
nameId: nameId,
|
nameId: nameId,
|
||||||
nameIdLower: nameId.toLowerCase(),
|
nameIdLower: nameId.toLowerCase(),
|
||||||
|
@ -40,6 +40,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(history.map(async h =>
|
res(await Promise.all(history.map(h => pack(h.messageId, user))));
|
||||||
await pack(h.messageId, user))));
|
|
||||||
});
|
});
|
||||||
|
43
src/server/api/endpoints/messaging/messages/read.ts
Normal file
43
src/server/api/endpoints/messaging/messages/read.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
|
||||||
|
import Message from '../../../../../models/messaging-message';
|
||||||
|
import { ILocalUser } from '../../../../../models/user';
|
||||||
|
import read from '../../../common/read-messaging-message';
|
||||||
|
import getParams from '../../../get-params';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
ja: '指定した自分宛てのメッセージを既読にします。',
|
||||||
|
en: 'Mark as read a message of messaging.'
|
||||||
|
},
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'messaging-write',
|
||||||
|
|
||||||
|
params: {
|
||||||
|
messageId: $.type(ID).note({
|
||||||
|
desc: {
|
||||||
|
ja: '既読にするメッセージのID',
|
||||||
|
en: 'The ID of a message that you want to mark as read'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
|
const [ps, psErr] = getParams(meta, params);
|
||||||
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
|
const message = await Message.findOne({
|
||||||
|
_id: ps.messageId,
|
||||||
|
recipientId: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (message == null) {
|
||||||
|
return rej('message not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
read(user._id, message.userId, message);
|
||||||
|
|
||||||
|
res();
|
||||||
|
});
|
@ -8,7 +8,8 @@ import getParams from '../../get-params';
|
|||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
ja: 'タイムラインを取得します。'
|
ja: 'タイムラインを取得します。',
|
||||||
|
en: 'Get timeline of myself.'
|
||||||
},
|
},
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@ -67,9 +68,6 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get timeline of myself
|
|
||||||
*/
|
|
||||||
export default async (params: any, user: ILocalUser) => {
|
export default async (params: any, user: ILocalUser) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import * as Koa from 'koa';
|
import * as Koa from 'koa';
|
||||||
import * as send from 'koa-send';
|
import * as send from 'koa-send';
|
||||||
import * as mongodb from 'mongodb';
|
import * as mongodb from 'mongodb';
|
||||||
@ -51,23 +49,16 @@ export default async function(ctx: Koa.Context) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ('thumbnail' in ctx.query) {
|
if ('thumbnail' in ctx.query) {
|
||||||
// 画像以外
|
const thumb = await DriveFileThumbnail.findOne({
|
||||||
if (!file.contentType.startsWith('image/')) {
|
'metadata.originalId': fileId
|
||||||
const readable = fs.createReadStream(`${__dirname}/assets/thumbnail-not-available.png`);
|
});
|
||||||
ctx.set('Content-Type', 'image/png');
|
|
||||||
ctx.body = readable;
|
if (thumb != null) {
|
||||||
} else if (file.contentType == 'image/gif') {
|
ctx.set('Content-Type', 'image/jpeg');
|
||||||
// GIF
|
const bucket = await getDriveFileThumbnailBucket();
|
||||||
await sendRaw();
|
ctx.body = bucket.openDownloadStream(thumb._id);
|
||||||
} else {
|
} else {
|
||||||
const thumb = await DriveFileThumbnail.findOne({ 'metadata.originalId': fileId });
|
await sendRaw();
|
||||||
if (thumb != null) {
|
|
||||||
ctx.set('Content-Type', 'image/jpeg');
|
|
||||||
const bucket = await getDriveFileThumbnailBucket();
|
|
||||||
ctx.body = bucket.openDownloadStream(thumb._id);
|
|
||||||
} else {
|
|
||||||
await sendRaw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ('download' in ctx.query) {
|
if ('download' in ctx.query) {
|
||||||
|
@ -14,7 +14,9 @@ module.exports = async (ctx: Koa.Context) => {
|
|||||||
|
|
||||||
ctx.body = summary;
|
ctx.body = summary;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.status = 500;
|
ctx.status = 200;
|
||||||
|
ctx.set('Cache-Control', 'max-age=86400, immutable');
|
||||||
|
ctx.body = '{}';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as stream from 'stream';
|
|
||||||
|
|
||||||
import * as mongodb from 'mongodb';
|
import * as mongodb from 'mongodb';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
@ -17,30 +16,52 @@ import { publishUserStream, publishDriveStream } from '../../stream';
|
|||||||
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
||||||
import delFile from './delete-file';
|
import delFile from './delete-file';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
|
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||||
|
|
||||||
const log = debug('misskey:drive:add-file');
|
const log = debug('misskey:drive:add-file');
|
||||||
|
|
||||||
async function save(readable: stream.Readable, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
|
async function save(path: string, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
|
||||||
|
let thumbnail: Buffer;
|
||||||
|
|
||||||
|
if (['image/jpeg', 'image/png', 'image/webp'].includes(type)) {
|
||||||
|
thumbnail = await sharp(path)
|
||||||
|
.resize(300)
|
||||||
|
.jpeg({
|
||||||
|
quality: 50,
|
||||||
|
progressive: true
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
if (config.drive && config.drive.storage == 'minio') {
|
if (config.drive && config.drive.storage == 'minio') {
|
||||||
const minio = new Minio.Client(config.drive.config);
|
const minio = new Minio.Client(config.drive.config);
|
||||||
const id = uuid.v4();
|
const id = uuid.v4();
|
||||||
const obj = `${config.drive.prefix}/${id}`;
|
const obj = `${config.drive.prefix}/${id}`;
|
||||||
|
const thumbnailObj = `${obj}-thumbnail`;
|
||||||
|
|
||||||
const baseUrl = config.drive.baseUrl
|
const baseUrl = config.drive.baseUrl
|
||||||
|| `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`;
|
|| `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`;
|
||||||
|
|
||||||
await minio.putObject(config.drive.bucket, obj, readable, size, {
|
await minio.putObject(config.drive.bucket, obj, fs.createReadStream(path), size, {
|
||||||
'Content-Type': type,
|
'Content-Type': type,
|
||||||
'Cache-Control': 'max-age=31536000, immutable'
|
'Cache-Control': 'max-age=31536000, immutable'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (thumbnail) {
|
||||||
|
await minio.putObject(config.drive.bucket, thumbnailObj, fs.createReadStream(path), size, {
|
||||||
|
'Content-Type': 'image/jpeg',
|
||||||
|
'Cache-Control': 'max-age=31536000, immutable'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(metadata, {
|
Object.assign(metadata, {
|
||||||
withoutChunks: true,
|
withoutChunks: true,
|
||||||
storage: 'minio',
|
storage: 'minio',
|
||||||
storageProps: {
|
storageProps: {
|
||||||
id: id
|
id: id
|
||||||
},
|
},
|
||||||
url: `${ baseUrl }/${ obj }`
|
url: `${ baseUrl }/${ obj }`,
|
||||||
|
thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailObj }` : null
|
||||||
});
|
});
|
||||||
|
|
||||||
const file = await DriveFile.insert({
|
const file = await DriveFile.insert({
|
||||||
@ -57,12 +78,36 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
|
|||||||
// Get MongoDB GridFS bucket
|
// Get MongoDB GridFS bucket
|
||||||
const bucket = await getDriveFileBucket();
|
const bucket = await getDriveFileBucket();
|
||||||
|
|
||||||
return new Promise<IDriveFile>((resolve, reject) => {
|
const file = await new Promise<IDriveFile>((resolve, reject) => {
|
||||||
const writeStream = bucket.openUploadStream(name, { contentType: type, metadata });
|
const writeStream = bucket.openUploadStream(name, {
|
||||||
|
contentType: type,
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
|
||||||
writeStream.once('finish', resolve);
|
writeStream.once('finish', resolve);
|
||||||
writeStream.on('error', reject);
|
writeStream.on('error', reject);
|
||||||
readable.pipe(writeStream);
|
|
||||||
|
fs.createReadStream(path).pipe(writeStream);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (thumbnail) {
|
||||||
|
const thumbnailBucket = await getDriveFileThumbnailBucket();
|
||||||
|
|
||||||
|
await new Promise<IDriveFile>((resolve, reject) => {
|
||||||
|
const writeStream = thumbnailBucket.openUploadStream(name, {
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
metadata: {
|
||||||
|
originalId: file._id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
writeStream.once('finish', resolve);
|
||||||
|
writeStream.on('error', reject);
|
||||||
|
writeStream.end(thumbnail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +366,7 @@ export default async function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
driveFile = await (save(fs.createReadStream(path), detectedName, mime, hash, size, metadata));
|
driveFile = await (save(path, detectedName, mime, hash, size, metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`drive file has been created ${driveFile._id}`);
|
log(`drive file has been created ${driveFile._id}`);
|
||||||
|
@ -6,8 +6,14 @@ import config from '../../config';
|
|||||||
export default async function(file: IDriveFile, isExpired = false) {
|
export default async function(file: IDriveFile, isExpired = false) {
|
||||||
if (file.metadata.storage == 'minio') {
|
if (file.metadata.storage == 'minio') {
|
||||||
const minio = new Minio.Client(config.drive.config);
|
const minio = new Minio.Client(config.drive.config);
|
||||||
|
|
||||||
const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`;
|
const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`;
|
||||||
await minio.removeObject(config.drive.bucket, obj);
|
await minio.removeObject(config.drive.bucket, obj);
|
||||||
|
|
||||||
|
if (file.metadata.thumbnailUrl) {
|
||||||
|
const thumbnailObj = `${config.drive.prefix}/${file.metadata.storageProps.id}-thumbnail`;
|
||||||
|
await minio.removeObject(config.drive.bucket, thumbnailObj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// チャンクをすべて削除
|
// チャンクをすべて削除
|
||||||
|
@ -95,6 +95,8 @@ type Option = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async (user: IUser, data: Option, silent = false) => new Promise<INote>(async (res, rej) => {
|
export default async (user: IUser, data: Option, silent = false) => new Promise<INote>(async (res, rej) => {
|
||||||
|
const isFirstNote = user.notesCount === 0;
|
||||||
|
|
||||||
if (data.createdAt == null) data.createdAt = new Date();
|
if (data.createdAt == null) data.createdAt = new Date();
|
||||||
if (data.visibility == null) data.visibility = 'public';
|
if (data.visibility == null) data.visibility = 'public';
|
||||||
if (data.viaMobile == null) data.viaMobile = false;
|
if (data.viaMobile == null) data.viaMobile = false;
|
||||||
@ -164,6 +166,10 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
|||||||
// Pack the note
|
// Pack the note
|
||||||
const noteObj = await pack(note);
|
const noteObj = await pack(note);
|
||||||
|
|
||||||
|
if (isFirstNote) {
|
||||||
|
noteObj.isFirstNote = true;
|
||||||
|
}
|
||||||
|
|
||||||
const nm = new NotificationManager(user, note);
|
const nm = new NotificationManager(user, note);
|
||||||
const nmRelatedPromises = [];
|
const nmRelatedPromises = [];
|
||||||
|
|
||||||
@ -188,6 +194,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
|||||||
// 通知
|
// 通知
|
||||||
if (isLocalUser(data.reply._user)) {
|
if (isLocalUser(data.reply._user)) {
|
||||||
nm.push(data.reply.userId, 'reply');
|
nm.push(data.reply.userId, 'reply');
|
||||||
|
publishUserStream(data.reply.userId, 'reply', noteObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +216,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Publish event
|
// Publish event
|
||||||
if (!user._id.equals(data.renote.userId)) {
|
if (!user._id.equals(data.renote.userId) && isLocalUser(data.renote._user)) {
|
||||||
publishUserStream(data.renote.userId, 'renote', noteObj);
|
publishUserStream(data.renote.userId, 'renote', noteObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,8 @@ const consts = {
|
|||||||
_URL_: config.url,
|
_URL_: config.url,
|
||||||
_LICENSE_: licenseHtml,
|
_LICENSE_: licenseHtml,
|
||||||
_GOOGLE_MAPS_API_KEY_: config.google_maps_api_key,
|
_GOOGLE_MAPS_API_KEY_: config.google_maps_api_key,
|
||||||
_WELCOME_BG_URL_: config.welcome_bg_url
|
_WELCOME_BG_URL_: config.welcome_bg_url,
|
||||||
|
_TWITTER_INTEGRATION_: config.twitter != null
|
||||||
};
|
};
|
||||||
|
|
||||||
const _consts: { [ key: string ]: any } = {};
|
const _consts: { [ key: string ]: any } = {};
|
||||||
|
Reference in New Issue
Block a user