Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
16d59e9988 | |||
93ce25db91 | |||
c97a523ada | |||
488fe2e7bd | |||
8da278a279 | |||
da69650505 | |||
a85f6edd8a | |||
d17c6adba4 | |||
2671c6b4f2 | |||
a81d5786f3 | |||
75e2b075e1 | |||
533884dcaa | |||
7298b41a16 | |||
39561e858a | |||
96b88ee369 | |||
d94e2025be | |||
6ba5968861 | |||
cea2d621f2 | |||
84be4aa91e | |||
a943bf3906 | |||
42a14508f6 | |||
f4cb62db16 | |||
4dc4ac2f0c | |||
bf9da8458c | |||
1832b2e53c | |||
17ebaa01f5 | |||
c595f68d9d | |||
b040ac6373 | |||
e94dd8a5e8 | |||
e14509574d | |||
3a093f8bd7 | |||
ebb9a9b64f | |||
ef17838c64 | |||
11ddbdc58d | |||
4e8047275b | |||
c19670d611 | |||
540bd3630a | |||
49f6b3233e | |||
0f2d392b4b | |||
53fba9b137 | |||
24f2183829 | |||
61642539b1 | |||
1b7454ca77 | |||
60d2558ed6 | |||
9f5a763c69 | |||
0ef31a8bac | |||
1a8632e802 | |||
c1c955031e | |||
75ab146860 | |||
c3f9b2dcc3 | |||
c9e33f665e | |||
b719d43049 | |||
f555aa734b | |||
37fb685016 | |||
09fe1b355a | |||
9d530a5c38 | |||
827c378ac1 | |||
9b91b92bca | |||
e7fcca0e06 | |||
a36e5b4543 | |||
ba474648a2 | |||
a7b710a2f8 | |||
c033464b92 | |||
3ef6305c76 | |||
511ef20b8a | |||
4db710a4b0 | |||
efe92724a4 | |||
aa19767ebc | |||
143190ab38 |
@ -1 +1 @@
|
||||
v12.9.1
|
||||
v12.11.1
|
||||
|
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,6 +1,61 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
11.34.0 (2019/10/14)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* みつけるに連合分を表示するように
|
||||
* リアクションピッカーをカスタマイズできるように
|
||||
* inputで不正な値を入力したときにメッセージを表示するように
|
||||
* ドライブファイルダウンロードURLにdownloadを付けないように
|
||||
* web-pushでProxyを使用するように
|
||||
|
||||
### 🐛Fixes
|
||||
* 投稿本文が合字を含んで8192文字の場合に制限をパスしても投稿できない問題を修正
|
||||
* meta.maxNoteTextLengthをnote.textの最大サイズより大きく設定できる問題を修正
|
||||
* ページ移動等してもメニュー等が閉じずに残ってしまう問題を修正
|
||||
* ページ移動してもメディアビューワーが残ってしまう問題を修正
|
||||
* APに関する問題の修正
|
||||
* オフライン時に大室櫻子が出てこない問題を修正
|
||||
|
||||
11.33.0 (2019/09/27)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* 絵文字ピッカーの Unicode 12.0 対応
|
||||
* Adminページのユーザー一覧フィルターに利用可能アカウントを追加
|
||||
* お知らせウィジェットのデザインを変更
|
||||
|
||||
### 🐛Fixes
|
||||
* ユーザーを特定のリストから外すと他のリストからも消える問題を修正
|
||||
* 任意のグループからユーザーを削除すると他のグループからでも削除されちゃうのを修正
|
||||
* 非公開の投稿にリアクションや返信がついたとき通知が正しく行われない問題を修正
|
||||
* AP: featured collectionの取り扱いが間違ってるのを修正
|
||||
* AP: 存在しないユーザーをAPで参照すると404にするべきところ500になってしまうのを修正
|
||||
* AndoridとデスクトップOSのChromeでPWAとしてインストール(ホーム画面に追加)できない問題を修正
|
||||
* /@:user/* でユーザー用のページを返さない問題を修正
|
||||
* インスタンス設定で空Arrayがうまく保存できない問題を修正
|
||||
* 絵文字サジェスト時にそれがサロゲートペアだった場合「1文字」分しかカーソルが移動しない問題を修正
|
||||
* ハッシュタグパースの修正
|
||||
* MFMでKeycap Number Signが表示できない問題を修正
|
||||
* フォローリクエストページのデザインを修正
|
||||
* selectコンポーネントに余分な下線が付く問題を修正
|
||||
|
||||
11.32.0 (2019/09/10)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* APでRenote/Boost取り消しをやりとりするように
|
||||
* リモートNote存在確認から作成までの間にLockをかけるように
|
||||
* 広告ブロッカーの警告を削除
|
||||
|
||||
### 🐛Fixes
|
||||
* ミュートしているユーザーからトークメッセージが来たときに通知インジケーターが点灯する問題を修正
|
||||
* リアクションした人が10人を超えるときツールチップの表示がおかしい問題を修正
|
||||
|
||||
11.31.4 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 誰がリアクションしたか見れるやつの表示を改善
|
||||
|
||||
11.31.3 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
|
@ -270,3 +270,7 @@ npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
|
||||
```
|
||||
|
||||
作成されたスクリプトは不必要な変更を含むため除去してください。
|
||||
|
||||
## その他
|
||||
### HTMLのクラス名で follow という単語は使わない
|
||||
広告ブロッカーで誤ってブロックされる
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:12.9.1-alpine AS base
|
||||
FROM node:12.11.1-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
18
README.md
18
README.md
@ -104,47 +104,51 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<!-- PATREON_START -->
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td>
|
||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
||||
@ -165,7 +169,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Sun, 01 Sep 2019 22:11:05 UTC
|
||||
**Last updated:** Thu, 03 Oct 2019 14:45:07 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
BIN
assets/icons/512.png
Normal file
BIN
assets/icons/512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
91
assets/icons/icon.svg
Normal file
91
assets/icons/icon.svg
Normal file
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 135.46667 135.46667"
|
||||
height="512"
|
||||
width="512"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
inkscape:export-filename="H:\misskey\assets\icons\512.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="965"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:zoom="0.921875"
|
||||
inkscape:cx="219.9952"
|
||||
inkscape:cy="361.00053"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
style="opacity:1;fill:#2ba3bc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291675;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="rect828"
|
||||
width="135.46667"
|
||||
height="135.46667"
|
||||
x="1.896631e-16"
|
||||
y="0" />
|
||||
<g
|
||||
transform="matrix(0.57430748,0,0,0.57430748,11.070705,-35.453717)"
|
||||
id="layer1"
|
||||
style="stroke-width:1.74122751;fill:#ffffff;fill-opacity:1">
|
||||
<g
|
||||
transform="matrix(1.096096,0,0,1.096096,-2.960633,-44.023579)"
|
||||
id="g4502"
|
||||
style="stroke-width:1.74122751;fill:#ffffff;fill-opacity:1">
|
||||
<g
|
||||
id="g5125"
|
||||
transform="translate(-1.3333333e-6,-1.3439941e-6)"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.74122751">
|
||||
<g
|
||||
aria-label="Mi"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:141.03404236px;line-height:476.69509888px;font-family:'OTADESIGN Rounded';-inkscape-font-specification:'OTADESIGN Rounded';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.50409585px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text4489"
|
||||
transform="matrix(0.91391326,0,0,0.91391326,7.9719907,17.595761)">
|
||||
<path
|
||||
id="path5210"
|
||||
transform="matrix(0.26412464,0,0,0.26412464,24.988264,136.28626)"
|
||||
d="m 62.474609,76.585938 c -7.47555,0 -14.595784,1.246427 -21.359375,3.738281 C 29.011968,84.595952 19.044417,92.249798 11.212891,103.28516 3.7373405,113.96451 0,125.88934 0,139.06055 v 233.8789 c 0,17.08697 6.0510264,31.85913 18.154297,44.31836 12.459246,12.10327 27.233346,18.15625 44.320312,18.15625 17.442947,0 32.215089,-6.05298 44.318361,-18.15625 12.45925,-12.45923 18.68945,-27.23139 18.68945,-44.31836 V 330.4082 c 0.13441,-9.21122 9.6225,-6.79429 14.41797,0 8.98111,15.55395 28.02226,28.91242 50.19141,28.83594 22.16915,-0.0764 40.58194,-11.03699 50.19336,-28.83594 3.63981,-4.29263 13.89902,-11.60675 14.95117,0 v 42.53125 c 0,17.08697 6.05102,31.85913 18.15429,44.31836 12.45923,12.10327 27.23335,18.15625 44.32032,18.15625 17.44294,0 32.21509,-6.05298 44.31836,-18.15625 12.45923,-12.45923 18.68945,-27.23139 18.68945,-44.31836 v -233.8789 c 0,-13.17121 -3.9146,-25.09604 -11.74609,-35.77539 -7.47557,-11.035362 -17.26588,-18.689208 -29.36914,-22.960941 -7.11956,-2.491854 -14.23982,-3.738281 -21.35938,-3.738281 -19.22286,0 -35.41865,7.476649 -48.58984,22.427734 l -63.40235,74.199218 c -1.42391,1.06791 -6.14093,9.23242 -16.16015,9.23242 -10.01923,0 -14.20109,-8.16451 -15.625,-9.23242 L 110.53125,99.013672 C 97.716024,84.062587 81.697447,76.585938 62.474609,76.585938 Z m 395.060551,0 c -14.9511,-10e-7 -27.76596,5.340179 -38.44532,16.019531 -10.32338,10.323381 -15.48437,22.961011 -15.48437,37.912111 0,14.9511 5.16099,27.76596 15.48437,38.44531 10.67936,10.32338 23.49422,15.48633 38.44532,15.48633 14.95109,0 27.76596,-5.16295 38.44531,-15.48633 C 506.65982,158.28354 512,145.46868 512,130.51758 512,115.56648 506.65982,102.92885 495.98047,92.605469 485.30112,81.926117 472.48625,76.585938 457.53516,76.585938 Z m 0.5332,118.541012 c -14.9511,0 -27.76596,5.34018 -38.44531,16.01953 -10.67936,10.67936 -16.01758,23.49422 -16.01758,38.44532 v 131.89062 c 0,14.9511 5.33822,27.76596 16.01758,38.44531 10.67935,10.32339 23.49421,15.48633 38.44531,15.48633 14.9511,0 27.58873,-5.16294 37.91211,-15.48633 C 506.65982,409.24838 512,396.43352 512,381.48242 V 249.5918 c 0,-14.9511 -5.34018,-27.76596 -16.01953,-38.44532 -10.32338,-10.67935 -22.96101,-16.01953 -37.91211,-16.01953 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'OTADESIGN Rounded';-inkscape-font-specification:'OTADESIGN Rounded';fill:#ffffff;fill-opacity:1;stroke-width:1.90855289px"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
@ -1,7 +1,7 @@
|
||||
# Sample nginx configuration for Misskey
|
||||
#
|
||||
# 1. Replace example.tld to your domain
|
||||
# 2. Copy to /etc/nginx/sites-available/ and then symlink from /etc/nginx/sites-ebabled/
|
||||
# 2. Copy to /etc/nginx/sites-available/ and then symlink from /etc/nginx/sites-enabled/
|
||||
# or copy to /etc/nginx/conf.d/
|
||||
|
||||
# For WebSocket
|
||||
|
@ -54,6 +54,7 @@ gulp.task('build:copy:fonts', () =>
|
||||
gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:fonts', () =>
|
||||
gulp.src([
|
||||
'./src/const.json',
|
||||
'./src/emojilist.json',
|
||||
'./src/server/web/views/**/*',
|
||||
'./src/**/assets/**/*',
|
||||
'!./src/client/app/**/assets/**/*'
|
||||
|
8
index.js
8
index.js
@ -1 +1,7 @@
|
||||
require('./built').default();
|
||||
const fs = require('fs');
|
||||
|
||||
if (fs.existsSync('./built')) {
|
||||
require('./built').default();
|
||||
} else {
|
||||
console.log('Built code is not found. Probably an error occurred during a build or you just forgot to build.');
|
||||
}
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "Disk"
|
||||
drive-desc: "Chcete sdílet obrázek, který jste již nahráli? Chcete vaše nahrané soubory organizovat, pojmenovávat a vytvářet pro ně složky? Misskey Disk je pro vás nejlepší řešení. Je velmi snadné sdílet vaše soubory online."
|
||||
outro: "Podívejte se na unikátní vlastnosti Misskey vlastníma očima! Pokud si myslíte, že tato instance není pro vás, zkuste jiné instance, neboť Misskey je decentralizovaná sociální síť, takže můžete snadno najít své přátele. Hodně štěstí a zábavy!"
|
||||
adblock:
|
||||
detected: "Prosím vypněte svůj blokovač reklam"
|
||||
warning: "Některé vlastnosti mohou být nedostupné nebo mohou způsobovat chyby, pokud máte povolený blokovač reklam. <strong>Misskey nepoužívá reklamy</strong>."
|
||||
application-authorization: "Autorizované aplikace"
|
||||
close: "Zavřít"
|
||||
do-not-copy-paste: "Prosím nezadávejte ani nevkládejte sem kód. Váš účet může být kompromitován."
|
||||
@ -34,6 +31,7 @@ common:
|
||||
signup: "Registrovat"
|
||||
signout: "Odhlásit"
|
||||
reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?"
|
||||
fetching-as-ap-object: "Načítám data z Fediversu..."
|
||||
delete-confirm: "Opravdu chcete smazat tento příspěvek?"
|
||||
signin-required: "Přihlašte se, prosím"
|
||||
notification-type: "Typy oznámení"
|
||||
@ -104,6 +102,9 @@ common:
|
||||
"read:reactions": "Prohlížet reakce"
|
||||
"write:reactions": "Narabět s reakcemi"
|
||||
"write:votes": "Hlasovat"
|
||||
"read:pages": "Zhlédnutí stránky"
|
||||
"write:pages": "Upravit stránky"
|
||||
"write:user-groups": "Upravit uživatelskou skupinu"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
|
||||
explore: "Najít uživatele"
|
||||
@ -118,10 +119,12 @@ common:
|
||||
renote: "Renotovat"
|
||||
posting: "Posílání"
|
||||
attach-media-from-local: "Nahrát soubor z vašeho zařízení"
|
||||
attach-media-from-drive: "Přiložit soubory z vašeho Drivu"
|
||||
insert-a-kao: "v('ω')v"
|
||||
create-poll: "Vytvořit anketu"
|
||||
text-remain: "zbývá ještě {} znaků"
|
||||
recent-tags: "Nejnovější"
|
||||
local-only-message: "Publikovat zprávu pouze lokálně"
|
||||
click-to-tagging: "Klikni pro otágování"
|
||||
visibility: "Viditelnost"
|
||||
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
|
||||
@ -190,6 +193,7 @@ common:
|
||||
other: "Ostatní"
|
||||
appearance: "Vzhled"
|
||||
behavior: "Chování"
|
||||
reactions: "Reakce"
|
||||
fetch-on-scroll: "Nekonečné načítaní posuvem"
|
||||
fetch-on-scroll-desc: "Pokud budete rolovat dolů po stránce, automaticky bude načten další obsah."
|
||||
note-visibility: "Viditelnost příspěvku"
|
||||
@ -198,6 +202,8 @@ common:
|
||||
web-search-engine: "Webové vyhledávače"
|
||||
web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}"
|
||||
paste: "Vložit"
|
||||
pasted-file-name-desc: "Například: \"rrrr-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
|
||||
paste-dialog: "Upravit název vloženého souboru"
|
||||
keep-cw: "Zachovat varování o obsahu"
|
||||
keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek."
|
||||
i-like-sushi: "Mam radši sushi (než puding)"
|
||||
@ -386,6 +392,7 @@ common/views/components/games/reversi/reversi.game.vue:
|
||||
surrender: "Vzdát se"
|
||||
surrendered: "Vzdaním se"
|
||||
looped-map: "Zacyklená mapa"
|
||||
can-put-everywhere: "Lze položit kamkoliv"
|
||||
common/views/components/games/reversi/reversi.index.vue:
|
||||
title: "Misskey Reversi"
|
||||
sub-title: "Hrajte Reversi s Vašimi kamarády!"
|
||||
@ -435,6 +442,7 @@ common/views/components/connect-failed.troubleshooter.vue:
|
||||
no-server: "Nelze se připojit k serveru Misskey"
|
||||
success: "Úspěšně se podařilo spojit s Misskey serverem"
|
||||
flush: "Vyčistit mezipaměť"
|
||||
set-version: "Vyberte verzi"
|
||||
common/views/components/media-banner.vue:
|
||||
sensitive: "Choulostivý obsah"
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
@ -488,6 +496,7 @@ common/views/components/messaging.vue:
|
||||
group: "Skupina"
|
||||
start-with-user: "Zahájit konverzaci s uživatelem"
|
||||
start-with-group: "Zahájit skupinovou konverzaci"
|
||||
select-group: "Vybrat skupinu"
|
||||
common/views/components/messaging-room.vue:
|
||||
new-message: "Máte novou zprávu"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
@ -559,6 +568,7 @@ common/views/components/poll-editor.vue:
|
||||
deadline-date: "Termín ukončení"
|
||||
deadline-time: "Doba trvání"
|
||||
interval: "Trvání"
|
||||
unit: "Jednotka"
|
||||
second: "Sekunda"
|
||||
minute: "Minuta"
|
||||
hour: "Hodina"
|
||||
@ -577,6 +587,12 @@ common/views/components/emoji-picker.vue:
|
||||
symbols: "Symboly"
|
||||
flags: "Vlajky"
|
||||
common/views/components/settings/app-type.vue:
|
||||
title: "Režim"
|
||||
intro: "Můžete vybrat zdali chcete použít stolní, nebo mobilní vzhled."
|
||||
choices:
|
||||
auto: "Vybrat vzhled automaticky"
|
||||
desktop: "Vždy použít stolní vzhled"
|
||||
mobile: "Vždy použít mobilní vzhled"
|
||||
info: "Pro aktivování změn musíte znovu načíst stránky."
|
||||
common/views/components/signin.vue:
|
||||
username: "Přezdívka"
|
||||
@ -588,6 +604,7 @@ common/views/components/signin.vue:
|
||||
signin-with-github: "Přihlásit se pomocí účtu GitHub"
|
||||
signin-with-discord: "Přihlásit se pomocí účtu Discord"
|
||||
login-failed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo."
|
||||
enter-2fa-code: "Vložte Váš verifikační kód"
|
||||
common/views/components/signup.vue:
|
||||
invitation-code: "Kód pozvánky"
|
||||
invitation-info: "Pokud máte pozvánku, prosím kontaktujte <a href=\"{}\">administrátora</a>."
|
||||
@ -609,6 +626,7 @@ common/views/components/signup.vue:
|
||||
password-matched: "OK"
|
||||
password-not-matched: "Neshodují se"
|
||||
recaptcha: "Potvrzení"
|
||||
agree-to: "Souhlasím s {0}."
|
||||
tos: "Podmínky užívání"
|
||||
create: "Vytvořit účet"
|
||||
some-error: "Pokus o vytvoření účtu selhal. Prosím zkuste to znovu."
|
||||
@ -625,8 +643,10 @@ common/views/components/notification-settings.vue:
|
||||
mark-as-read-all-unread-notes: "Označit všechny příspěvky za přečtené"
|
||||
mark-as-read-all-talk-messages: "Označit všechny zprávy za přečtené"
|
||||
common/views/components/integration-settings.vue:
|
||||
title: "Integrace"
|
||||
connect: "Připojit"
|
||||
disconnect: "Odpojit"
|
||||
connected-to: "Jste připojen k tomuto GitHub účtu"
|
||||
common/views/components/github-setting.vue:
|
||||
description: "Jakmile spojíte Váš GitHub účet s Vaším Misskey účtem, uvidíte informace o Vašem GitHub účtu na Vašem profilu a budete se moci přihlásit skrze GitHub."
|
||||
connected-to: "Je připojen k tomuto GitHub účtu"
|
||||
@ -646,6 +666,8 @@ common/views/components/uploader.vue:
|
||||
common/views/components/visibility-chooser.vue:
|
||||
public: "Veřejné"
|
||||
home: "Domů"
|
||||
home-desc: "Poslat pouze na domovskou časovou osu"
|
||||
specified: "Přímá"
|
||||
specified-desc: "Poslat pouze zmíněným uživatelům"
|
||||
local-public: "Veřejná (pouze místní)"
|
||||
local-public-desc: "Nepublikovat na vzdálených serverech"
|
||||
@ -730,12 +752,14 @@ common/views/components/user-groups.vue:
|
||||
group-name: "Název skupiny"
|
||||
owned-groups: "Moje skupiny"
|
||||
invites: "Pozvat"
|
||||
accept-invite: "Přidat se"
|
||||
reject-invite: "Odmítnout"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Načítám"
|
||||
no-broadcasts: "Žádná nová oznámení"
|
||||
have-a-nice-day: "Přejeme Vám příjemný den!"
|
||||
next: "Další"
|
||||
prev: "Předchozí"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "Rok {}"
|
||||
month: "{},"
|
||||
@ -744,6 +768,7 @@ common/views/widgets/calendar.vue:
|
||||
this-month: "Měsíc:"
|
||||
this-year: "Rok:"
|
||||
common/views/widgets/photo-stream.vue:
|
||||
title: "Foto stream"
|
||||
no-photos: "Žádné obrázky"
|
||||
common/views/widgets/posts-monitor.vue:
|
||||
title: "Grafy příspěvků"
|
||||
@ -759,6 +784,8 @@ common/views/widgets/memo.vue:
|
||||
save: "Uložit"
|
||||
common/views/widgets/slideshow.vue:
|
||||
no-image: "V této složce nebyly nalezeny žádné fotky."
|
||||
common/views/widgets/tips.vue:
|
||||
tips-line23: "Ai-chan kawaii!"
|
||||
common/views/pages/not-found.vue:
|
||||
page-not-found: "Stránka nenalezena"
|
||||
common/views/pages/follow.vue:
|
||||
@ -830,6 +857,7 @@ desktop/views/components/drive.folder.vue:
|
||||
input-new-folder-name: "Zadejte nové jméno"
|
||||
else-folders: "Ostatní"
|
||||
desktop/views/components/drive.vue:
|
||||
search: "Vyhledávání"
|
||||
empty-drive-description: "Klikněte pravým tlačítkem myši pro otevření menu, nebo sem přetáhněte soubor pro nahrání."
|
||||
empty-folder: "Tato složka je prázdná"
|
||||
unable-to-process: "Operace nemohla být dokončena."
|
||||
@ -845,10 +873,13 @@ desktop/views/components/drive.vue:
|
||||
url-upload: "Nahrát z URL"
|
||||
desktop/views/components/media-video.vue:
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
desktop/views/components/followers.vue:
|
||||
empty: "Vypadá to že Vás nikdo nesleduje."
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Hotovo"
|
||||
add-widget: "Přidat widget:"
|
||||
add: "Přidat"
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Zrušit"
|
||||
@ -856,6 +887,7 @@ desktop/views/input-dialog.vue:
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Tento příspěvek je soukromý"
|
||||
deleted: "Tento příspěvek byl odstraněn"
|
||||
location: "Lokace"
|
||||
renote: "Renotovat"
|
||||
add-reaction: "Přidat reakci"
|
||||
undo-reaction: "Odebrat reakci"
|
||||
@ -870,6 +902,8 @@ desktop/views/components/note.vue:
|
||||
desktop/views/components/notes.vue:
|
||||
error: "Načítání selhalo."
|
||||
retry: "Opakovat"
|
||||
desktop/views/components/notifications.vue:
|
||||
empty: "Žádné nové notifikace!"
|
||||
desktop/views/components/post-form.vue:
|
||||
posted: "Odesláno!"
|
||||
replied: "Odpověděno!"
|
||||
@ -894,10 +928,29 @@ desktop/views/components/renote-form-window.vue:
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
detail: "Více…"
|
||||
url: "https://www.google.cz/landing/2step/"
|
||||
register: "Přidat zařízení"
|
||||
already-registered: "Toto zařízení je již připojené"
|
||||
unregister: "Odebrat"
|
||||
enter-password: "Prosím zadejte heslo"
|
||||
authenticator: "Nejprve musíte nainstalovat Google Authenticator na Vašem zařízení:"
|
||||
howtoinstall: "Jak nainstalovat"
|
||||
token: "Token"
|
||||
scan: "Poté naskenujte QR kód:"
|
||||
done: "Prosím vložte kód zobrazený na Vašem zařízení:"
|
||||
submit: "Uložit"
|
||||
success: "Nastavení uloženo!"
|
||||
failed: "Nepodařilo se spárovat. Prosím zkontrolujte správnost bezpečnostního kódu."
|
||||
totp-header: "Ověřovací aplikace"
|
||||
security-key-header: "Bezpečnostní klíč"
|
||||
last-used: "Naposledy použito:"
|
||||
activate-key: "Klikněte pro aktivaci bezpečnostního klíče"
|
||||
security-key-name: "Název klíče"
|
||||
key-unregistered: "Bezpečnostní klíč byl odstraněn"
|
||||
common/views/components/media-image.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
common/views/components/api-settings.vue:
|
||||
caution: "Nepoužívejte tento kód v žádné jiné aplikace nebo ho sdílejte s ostatními (jinak můžete ohrozit svojí bezpečnost)."
|
||||
token: "Token:"
|
||||
enter-password: "Prosím zadejte heslo"
|
||||
console:
|
||||
@ -979,20 +1032,48 @@ admin/views/index.vue:
|
||||
announcements: "Oznámení"
|
||||
queue: "Fronta úloh"
|
||||
logs: "Logy"
|
||||
db: "Databáze"
|
||||
back-to-misskey: "Zpět na Misskey"
|
||||
admin/views/db.vue:
|
||||
tables: "Tabulky"
|
||||
vacuum: "Vysavač"
|
||||
vacuum-info: "Uklidí databázi. Neohrozí data a sníží využití disku. Tohle se děje automaticky a opakovaně."
|
||||
vacuum-exclamation: "Vysavač může dočasně přetížit databázi a dočasně omezit akce uživatelů."
|
||||
admin/views/dashboard.vue:
|
||||
dashboard: "Kontrolní panel"
|
||||
accounts: "Účty"
|
||||
notes: "Poznámky"
|
||||
drive: "Disk"
|
||||
instances: "Instance"
|
||||
this-instance: "Tato instance"
|
||||
federated: "Z fedivesmíru"
|
||||
admin/views/queue.vue:
|
||||
title: "Ve frontě"
|
||||
remove-all-jobs: "Vyčistit frontu"
|
||||
jobs: "Úkoly"
|
||||
queue: "Ve frontě"
|
||||
domains:
|
||||
inbox: "Obdržené"
|
||||
db: "Databáze"
|
||||
states:
|
||||
active: "V procesu"
|
||||
delayed: "Naplánováno"
|
||||
waiting: "Ve frontě"
|
||||
result-is-truncated: "Zkrácený výsledek"
|
||||
admin/views/logs.vue:
|
||||
logs: "Logy"
|
||||
domain: "Doména"
|
||||
level: "Úroveň"
|
||||
levels:
|
||||
all: "Vše"
|
||||
info: "Informace"
|
||||
success: "Podařilo se"
|
||||
warning: "Varování"
|
||||
error: "Chyba"
|
||||
debug: "Debug"
|
||||
delete-all: "Smazat vše"
|
||||
admin/views/abuse.vue:
|
||||
target: "Cíl"
|
||||
details: "Popis"
|
||||
remove-report: "Odstranit"
|
||||
admin/views/instance.vue:
|
||||
@ -1000,17 +1081,33 @@ admin/views/instance.vue:
|
||||
instance-name: "Název instance"
|
||||
instance-description: "Popis instance"
|
||||
host: "Hostitel"
|
||||
icon-url: "URL ikonky"
|
||||
logo-url: "URL loga"
|
||||
banner-url: "URL pro baner"
|
||||
error-image-url: "URL pro chybový obrázek"
|
||||
languages: "Jazyk této instance"
|
||||
languages-desc: "Můžete nastavit více než jeden, oddělte mezerami."
|
||||
tos-url: "URL pro smluvní podmínky"
|
||||
repository-url: "URL adresa repositáře"
|
||||
feedback-url: "URL pro zpětnou vazbu"
|
||||
maintainer-config: "Informace o administrátorovi"
|
||||
maintainer-name: "Jméno administrátora"
|
||||
maintainer-email: "Kontakt na administrátora"
|
||||
advanced-config: "Další nastavení"
|
||||
object-storage-base-url: "URL"
|
||||
object-storage-prefix: "Předpona"
|
||||
object-storage-endpoint: "Endpoint"
|
||||
object-storage-region: "Region"
|
||||
object-storage-port: "Port"
|
||||
object-storage-access-key: "Přístupový klíč"
|
||||
object-storage-secret-key: "Tajný Klíč (Secret Key)"
|
||||
object-storage-use-ssl: "Použít SSL"
|
||||
object-storage-s3-info-here: "zde"
|
||||
mb: "V megabajtech"
|
||||
recaptcha-config: "nastavení služby reCAPTCHA"
|
||||
recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
|
||||
enable-recaptcha: "povolit reCAPTCHA"
|
||||
recaptcha-secret-key: "Tajný Klíč (Secret Key)"
|
||||
recaptcha-preview: "Náhled"
|
||||
twitter-integration-config: "Nastavení spojení s Twitterem"
|
||||
twitter-integration-info: "The callback URL is set on {url}."
|
||||
@ -1209,6 +1306,9 @@ desktop/views/widgets/notifications.vue:
|
||||
title: "Oznámení"
|
||||
desktop/views/widgets/polls.vue:
|
||||
title: "Ankety"
|
||||
nothing: "Žádné nové notifikace!"
|
||||
desktop/views/widgets/trends.vue:
|
||||
nothing: "Žádné nové notifikace!"
|
||||
desktop/views/widgets/users.vue:
|
||||
title: "Doporučení uživatelé"
|
||||
mobile/views/components/drive.vue:
|
||||
@ -1251,6 +1351,8 @@ mobile/views/components/note-sub.vue:
|
||||
admin: "admin"
|
||||
bot: "bot"
|
||||
cat: "kočka"
|
||||
mobile/views/components/notifications.vue:
|
||||
empty: "Žádné nové notifikace!"
|
||||
mobile/views/components/sub-note-content.vue:
|
||||
private: "Tento příspěvek je soukromý"
|
||||
deleted: "Tento příspěvek byl odstraněn"
|
||||
@ -1294,6 +1396,7 @@ mobile/views/pages/note.vue:
|
||||
mobile/views/pages/games/reversi.vue:
|
||||
reversi: "Reversi"
|
||||
mobile/views/pages/search.vue:
|
||||
search: "Vyhledávání"
|
||||
not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
|
||||
mobile/views/pages/selectdrive.vue:
|
||||
select-file: "Vybrat soubory"
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "Drev"
|
||||
drive-desc: "Vil du poste et billede, som du tidligere har uploadet? Har du brug for at navngive filer og organisere dem i mapper, som du selv har navngivet? Så er Misskey Drev den bedste løsning for dig. Den gør det så let som ingenting at dele dine filer online."
|
||||
outro: "Tjek Misskey's unikke funktioner ved at se dem med dine egne øjne. Hvis du kommer frem til, at den ene server ikke er noget for dig, så kan du prøve en anden. Misskey er et decentraliseret SNS, så du kan lettere finde frem til brugere, som du klikker med. God fornøjelse!"
|
||||
adblock:
|
||||
detected: "Her kan du deaktivere ad blocker."
|
||||
warning: "Visse funktioner kan forsvinde eller medføre forstyrrelser, hvis ad blocking er aktiveret. <strong>Misskey kører ikke annoncer</strong>."
|
||||
application-authorization: "Adgangsstyring"
|
||||
close: "Luk"
|
||||
do-not-copy-paste: "Undgå venligst at skrive eller klistre kode ind her. I modsat fald kan din konto blive kompromitteret."
|
||||
@ -169,6 +166,7 @@ common:
|
||||
other: "Andet"
|
||||
appearance: "Udseende"
|
||||
behavior: "Opførsel"
|
||||
reactions: "Reaktion"
|
||||
fetch-on-scroll: "Uendeligt scroll"
|
||||
fetch-on-scroll-desc: "Når du scroller ned ad siden, hentes der automatisk nyt indhold ind"
|
||||
note-visibility: "Post synlighed"
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "Drive"
|
||||
drive-desc: "Du willst ein hochgeladenes Foto nochmal posten? Deine Dateien benennen und in Ordnern sortieren? Misskeys Drive ist der beste Ort dafür. Damit wird das Teilen zum Kinderspiel!"
|
||||
outro: "Probiere Misskey aus und entdecke Misskeys einzigartige Funktionen. Wenn dir diese Instanz nicht zusagt, nimm einfach eine andere. Dank Misskeys dezentralem System kannst du dich überall mit deinen Freunden verbinden. Also dann, GLHF!"
|
||||
adblock:
|
||||
detected: "Bitte deaktiviere den Werbeblocker."
|
||||
warning: "<strong>Misskey hat keine Werbung.</strong> Allerdings können Werbeblocker für unerwartete Probleme und fehlende Funktionen sorgen."
|
||||
application-authorization: "Autorisierte Anwendungen"
|
||||
close: "Schließen"
|
||||
do-not-copy-paste: "Bitte keinen Code einfügen. Ihr Account könnte gefährdet werden."
|
||||
|
@ -12,15 +12,12 @@ common:
|
||||
rich-contents: "Post"
|
||||
rich-contents-desc: "Just post your idea, hot topics, and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files, including videos, or create a poll - those are some of the things you can do with Misskey!"
|
||||
reaction: "Reactions"
|
||||
reaction-desc: "The easiest way to express your emotions. Misskey allows you to add various kinds of reactions to other's posts. The emotional experience on Misskey will never be on other SNSs, which are only able to push “likes”."
|
||||
reaction-desc: "The easiest way to express your emotions. Misskey allows you to add various reactions to posts. Other SNSs only have a \"like\" reaction."
|
||||
ui: "Interface"
|
||||
ui-desc: "No single UI can suit everyone. Therefore, Misskey has a highly customizable UI for your tastes. You can make your home original by editing the layout of your timeline, and moving around selectable widgets that you can easily adjust to make this place your own."
|
||||
drive: "Drive"
|
||||
drive-desc: "Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online."
|
||||
outro: "Check Misskey-unique features by seeing them with your own eyes! If you feel like this instance is not for you, try other instances, as Misskey is a decentralized SNS, so that you can easily find your mates. Then, GLHF!"
|
||||
adblock:
|
||||
detected: "Please disable ad blocker."
|
||||
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"
|
||||
close: "Close"
|
||||
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
|
||||
@ -94,7 +91,7 @@ common:
|
||||
"write:drive": "Work with the Drive"
|
||||
"read:favorites": "View Favorites"
|
||||
"write:favorites": "Work with Favorites"
|
||||
"read:following": "View Follower info"
|
||||
"read:following": "View your Follow info"
|
||||
"write:following": "Work with Follow info"
|
||||
"read:messaging": "View Messaging"
|
||||
"write:messaging": "Work with Messaging"
|
||||
@ -106,6 +103,12 @@ common:
|
||||
"read:reactions": "View reactions"
|
||||
"write:reactions": "Work with reactions"
|
||||
"write:votes": "Vote"
|
||||
"read:pages": "View Pages"
|
||||
"write:pages": "Update Pages"
|
||||
"read:page-likes": "View Likes on Pages"
|
||||
"write:page-likes": "Update Likes on Pages"
|
||||
"read:user-groups": "View User groups"
|
||||
"write:user-groups": "Edit User groups"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
|
||||
explore: "Find users"
|
||||
@ -197,6 +200,8 @@ common:
|
||||
other: "Other"
|
||||
appearance: "Appearance"
|
||||
behavior: "Behavior"
|
||||
reactions: "Reaction"
|
||||
reactions-description: "Customize Emojis of Reaction picker delimited by line breaks"
|
||||
fetch-on-scroll: "Endless loading on scroll"
|
||||
fetch-on-scroll-desc: "When you scroll down the page, it automatically fetches additional content."
|
||||
note-visibility: "Post visibility"
|
||||
@ -818,6 +823,7 @@ common/views/widgets/broadcast.vue:
|
||||
no-broadcasts: "No announcements"
|
||||
have-a-nice-day: "Have a nice day!"
|
||||
next: "Next"
|
||||
prev: "Previous"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "Year {}"
|
||||
month: "{},"
|
||||
@ -1296,7 +1302,7 @@ admin/views/instance.vue:
|
||||
discord-integration-client-id: "Client ID"
|
||||
discord-integration-client-secret: "Client Secret"
|
||||
proxy-account-config: "Proxy account"
|
||||
proxy-account-info: "Proxy account can follow a remote user to deliver activities if no one in this instance follow him or her. When you add a remote user who is followed by nobody in this instance to your list, in order to get his or her data, proxy account follow him or her instead of your following."
|
||||
proxy-account-info: "A proxy account is an account that acts as a remote follower for users under certain conditions. For example, when a user adds a remote user to the list, the activity will not be delivered to the server if no one is following that user, so the proxy account will follow instead."
|
||||
proxy-account-username: "Proxy account user name"
|
||||
proxy-account-username-desc: "Specify the user name of the account that is used as a proxy."
|
||||
proxy-account-warn: "You must make an account having this username before this action."
|
||||
@ -1416,6 +1422,7 @@ admin/views/users.vue:
|
||||
state:
|
||||
title: "Sort"
|
||||
all: "All"
|
||||
available: "Available"
|
||||
admin: "Administrator"
|
||||
moderator: "Moderator"
|
||||
adminOrModerator: "Admin/Moderator"
|
||||
@ -1822,6 +1829,8 @@ pages:
|
||||
page-created: "Created the page!"
|
||||
page-updated: "Updated the page"
|
||||
name-already-exists: "The specified page name already exists"
|
||||
title-invalid-name: "The specified page URL is invalid"
|
||||
text-invalid-name: "Check whether that is not a blank"
|
||||
are-you-sure-delete: "Do you want to delete this page?"
|
||||
page-deleted: "The page has been deleted"
|
||||
edit-this-page: "Edit this page"
|
||||
|
@ -16,9 +16,6 @@ common:
|
||||
ui: "Interfaz"
|
||||
ui-desc: "No hay ninguna interfaz que le vaya bien a todos. Por eso, Misskey tiene una interfaz altamente personalizable para tus gustos. Puedes hacer tu página principal única editando la interfaz de tu timeline y moviendo varios widgets para conseguir hacer de este lugar uno propio."
|
||||
drive: "Drive"
|
||||
adblock:
|
||||
detected: "Por favor, desactive el bloqueador de publicidad."
|
||||
warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
|
||||
application-authorization: "Autorizaciones de la aplicación."
|
||||
close: "Cerrar"
|
||||
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "Drive"
|
||||
drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne."
|
||||
outro: "Découvrez vous-même les fonctionnalités de Misskey. Étant donné que Misskey est un réseau social fédéré, vous pouvez essayer d’autres instances afin de trouver vos amis si la présente instance ne vous correspond pas. Bonne chance et amusez-vous bien !"
|
||||
adblock:
|
||||
detected: "Veuillez désactiver votre bloqueur de publicités"
|
||||
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: "Autorisations de l’application"
|
||||
close: "Fermer"
|
||||
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte pourrait être compromis."
|
||||
@ -37,17 +34,17 @@ common:
|
||||
unfollow-confirm: "Désirez-vous vous désabonner de {name} ?"
|
||||
delete-confirm: "Supprimer cette publication ?"
|
||||
signin-required: "Veuillez vous connecter"
|
||||
notification-type: "Type de notification"
|
||||
notification-type: "Type de notifications"
|
||||
notification-types:
|
||||
all: "Tout"
|
||||
pollVote: "Sondage"
|
||||
pollVote: "Sondages"
|
||||
follow: "Abonnements"
|
||||
receiveFollowRequest: "Demandes d’abonnements"
|
||||
reply: "Répondre"
|
||||
reply: "Réponses"
|
||||
quote: "Cité par"
|
||||
renote: "Republier"
|
||||
renote: "Partages"
|
||||
mention: "Mentions"
|
||||
reaction: "Réaction"
|
||||
reaction: "Réactions"
|
||||
got-it: "J’ai compris !"
|
||||
customization-tips:
|
||||
title: "Conseils de personnalisation"
|
||||
@ -102,6 +99,7 @@ common:
|
||||
"read:reactions": "Lire les réactions"
|
||||
"write:reactions": "Gérer vos réactions"
|
||||
"write:votes": "Vote"
|
||||
"read:pages": "Afficher la page"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil."
|
||||
explore: "Trouver des utilisateur·rice·s"
|
||||
@ -183,7 +181,7 @@ common:
|
||||
notification: "Notifications"
|
||||
apps: "Applications"
|
||||
tags: "Hashtags"
|
||||
mute-and-block: "Silencés / Bloqués"
|
||||
mute-and-block: "Masqués / Bloqués"
|
||||
blocking: "En cours blocage"
|
||||
security: "Sécurité"
|
||||
signin: "Historique des connexions"
|
||||
@ -191,6 +189,7 @@ common:
|
||||
other: "Avancé"
|
||||
appearance: "Apparence"
|
||||
behavior: "Comportement"
|
||||
reactions: "Réaction"
|
||||
fetch-on-scroll: "Chargement automatique lors du défilement"
|
||||
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
|
||||
note-visibility: "Visibilité de la publication"
|
||||
@ -199,6 +198,7 @@ common:
|
||||
web-search-engine: "Moteur de recherche Web"
|
||||
web-search-engine-desc: "Exemple : https://www.google.com/?#q={{query}}"
|
||||
paste: "Coller"
|
||||
pasted-file-name-desc: "Exemple : \"yyyy-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
|
||||
keep-cw: "Maintenir l'avertissement de contenu"
|
||||
keep-cw-desc: "Lorsque vous répondez à un message, le même avertissement de contenu est reprit par défaut dans la réponse, le même que celui qui a été défini dans le message original."
|
||||
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
|
||||
@ -380,6 +380,8 @@ common/views/pages/explore.vue:
|
||||
federated: "Du Fédiverse"
|
||||
explore: "Explorer {host}"
|
||||
users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users} ont réagit avec {reaction}"
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "Activer la lecture"
|
||||
disable-player: "Fermer le lecteur"
|
||||
@ -545,8 +547,9 @@ common/views/components/note-menu.vue:
|
||||
unpin: "Désépingler"
|
||||
delete: "Supprimer"
|
||||
delete-confirm: "Supprimer cette publication ?"
|
||||
delete-and-edit: "Effacer et éditer"
|
||||
delete-and-edit: "Supprimer et réécrire"
|
||||
remote: "Afficher la note originale"
|
||||
pin-limit-exceeded: "Vous ne pouvez plus épingler davantage de publications."
|
||||
common/views/components/user-menu.vue:
|
||||
mention: "Mention"
|
||||
mute: "Silencier"
|
||||
@ -600,7 +603,7 @@ common/views/components/poll-editor.vue:
|
||||
hour: "Heures"
|
||||
day: "D"
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "Choisissez votre réaction"
|
||||
choose-reaction: "Envoyer une réaction"
|
||||
input-reaction-placeholder: "ou insérez un émoji"
|
||||
common/views/components/emoji-picker.vue:
|
||||
custom-emoji: "Émoji personnalisé"
|
||||
@ -978,8 +981,8 @@ desktop/views/components/note-detail.vue:
|
||||
deleted: "cette publication a été supprimée"
|
||||
location: "Géolocalisation"
|
||||
renote: "Republier"
|
||||
add-reaction: "Ajouter votre reaction"
|
||||
undo-reaction: "Inverser la réaction"
|
||||
add-reaction: "Ajouter une réaction"
|
||||
undo-reaction: "Annuler la réaction"
|
||||
desktop/views/components/note.vue:
|
||||
reply: "Répondre"
|
||||
renote: "Partager"
|
||||
@ -1075,9 +1078,9 @@ common/views/components/drive-settings.vue:
|
||||
default-upload-folder-name: "Dossier·s"
|
||||
change-default-upload-folder: "Changer de dossier"
|
||||
common/views/components/mute-and-block.vue:
|
||||
mute-and-block: "Silencés / Bloqués"
|
||||
mute: "Mettre en sourdine"
|
||||
block: "En cours blocage"
|
||||
mute-and-block: "Masqués / Bloqués"
|
||||
mute: "Mis en sourdine"
|
||||
block: "Vous avez bloqué"
|
||||
no-muted-users: "Aucun utilisateur n’est mis en sourdine"
|
||||
no-blocked-users: "Aucun utilisateur n’est bloqué"
|
||||
word-mute: "Filtre de mots"
|
||||
@ -1382,6 +1385,7 @@ admin/views/users.vue:
|
||||
state:
|
||||
title: "État"
|
||||
all: "Tout"
|
||||
available: "Disponible"
|
||||
admin: "Admin"
|
||||
moderator: "Modérateur"
|
||||
adminOrModerator: "Administrateur/Modérateur"
|
||||
@ -1775,6 +1779,8 @@ pages:
|
||||
read-page: "Voir la source"
|
||||
page-created: "Page a été créée !"
|
||||
page-updated: "A mis à jour la page"
|
||||
name-already-exists: "Une page portant le même nom existe déjà"
|
||||
title-invalid-name: "L’URL de la page spécifiée n’est pas valide"
|
||||
are-you-sure-delete: "Confirmez-vous la suppression de cette page ?"
|
||||
page-deleted: "La page a bien été supprimée."
|
||||
edit-this-page: "Éditer cette page"
|
||||
@ -1783,6 +1789,7 @@ pages:
|
||||
view-source: "Afficher la source"
|
||||
view-page: "Afficher la page"
|
||||
like: "Bien"
|
||||
unlike: "Je n’aime pas"
|
||||
liked-pages: "Pages favorites"
|
||||
my-pages: "Mes pages"
|
||||
inspector: "Inspecteur"
|
||||
@ -2047,6 +2054,7 @@ room:
|
||||
book: "Livre"
|
||||
book2: "Livre 2"
|
||||
piano: "Piano"
|
||||
facial-tissue: "Mouchoirs en papier"
|
||||
server: "Serveurs"
|
||||
moon: "Lune"
|
||||
corkboard: "Tableau en liège"
|
||||
|
@ -19,9 +19,6 @@ common:
|
||||
drive: "ドライブ"
|
||||
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
|
||||
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
|
||||
adblock:
|
||||
detected: "広告ブロッカーを無効にしてください"
|
||||
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "閉じる"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
||||
@ -110,6 +107,12 @@ common:
|
||||
"read:reactions": "リアクションを見る"
|
||||
"write:reactions": "リアクションを操作する"
|
||||
"write:votes": "投票する"
|
||||
"read:pages": "ページを見る"
|
||||
"write:pages": "ページを操作する"
|
||||
"read:page-likes": "ページのいいねを見る"
|
||||
"write:page-likes": "ページのいいねを操作する"
|
||||
"read:user-groups": "ユーザーグループを見る"
|
||||
"write:user-groups": "ユーザーグループを操作する"
|
||||
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "ユーザーをフォローすると投稿がタイムラインに表示されます。"
|
||||
@ -209,6 +212,8 @@ common:
|
||||
other: "その他"
|
||||
appearance: "デザイン"
|
||||
behavior: "動作"
|
||||
reactions: "リアクション"
|
||||
reactions-description: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
|
||||
fetch-on-scroll: "スクロールで自動読み込み"
|
||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||
note-visibility: "投稿の公開範囲"
|
||||
@ -408,9 +413,11 @@ common/views/pages/explore.vue:
|
||||
popular-users: "人気のユーザー"
|
||||
recently-updated-users: "最近投稿したユーザー"
|
||||
recently-registered-users: "新規ユーザー"
|
||||
recently-discovered-users: "最近発見されたユーザー"
|
||||
popular-tags: "人気のタグ"
|
||||
federated: "連合"
|
||||
explore: "{host}を探索"
|
||||
explore-fediverse: "Fediverseを探索"
|
||||
users-info: "現在{users}ユーザーが登録されています"
|
||||
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
@ -881,6 +888,7 @@ common/views/widgets/broadcast.vue:
|
||||
no-broadcasts: "お知らせはありません"
|
||||
have-a-nice-day: "良い一日を!"
|
||||
next: "次"
|
||||
prev: "前"
|
||||
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "{}年"
|
||||
@ -1550,6 +1558,7 @@ admin/views/users.vue:
|
||||
state:
|
||||
title: "状態"
|
||||
all: "すべて"
|
||||
available: "利用可能"
|
||||
admin: "管理者"
|
||||
moderator: "モデレーター"
|
||||
adminOrModerator: "管理者+モデレーター"
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "ドライブ"
|
||||
drive-desc: "「こないだの画像、どこやったかな……また投稿したいんやけど……」「さっきのファイルあのフォルダに直しといて」そんなこと言わんとって。Misskeyはもとからドライブ機能持っとるさかい、心配あらへん。ファイルの「わけわけ」したってな。"
|
||||
outro: "Misskeyの機能は無限大や!知らんけど。知らん言うとるやんけ、あんたが見に行けや!Misskeyは分散型SNSやから、ここがあかんくても他がある。阪神でもオリックスでもワイは応援するで!"
|
||||
adblock:
|
||||
detected: "広告ブロッカーを無効にしてや"
|
||||
warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "さいなら"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
|
||||
@ -129,6 +126,7 @@ common:
|
||||
blocking: "ブロック"
|
||||
password: "パスワード"
|
||||
other: "その他"
|
||||
reactions: "リアクション"
|
||||
timeline: "タイムライン"
|
||||
save: "保存"
|
||||
saved: "保存したで!"
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "드라이브"
|
||||
drive-desc: "이전에 올렸던 적 있는 이미지를 다시 올리고 싶을 때가 있지 않으신가요? 아니면 업로드했던 파일을 폴더로 정리하고 싶지 않으신가요? Misskey에 기본적으로 내장된 드라이브 기능으로 해결됩니다. 파일 공유도 쉽습니다."
|
||||
outro: "이외에도 Misskey에만 있는 기능이 아직도 더 있으니 부디 여러분 자신의 눈으로 확인해보시기 바랍니다. Misskey는 분산형 SNS라서 이 인스턴스가 마음에 들지 않으신다면 다른 인스턴스를 시도해보실 수도 있습니다. 그럼, GLHF!"
|
||||
adblock:
|
||||
detected: "광고 차단기를 해제하십시오"
|
||||
warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong>. 하지만 광고 차단 기능을 사용할 경우 일부 기능을 사용할 수 없거나 문제가 발생할 수 있습니다."
|
||||
application-authorization: "앱 연계"
|
||||
close: "닫기"
|
||||
do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다."
|
||||
@ -86,14 +83,14 @@ common:
|
||||
followers: "팔로워"
|
||||
favorites: "즐겨찾기"
|
||||
permissions:
|
||||
"read:account": "계정의 정보를 볼 수 있습니다."
|
||||
"write:account": "계정의 정보를 변경할 수 있습니다."
|
||||
"read:blocks": "차단 목록을 볼 수 있습니다."
|
||||
"write:blocks": "차단 목록을 조작할 수 있습니다."
|
||||
"read:drive": "드라이브의 파일들을 볼 수 있습니다."
|
||||
"write:drive": "드라이브의 파일들을 조작할 수 있습니다."
|
||||
"read:favorites": "즐겨찾기 목록을 볼 수 있습니다."
|
||||
"write:favorites": "즐겨찾기 목록을 조작할 수 있습니다."
|
||||
"read:account": "계정 정보 보기"
|
||||
"write:account": "계정 정보 변경"
|
||||
"read:blocks": "차단 보기"
|
||||
"write:blocks": "차단 수정"
|
||||
"read:drive": "드라이브 보기"
|
||||
"write:drive": "드라이브 수정"
|
||||
"read:favorites": "즐겨찾기 보기"
|
||||
"write:favorites": "즐겨찾기 수정"
|
||||
"read:following": "팔로우 정보 보기"
|
||||
"write:following": "팔로잉, 팔로우 수정"
|
||||
"read:messaging": "대화 보기"
|
||||
@ -106,6 +103,12 @@ common:
|
||||
"read:reactions": "리액션 보기"
|
||||
"write:reactions": "리액션 수정"
|
||||
"write:votes": "투표하기"
|
||||
"read:pages": "페이지 보기"
|
||||
"write:pages": "페이지 변경"
|
||||
"read:page-likes": "페이지의 좋아요 보기"
|
||||
"write:page-likes": "페이지의 좋아요 변경"
|
||||
"read:user-groups": "유저 그룹 보기"
|
||||
"write:user-groups": "유저 그룹을 변경"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
|
||||
explore: "사용자 탐색"
|
||||
@ -197,6 +200,8 @@ common:
|
||||
other: "기타"
|
||||
appearance: "디자인"
|
||||
behavior: "동작"
|
||||
reactions: "리액션"
|
||||
reactions-description: "리액션 선택창에 표시할 리액션을 줄바꿈으로 구분해 설정합니다."
|
||||
fetch-on-scroll: "스크롤하여 자동으로 불러오기"
|
||||
fetch-on-scroll-desc: "페이지를 아래로 스크롤하였을 때 자동으로 추가 콘텐츠를 불러옵니다."
|
||||
note-visibility: "게시물의 공개 범위"
|
||||
@ -818,6 +823,7 @@ common/views/widgets/broadcast.vue:
|
||||
no-broadcasts: "공지사항이 없습니다"
|
||||
have-a-nice-day: "좋은 하루 되세요!"
|
||||
next: "다음"
|
||||
prev: "이전"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "{}년"
|
||||
month: "{}월"
|
||||
@ -1404,7 +1410,7 @@ admin/views/users.vue:
|
||||
delete-all-files: "모든 파일 삭제"
|
||||
delete-all-files-confirm: "모든 파일을 삭제하시겠습니까?"
|
||||
username: "사용자명"
|
||||
host: "관리자"
|
||||
host: "호스트"
|
||||
users:
|
||||
title: "사용자"
|
||||
sort:
|
||||
@ -1416,6 +1422,7 @@ admin/views/users.vue:
|
||||
state:
|
||||
title: "상태"
|
||||
all: "모두"
|
||||
available: "이용 가능"
|
||||
admin: "관리자"
|
||||
moderator: "모더레이터"
|
||||
adminOrModerator: "관리자+모더레이터"
|
||||
@ -1822,6 +1829,8 @@ pages:
|
||||
page-created: "페이지를 만들었습니다"
|
||||
page-updated: "페이지를 수정했습니다"
|
||||
name-already-exists: "지정한 페이지 URL은 이미 존재합니다"
|
||||
title-invalid-name: "유효하지 않은 페이지 URL입니다"
|
||||
text-invalid-name: "비어있지 않은지 확인해주세요"
|
||||
are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
|
||||
page-deleted: "페이지가 삭제되었습니다"
|
||||
edit-this-page: "이 페이지를 편집"
|
||||
@ -2100,7 +2109,7 @@ room:
|
||||
saved: "저장하였습니다"
|
||||
clear: "모두 치우기"
|
||||
clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?"
|
||||
leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?"
|
||||
leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 나가시겠습니까?"
|
||||
chooseImage: "이미지 선택"
|
||||
room-type: "룸 종류"
|
||||
carpet-color: "바닥 색상"
|
||||
|
@ -3,16 +3,58 @@ meta:
|
||||
lang: "Nederlands"
|
||||
common:
|
||||
misskey: "Deel alles met anderen die ook Misskey gebruiken."
|
||||
about-title: "Een ster van het fediverse"
|
||||
about: "Bedankt voor het ontdekken van Misskey. Misskey is een <b>gedecentraliseerd microblogging platform</b> geboren op aarde. Omdat het bestaat binnen het Fediverse (een georganiseerd universum van verschillende sociale mediaplatformen), staat het verbonden met andere sociale medieplatformen. Neem een pauze van de stedelijke drukte, en duik in het nieuwe intenet?"
|
||||
intro:
|
||||
title: "Wat is Misskey?"
|
||||
about: "Misskey is een open source <b>gedecentraliseerd blogplatform</b>. Het heeft een gesofisticeerde, volledig aanpasbare gebruikersinterface, uitgebreide reactiemogelijkheden voor posts, gratis geïntegreerd bestandsoplagbeheer en andere geavanceerde mogelijkheden. Daarnaast staat Misskey verbonden aan een netwerksysteem genaam het \"Fediverse\", hiermee kunnen we communiceren met andere gebruikers op andere SNSs. Dit betekent dat wanneer je iets post het niet enkel verstuurd wordt naar andere Misskey gebruikers, maar ook naar gebruikers op Mastodon en Pleroma. Stel je voor dat een planeet een radiosignaal verzendt naar een andere planeet als manier van communiceren."
|
||||
features: "Kenmerken"
|
||||
rich-contents: "Bericht"
|
||||
rich-contents-desc: "Post jouw idee, hot topic, wat je ook maar wil delen. Maak jouw teksten aantrekkelijk met je favoriete foto's, verzend bestanden, zelfs video's, of maak een poll. Dit zijn enkele van de mogelijkheden die Misskey aanbiedt!"
|
||||
reaction: "Reactie"
|
||||
reaction-desc: "Dé makkelijkste manier om jouw gevoelens uit te drukken. Met Misskey kan je verschillende reacties toevoegen aan jouw posts. Andere SNSs hebben enkel maar een \"vind ik leuk\" reactie."
|
||||
ui: "Interface"
|
||||
ui-desc: "Niet één UI past nij iedereen. Daarom heeft Misskey een uitgebreide keuze om de UI naar jouw hand te zetten. Je kan jouw nieuwe thuis zo origineel maken als je zelf wil door jouw tijdslijn aan te passen door widgets te verplaatsen en hun look te veranderen. Zo maak je van Misskey jouw eigen stek."
|
||||
drive: "Drive"
|
||||
drive-desc: "Wil je een foto posten die je reeds het geüpload? Wens je georganiseerde map met zelfgekozen naam maken voor al jouw bestanden? De beste oplossing voor jou is Misskey Drive. Dit maakt het supermakkelijk om jouw bestanden online te delen."
|
||||
application-authorization: "Geauthoriseerde applicaties"
|
||||
close: "Sluiten"
|
||||
do-not-copy-paste: "Gelieve de code hier niet in te geven of te plakken. De account kan gecompromiseerd zijn."
|
||||
load-more: "Laad meer resultaten"
|
||||
enter-password: "Voer het wachtwoord in"
|
||||
2fa: "Tweestapsverificatie"
|
||||
customize-home: "Layout aanpassen"
|
||||
featured-notes: "Uitgelicht"
|
||||
dark-mode: "Donker thema"
|
||||
signin: "Aanmelden"
|
||||
signup: "Registreren"
|
||||
signout: "Afmelden"
|
||||
reload-to-apply-the-setting: "Herlaad de pagina om je aanpassingen te bekijken. Wil je de pagina nu herladen?"
|
||||
fetching-as-ap-object: "Verzenden naar Fediverse"
|
||||
unfollow-confirm: "Wil stoppen met {name} te volgen?"
|
||||
delete-confirm: "Ben je zeker dat je dit bericht wil verwijderen?"
|
||||
signin-required: "Gelieve in te loggen"
|
||||
notification-type: "Notificatietype"
|
||||
notification-types:
|
||||
all: "Alle"
|
||||
pollVote: "Stemmen"
|
||||
follow: "Volgend"
|
||||
receiveFollowRequest: "Volgverzoeken"
|
||||
reply: "Beantwoorden"
|
||||
quote: "Bron"
|
||||
mention: "Vermeldingen"
|
||||
reaction: "Reactie"
|
||||
got-it: "Ik snap het!"
|
||||
customization-tips:
|
||||
title: "Aanpassingstips"
|
||||
gotit: "Ik snap het!"
|
||||
notification:
|
||||
file-uploaded: "Je bestand is geüpload"
|
||||
message-from: "Bericht van {}:"
|
||||
reversi-invited: "Uitgenodigd voor spel"
|
||||
notified-by: "Bemerkt door: {}"
|
||||
reply-from: "Antwoord van: {}"
|
||||
quoted-by: "Geciteerd door: {}"
|
||||
time:
|
||||
unknown: "onbekend"
|
||||
future: "toekomstig"
|
||||
@ -24,7 +66,13 @@ common:
|
||||
weeks_ago: "{}week/weken geleden"
|
||||
months_ago: "{}maand(en) geleden"
|
||||
years_ago: "{}jaar geleden"
|
||||
month-and-day: "{day} {month}"
|
||||
trash: "Prullenbak"
|
||||
drive: "Drive"
|
||||
pages: "Pagina's"
|
||||
messaging: "Gesprekken"
|
||||
home: "Startpagina"
|
||||
deck: "Deck"
|
||||
timeline: "Tijdlijn"
|
||||
followers: "Volgers"
|
||||
favorites: "Deze notitie toevoegen aan favorieten"
|
||||
@ -59,7 +107,11 @@ common:
|
||||
profile: "Je profiel"
|
||||
notification: "Meldingen"
|
||||
password: "Wachtwoord"
|
||||
reactions: "Reactie"
|
||||
deck-column-align-center: "Centreren"
|
||||
deck-column-align-left: "Links"
|
||||
timeline: "Tijdlijn"
|
||||
navbar-position-left: "Links"
|
||||
search: "Zoeken"
|
||||
delete: "Verwijderen"
|
||||
loading: "Bezig met laden"
|
||||
@ -423,11 +475,13 @@ admin/views/index.vue:
|
||||
users: "Gebruiker"
|
||||
admin/views/dashboard.vue:
|
||||
notes: "Bericht"
|
||||
drive: "Drive"
|
||||
admin/views/abuse.vue:
|
||||
remove-report: "Verwijderen"
|
||||
admin/views/charts.vue:
|
||||
notes: "Bericht"
|
||||
users: "Gebruiker"
|
||||
drive: "Drive"
|
||||
admin/views/drive.vue:
|
||||
origin:
|
||||
local: "Lokaal"
|
||||
@ -563,6 +617,7 @@ mobile/views/pages/home.vue:
|
||||
global: "Algemeen"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Toevoegen"
|
||||
customization-tips: "Aanpassingstips"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
activity: "Activiteit"
|
||||
mobile/views/pages/note.vue:
|
||||
@ -643,3 +698,4 @@ room:
|
||||
translate: "Verplaatsen"
|
||||
furnitures:
|
||||
moon: "Maan"
|
||||
bin: "Prullenbak"
|
||||
|
@ -14,9 +14,6 @@ common:
|
||||
ui: "Interfejs"
|
||||
drive: "Dysk"
|
||||
drive-desc: "Chcesz opublikować zdjęcie, które już przesłałeś? Chcesz uporządkować, nazwać i utworzyć folder dla przesłanych plików? Dysk Misskey to najlepsze rozwiązanie dla Ciebie. Bardzo łatwo udostępniać swoje pliki online."
|
||||
adblock:
|
||||
detected: "Spróbuj wyłączyć blokadę reklam."
|
||||
warning: "<strong>Misskey nie zawiera reklam</strong>, ale część funkcji może nie działać prawidłowo z włączonym blokowaniem reklam."
|
||||
application-authorization: "Współpraca aplikacji"
|
||||
close: "Zamknij"
|
||||
load-more: "Załaduj więcej"
|
||||
@ -160,6 +157,7 @@ common:
|
||||
other: "Inne"
|
||||
appearance: "Wygląd"
|
||||
behavior: "Zachowanie"
|
||||
reactions: "Reakcja"
|
||||
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
|
||||
note-visibility: "Widoczność wpisów"
|
||||
remember-note-visibility: "Zapamiętaj widoczność wpisów"
|
||||
|
@ -12,9 +12,7 @@ common:
|
||||
rich-contents: "Post"
|
||||
rich-contents-desc: "Apenas poste suas ideias, temas do momento e qualquer coisa que você queira compartilhar. Você pode querer decorar suas palavras, anexar suas imagens favoritas, enviar arquivos, inclusive vídeos ou criar uma enquete. Essas são as coisas que você pode fazer em Misskey."
|
||||
reaction: "Reações"
|
||||
adblock:
|
||||
detected: "Por favor, desative o bloqueador de anúncios."
|
||||
warning: "Alguns recursos podem não estar disponíveis ou apresentar mal funcionamento se o bloqueio de anúncios estiver ativado. <strong>Misskey não está usando anúncios</strong>"
|
||||
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません"
|
||||
application-authorization: "Aplicativos autorizados"
|
||||
close: "Fechar"
|
||||
do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida."
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "Хранилище файлов"
|
||||
drive-desc: "Хотите запостить картинку, которую уже отправляли ранее? Хочется сортировать, переименовать и создать папку для ваших выложенных файлов? Тогда Misskey Drive - это лучшее решение для вас. Очень лёгкий способ делиться своими файлами онлайн."
|
||||
outro: "Попробуйте будущие, уникальные для Misskey функции своими глазами! Если чувствуете, что это не в вашем вкусе, то попробуйте другие инстанции, ведь Misskey - это децентрализованная социальная сеть, так что ты можешь с лёгкостью найти себе товарищей. И наконец, GLHF!"
|
||||
adblock:
|
||||
detected: "Пожалуйста, отключите блокировщик рекламы."
|
||||
warning: "Некоторые функции перестанут быть доступными или будут работать неправильно, если включена блокировка рекламы. <strong>В Misskey нет рекламы</strong>."
|
||||
application-authorization: "Авторизация приложений"
|
||||
close: "Закрыть"
|
||||
do-not-copy-paste: "Пожалуйста, не вводите и не вставляйте сюда код. Аккаунту может угрожать опасность."
|
||||
|
@ -18,9 +18,6 @@ common:
|
||||
drive: "网盘"
|
||||
drive-desc: "想要发布一张您已经上传过的照片吗?想要管理文件或为上传的文件创建文件夹吗?Misskey的网盘是一个最好的解决方案。"
|
||||
outro: "Misskey还有其他更多功能,请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS,如果您感觉某个功能不适合自己,试试其他的吧。祝您玩得开心!"
|
||||
adblock:
|
||||
detected: "请关闭广告拦截器"
|
||||
warning: "<strong>Misskey不会发布广告</strong>如果您启用广告拦截器, 可能会导致某些功能无法正常使用。"
|
||||
application-authorization: "应用程序授权"
|
||||
close: "关闭"
|
||||
do-not-copy-paste: "请不要在这里输入或粘贴代码。您帐户可能会受到损害。"
|
||||
@ -106,6 +103,12 @@ common:
|
||||
"read:reactions": "查看回应"
|
||||
"write:reactions": "回应操作"
|
||||
"write:votes": "投票"
|
||||
"read:pages": "查看页面"
|
||||
"write:pages": "操作页面"
|
||||
"read:page-likes": "查看喜欢的页面"
|
||||
"write:page-likes": "操作喜欢的页面"
|
||||
"read:user-groups": "查看用户组"
|
||||
"write:user-groups": "操作用户组"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
|
||||
explore: "查找用户"
|
||||
@ -197,6 +200,7 @@ common:
|
||||
other: "其他"
|
||||
appearance: "设计"
|
||||
behavior: "行为"
|
||||
reactions: "回应"
|
||||
fetch-on-scroll: "向下滚动时自动加载"
|
||||
fetch-on-scroll-desc: "向下滚动页面时,它会自动提取其他内容。"
|
||||
note-visibility: "帖子可见性"
|
||||
@ -818,6 +822,7 @@ common/views/widgets/broadcast.vue:
|
||||
no-broadcasts: "没有公告"
|
||||
have-a-nice-day: "祝你有愉快的一天!"
|
||||
next: "下一个"
|
||||
prev: "上一首"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "{}年"
|
||||
month: "{}月"
|
||||
@ -1416,6 +1421,7 @@ admin/views/users.vue:
|
||||
state:
|
||||
title: "状态"
|
||||
all: "全部"
|
||||
available: "可用"
|
||||
admin: "管理员"
|
||||
moderator: "版主"
|
||||
adminOrModerator: "管理员+版主"
|
||||
@ -1822,6 +1828,8 @@ pages:
|
||||
page-created: "页面已创建"
|
||||
page-updated: "页面已更新"
|
||||
name-already-exists: "该页面URL已存在"
|
||||
title-invalid-name: "无效的页面URL"
|
||||
text-invalid-name: "请确认该项不为空"
|
||||
are-you-sure-delete: "是否删除此页面?"
|
||||
page-deleted: "该页面已被删除。"
|
||||
edit-this-page: "编辑此页面"
|
||||
|
@ -7,8 +7,6 @@ common:
|
||||
rich-contents: "發佈"
|
||||
reaction: "回應"
|
||||
drive: "雲端硬碟"
|
||||
adblock:
|
||||
detected: "請禁用廣告封鎖器"
|
||||
close: "關閉"
|
||||
enter-password: "請輸入密碼"
|
||||
2fa: "雙重身份驗證"
|
||||
|
145
package.json
145
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.31.3",
|
||||
"version": "11.34.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -27,20 +27,23 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "7.3.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.19",
|
||||
"@fortawesome/free-brands-svg-icons": "5.9.0",
|
||||
"@fortawesome/free-regular-svg-icons": "5.9.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.9.0",
|
||||
"@fortawesome/vue-fontawesome": "0.1.6",
|
||||
"@elastic/elasticsearch": "7.4.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.25",
|
||||
"@fortawesome/free-brands-svg-icons": "5.11.2",
|
||||
"@fortawesome/free-regular-svg-icons": "5.11.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.11.2",
|
||||
"@fortawesome/vue-fontawesome": "0.1.7",
|
||||
"@koa/cors": "3.0.0",
|
||||
"@koa/multer": "2.0.0",
|
||||
"@koa/router": "8.0.2",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.5.15",
|
||||
"@types/bull": "3.10.3",
|
||||
"@types/cbor": "2.0.0",
|
||||
"@types/dateformat": "3.0.0",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/deep-equal": "1.0.1",
|
||||
"@types/double-ended-queue": "2.1.1",
|
||||
"@types/gulp": "4.0.6",
|
||||
@ -52,89 +55,87 @@
|
||||
"@types/is-url": "1.2.28",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/jsdom": "12.2.4",
|
||||
"@types/katex": "0.10.1",
|
||||
"@types/koa": "2.0.49",
|
||||
"@types/katex": "0.10.2",
|
||||
"@types/koa": "2.0.50",
|
||||
"@types/koa-bodyparser": "5.0.2",
|
||||
"@types/koa-compress": "2.0.9",
|
||||
"@types/koa-cors": "0.0.0",
|
||||
"@types/koa-favicon": "2.0.19",
|
||||
"@types/koa-logger": "3.1.1",
|
||||
"@types/koa-mount": "4.0.0",
|
||||
"@types/koa-multer": "1.0.0",
|
||||
"@types/koa-router": "7.0.42",
|
||||
"@types/koa-send": "4.1.2",
|
||||
"@types/koa-views": "2.0.3",
|
||||
"@types/koa__cors": "2.2.3",
|
||||
"@types/koa__multer": "2.0.0",
|
||||
"@types/koa__router": "8.0.0",
|
||||
"@types/lolex": "3.1.1",
|
||||
"@types/mocha": "5.2.7",
|
||||
"@types/node": "12.0.10",
|
||||
"@types/nodemailer": "6.2.0",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/nodemailer": "6.2.1",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/parse5": "5.0.0",
|
||||
"@types/parse5": "5.0.2",
|
||||
"@types/parsimmon": "1.10.0",
|
||||
"@types/portscanner": "2.1.0",
|
||||
"@types/pug": "2.0.4",
|
||||
"@types/qrcode": "1.3.3",
|
||||
"@types/qrcode": "1.3.4",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "2.1.28",
|
||||
"@types/redis": "2.8.13",
|
||||
"@types/redis": "2.8.14",
|
||||
"@types/rename": "1.0.1",
|
||||
"@types/request": "2.48.2",
|
||||
"@types/request-promise-native": "1.0.16",
|
||||
"@types/request": "2.48.3",
|
||||
"@types/request-promise-native": "1.0.17",
|
||||
"@types/request-stats": "3.0.0",
|
||||
"@types/rimraf": "2.0.2",
|
||||
"@types/seedrandom": "2.4.28",
|
||||
"@types/sharp": "0.22.2",
|
||||
"@types/sharp": "0.22.3",
|
||||
"@types/showdown": "1.9.3",
|
||||
"@types/speakeasy": "2.0.4",
|
||||
"@types/speakeasy": "2.0.5",
|
||||
"@types/systeminformation": "3.23.1",
|
||||
"@types/tinycolor2": "1.4.2",
|
||||
"@types/tmp": "0.1.0",
|
||||
"@types/uuid": "3.4.5",
|
||||
"@types/web-push": "3.3.0",
|
||||
"@types/webpack": "4.4.34",
|
||||
"@types/webpack": "4.39.3",
|
||||
"@types/webpack-stream": "3.2.10",
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
"@typescript-eslint/parser": "1.11.0",
|
||||
"agentkeepalive": "4.0.2",
|
||||
"@types/ws": "6.0.3",
|
||||
"@typescript-eslint/parser": "2.3.3",
|
||||
"agentkeepalive": "4.1.0",
|
||||
"animejs": "3.1.0",
|
||||
"apexcharts": "3.8.5",
|
||||
"apexcharts": "3.10.0",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.520.0",
|
||||
"aws-sdk": "2.548.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bootstrap": "4.3.1",
|
||||
"bootstrap-vue": "2.0.0-rc.13",
|
||||
"bull": "3.10.0",
|
||||
"bootstrap-vue": "2.0.4",
|
||||
"bull": "3.11.0",
|
||||
"cafy": "15.1.1",
|
||||
"cbor": "4.3.0",
|
||||
"cbor": "5.0.1",
|
||||
"chai": "4.2.0",
|
||||
"chalk": "2.4.2",
|
||||
"cli-highlight": "2.1.1",
|
||||
"commander": "2.20.0",
|
||||
"commander": "3.0.2",
|
||||
"content-disposition": "0.5.3",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "3.2.0",
|
||||
"cssnano": "4.1.10",
|
||||
"dateformat": "3.0.3",
|
||||
"deep-equal": "1.0.1",
|
||||
"deep-equal": "1.1.0",
|
||||
"diskusage": "1.1.3",
|
||||
"double-ended-queue": "2.1.0-0",
|
||||
"emojilib": "2.4.0",
|
||||
"eslint": "6.1.0",
|
||||
"eslint": "6.5.1",
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"eventemitter3": "4.0.0",
|
||||
"feed": "3.0.0",
|
||||
"file-type": "12.2.0",
|
||||
"feed": "4.0.0",
|
||||
"file-type": "12.3.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-imagemin": "6.1.0",
|
||||
"gulp-mocha": "6.0.0",
|
||||
"gulp-imagemin": "6.1.1",
|
||||
"gulp-mocha": "7.0.2",
|
||||
"gulp-rename": "1.4.0",
|
||||
"gulp-replace": "1.0.0",
|
||||
"gulp-sourcemaps": "2.6.5",
|
||||
@ -146,38 +147,37 @@
|
||||
"hard-source-webpack-plugin": "0.13.1",
|
||||
"html-minifier": "4.0.0",
|
||||
"http-signature": "1.2.0",
|
||||
"https-proxy-agent": "2.2.2",
|
||||
"https-proxy-agent": "3.0.0",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"is-root": "2.1.0",
|
||||
"is-svg": "4.2.0",
|
||||
"js-yaml": "3.13.1",
|
||||
"jsdom": "15.1.1",
|
||||
"json5": "2.1.0",
|
||||
"json5": "2.1.1",
|
||||
"json5-loader": "3.0.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"katex": "0.11.0",
|
||||
"koa": "2.8.1",
|
||||
"katex": "0.11.1",
|
||||
"koa": "2.10.0",
|
||||
"koa-bodyparser": "4.2.1",
|
||||
"koa-compress": "3.0.0",
|
||||
"koa-favicon": "2.0.1",
|
||||
"koa-json-body": "5.3.0",
|
||||
"koa-logger": "3.2.1",
|
||||
"koa-mount": "4.0.0",
|
||||
"koa-multer": "1.0.2",
|
||||
"koa-router": "7.4.0",
|
||||
"koa-send": "5.0.0",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.2.0",
|
||||
"koa-views": "6.2.1",
|
||||
"langmap": "0.0.16",
|
||||
"loader-utils": "1.2.3",
|
||||
"lolex": "4.2.0",
|
||||
"lookup-dns-cache": "2.1.0",
|
||||
"mocha": "6.2.0",
|
||||
"mocha": "6.2.1",
|
||||
"moji": "0.5.1",
|
||||
"ms": "2.1.2",
|
||||
"multer": "1.4.2",
|
||||
"nested-property": "1.0.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"nodemailer": "6.3.0",
|
||||
"nodemailer": "6.3.1",
|
||||
"nprogress": "0.2.0",
|
||||
"object-assign-deep": "0.4.0",
|
||||
"os-utils": "0.0.14",
|
||||
@ -193,49 +193,50 @@
|
||||
"pug": "2.0.4",
|
||||
"punycode": "2.1.1",
|
||||
"pureimage": "0.1.6",
|
||||
"qrcode": "1.4.1",
|
||||
"qrcode": "1.4.2",
|
||||
"random-seed": "0.3.0",
|
||||
"randomcolor": "0.5.4",
|
||||
"ratelimiter": "3.3.1",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.2.0",
|
||||
"redis": "2.8.0",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rename": "1.0.4",
|
||||
"request": "2.88.0",
|
||||
"request-promise-native": "1.0.7",
|
||||
"request-stats": "3.0.0",
|
||||
"require-all": "3.0.0",
|
||||
"rimraf": "2.6.3",
|
||||
"rimraf": "3.0.0",
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"seedrandom": "3.0.3",
|
||||
"sharp": "0.23.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"sharp": "0.23.1",
|
||||
"showdown": "1.9.0",
|
||||
"showdown-highlightjs-extension": "0.1.2",
|
||||
"speakeasy": "2.0.0",
|
||||
"stringz": "2.0.0",
|
||||
"style-loader": "0.23.1",
|
||||
"style-loader": "1.0.0",
|
||||
"stylus": "0.54.7",
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.3.1",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "4.14.8",
|
||||
"systeminformation": "4.14.11",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "1.4.1",
|
||||
"terser-webpack-plugin": "2.1.3",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.108.0",
|
||||
"three": "0.109.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.1.0",
|
||||
"ts-loader": "5.3.3",
|
||||
"ts-node": "7.0.1",
|
||||
"tslint": "5.18.0",
|
||||
"ts-loader": "6.2.0",
|
||||
"ts-node": "8.4.1",
|
||||
"tslint": "5.20.0",
|
||||
"tslint-sonarts": "1.9.0",
|
||||
"typeorm": "0.2.18",
|
||||
"typescript": "3.5.3",
|
||||
"typeorm": "0.2.19",
|
||||
"typescript": "3.6.4",
|
||||
"uglify-es": "3.3.9",
|
||||
"ulid": "2.3.0",
|
||||
"url-loader": "2.1.0",
|
||||
"url-loader": "2.2.0",
|
||||
"uuid": "3.3.3",
|
||||
"v-animate-css": "0.0.3",
|
||||
"v-debounce": "0.1.2",
|
||||
@ -243,25 +244,25 @@
|
||||
"vue-color": "2.7.0",
|
||||
"vue-content-loading": "1.6.0",
|
||||
"vue-cropperjs": "4.0.0",
|
||||
"vue-i18n": "8.14.0",
|
||||
"vue-i18n": "8.14.1",
|
||||
"vue-js-modal": "1.3.31",
|
||||
"vue-json-pretty": "1.6.1",
|
||||
"vue-json-pretty": "1.6.2",
|
||||
"vue-loader": "15.7.1",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
"vue-prism-component": "1.1.1",
|
||||
"vue-router": "3.1.2",
|
||||
"vue-router": "3.1.3",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.17",
|
||||
"vue-svg-inline-loader": "1.3.3",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.23.0",
|
||||
"vuedraggable": "2.23.2",
|
||||
"vuewordcloud": "18.7.11",
|
||||
"vuex": "3.1.1",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"web-push": "3.3.5",
|
||||
"webpack": "4.39.3",
|
||||
"webpack-cli": "3.3.7",
|
||||
"websocket": "1.0.29",
|
||||
"web-push": "3.4.0",
|
||||
"webpack": "4.41.1",
|
||||
"webpack-cli": "3.3.9",
|
||||
"websocket": "1.0.30",
|
||||
"ws": "7.1.2",
|
||||
"xev": "2.0.1"
|
||||
},
|
||||
|
@ -509,7 +509,7 @@ export default Vue.extend({
|
||||
|
||||
saveBlockedHosts() {
|
||||
this.$root.api('admin/update-meta', {
|
||||
blockedHosts: this.blockedHosts.split('\n')
|
||||
blockedHosts: this.blockedHosts ? this.blockedHosts.split('\n') : []
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
|
@ -461,7 +461,7 @@ export default Vue.extend({
|
||||
iconUrl: this.iconUrl,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
langs: this.languages.split(' '),
|
||||
langs: this.languages ? this.languages.split(' ') : [],
|
||||
cacheRemoteFiles: this.cacheRemoteFiles,
|
||||
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
|
||||
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
|
||||
@ -490,8 +490,8 @@ export default Vue.extend({
|
||||
enableServiceWorker: this.enableServiceWorker,
|
||||
swPublicKey: this.swPublicKey,
|
||||
swPrivateKey: this.swPrivateKey,
|
||||
pinnedUsers: this.pinnedUsers.split('\n'),
|
||||
hiddenTags: this.hiddenTags.split('\n'),
|
||||
pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
|
||||
hiddenTags: this.hiddenTags ? this.hiddenTags.split('\n') : [],
|
||||
useObjectStorage: this.useObjectStorage,
|
||||
objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
|
||||
objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
|
||||
|
@ -42,6 +42,7 @@
|
||||
<ui-select v-model="state">
|
||||
<template #label>{{ $t('users.state.title') }}</template>
|
||||
<option value="all">{{ $t('users.state.all') }}</option>
|
||||
<option value="available">{{ $t('users.state.available') }}</option>
|
||||
<option value="admin">{{ $t('users.state.admin') }}</option>
|
||||
<option value="moderator">{{ $t('users.state.moderator') }}</option>
|
||||
<option value="silenced">{{ $t('users.state.silenced') }}</option>
|
||||
|
@ -98,15 +98,10 @@
|
||||
|
||||
// If mobile, insert the viewport meta tag
|
||||
if (isMobile) {
|
||||
const meta = document.createElement('meta');
|
||||
meta.setAttribute('name', 'viewport');
|
||||
meta.setAttribute('content',
|
||||
'width=device-width,' +
|
||||
'initial-scale=1,' +
|
||||
'minimum-scale=1,' +
|
||||
'maximum-scale=1,' +
|
||||
'user-scalable=no');
|
||||
head.appendChild(meta);
|
||||
const viewport = document.getElementsByName("viewport").item(0);
|
||||
viewport.setAttribute('content',
|
||||
`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
|
||||
head.appendChild(viewport);
|
||||
}
|
||||
|
||||
// Switch desktop or mobile version
|
||||
|
@ -1,18 +0,0 @@
|
||||
declare const fuckAdBlock: any;
|
||||
|
||||
export default ($root: any) => {
|
||||
require('fuckadblock');
|
||||
|
||||
function adBlockDetected() {
|
||||
$root.dialog({
|
||||
title: $root.$t('@.adblock.detected'),
|
||||
text: $root.$t('@.adblock.warning')
|
||||
});
|
||||
}
|
||||
|
||||
if (fuckAdBlock === undefined) {
|
||||
adBlockDetected();
|
||||
} else {
|
||||
fuckAdBlock.onDetected(adBlockDetected);
|
||||
}
|
||||
};
|
@ -143,12 +143,15 @@ export default (opts: Opts = {}) => ({
|
||||
react(viaKeyboard = false) {
|
||||
pleaseLogin(this.$root);
|
||||
this.blur();
|
||||
this.$root.new(MkReactionPicker, {
|
||||
const w = this.$root.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.appearNote,
|
||||
showFocus: viaKeyboard,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.close();
|
||||
});
|
||||
},
|
||||
|
||||
reactDirectly(reaction) {
|
||||
@ -195,7 +198,7 @@ export default (opts: Opts = {}) => ({
|
||||
menu(viaKeyboard = false) {
|
||||
if (this.openingMenu) return;
|
||||
this.openingMenu = true;
|
||||
this.$root.new(MkNoteMenu, {
|
||||
const w = this.$root.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.appearNote,
|
||||
animation: !viaKeyboard
|
||||
@ -203,6 +206,9 @@ export default (opts: Opts = {}) => ({
|
||||
this.openingMenu = false;
|
||||
this.focus();
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
toggleShowContent() {
|
||||
|
@ -328,6 +328,9 @@ export default (opts) => ({
|
||||
w.$once('chosen', v => {
|
||||
this.applyVisibility(v);
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.close();
|
||||
});
|
||||
},
|
||||
|
||||
applyVisibility(v: string) {
|
||||
@ -457,6 +460,9 @@ export default (opts) => ({
|
||||
vm.$once('chosen', emoji => {
|
||||
insertTextAtCursor(this.$refs.text, emoji);
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
vm.close();
|
||||
});
|
||||
},
|
||||
|
||||
saveDraft() {
|
||||
|
@ -28,9 +28,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as emojilib from 'emojilib';
|
||||
import { emojilist } from '../../../../../misc/emojilist';
|
||||
import contains from '../../../common/scripts/contains';
|
||||
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||
import { twemojiSvgBase } from '../../../../../misc/twemoji-base';
|
||||
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
||||
|
||||
type EmojiDef = {
|
||||
@ -41,9 +41,7 @@ type EmojiDef = {
|
||||
isCustomEmoji?: boolean;
|
||||
};
|
||||
|
||||
const lib = Object.entries(emojilib.lib).filter((x: any) => {
|
||||
return x[1].category != 'flags';
|
||||
});
|
||||
const lib = emojilist.filter(x => x.category !== 'flags');
|
||||
|
||||
const char2file = (char: string) => {
|
||||
let codes = Array.from(char).map(x => x.codePointAt(0).toString(16));
|
||||
@ -52,21 +50,21 @@ const char2file = (char: string) => {
|
||||
return codes.join('-');
|
||||
};
|
||||
|
||||
const emjdb: EmojiDef[] = lib.map((x: any) => ({
|
||||
emoji: x[1].char,
|
||||
name: x[0],
|
||||
const emjdb: EmojiDef[] = lib.map(x => ({
|
||||
emoji: x.char,
|
||||
name: x.name,
|
||||
aliasOf: null,
|
||||
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||
url: `${twemojiSvgBase}/${char2file(x.char)}.svg`
|
||||
}));
|
||||
|
||||
for (const x of lib as any) {
|
||||
if (x[1].keywords) {
|
||||
for (const k of x[1].keywords) {
|
||||
for (const x of lib) {
|
||||
if (x.keywords) {
|
||||
for (const k of x.keywords) {
|
||||
emjdb.push({
|
||||
emoji: x[1].char,
|
||||
emoji: x.char,
|
||||
name: k,
|
||||
aliasOf: x[0],
|
||||
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||
aliasOf: x.name,
|
||||
url: `${twemojiSvgBase}/${char2file(x.char)}.svg`
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -85,7 +83,7 @@ export default Vue.extend({
|
||||
hashtags: [],
|
||||
emojis: [],
|
||||
select: -1,
|
||||
emojilib,
|
||||
emojilist,
|
||||
emojiDb: [] as EmojiDef[]
|
||||
}
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-connect-failed">
|
||||
<img src="https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg" alt=""/>
|
||||
<img src="/assets/error.jpg" onerror="this.src='https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg';" alt=""/>
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
<p class="text">
|
||||
<span>{{ this.$t('description').substr(0, this.$t('description').indexOf('{')) }}</span>
|
||||
|
@ -5,6 +5,7 @@
|
||||
:title="category.text"
|
||||
@click="go(category)"
|
||||
:class="{ active: category.isActive }"
|
||||
:key="category.text"
|
||||
>
|
||||
<fa :icon="category.icon" fixed-width/>
|
||||
</button>
|
||||
@ -12,18 +13,19 @@
|
||||
<div class="emojis">
|
||||
<header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
|
||||
<div v-if="categories.find(x => x.isActive).name">
|
||||
<button v-for="emoji in Object.entries(lib).filter(([k, v]) => v.category === categories.find(x => x.isActive).name)"
|
||||
:title="emoji[0]"
|
||||
@click="chosen(emoji[1].char)"
|
||||
:key="emoji[0]"
|
||||
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
|
||||
:title="emoji.name"
|
||||
@click="chosen(emoji.char)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<mk-emoji :emoji="emoji[1].char"/>
|
||||
<mk-emoji :emoji="emoji.char"/>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button v-for="emoji in customEmojis"
|
||||
:title="emoji.name"
|
||||
@click="chosen(`:${emoji.name}:`)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<img :src="emoji.url" :alt="emoji.name"/>
|
||||
</button>
|
||||
@ -35,7 +37,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { lib } from 'emojilib';
|
||||
import { emojilist } from '../../../../../misc/emojilist';
|
||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
@ -44,7 +46,7 @@ export default Vue.extend({
|
||||
|
||||
data() {
|
||||
return {
|
||||
lib,
|
||||
emojilist,
|
||||
customEmojis: [],
|
||||
categories: [{
|
||||
text: this.$t('custom-emoji'),
|
||||
|
@ -7,10 +7,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
// スクリプトサイズがデカい
|
||||
//import { lib } from 'emojilib';
|
||||
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
||||
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||
import { twemojiSvgBase } from '../../../../../misc/twemoji-base';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -92,7 +90,7 @@ export default Vue.extend({
|
||||
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
|
||||
codes = codes.filter(x => x && x.length);
|
||||
|
||||
this.url = `${twemojiBase}/2/svg/${codes.join('-')}.svg`;
|
||||
this.url = `${twemojiSvgBase}/${codes.join('-')}.svg`;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
49
src/client/app/common/views/components/frac.vue
Normal file
49
src/client/app/common/views/components/frac.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<span class="mk-frac"><span>{{ pad }}</span><span>{{ value }} / {{ total }}</span></span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pad(this: {
|
||||
value: number;
|
||||
total: number;
|
||||
length(value: number): number;
|
||||
}) {
|
||||
return '0'.repeat(this.length(this.total) - this.length(this.value));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
length(value: number) {
|
||||
const string = value.toString();
|
||||
|
||||
return string.includes('e') ? -~string.substr(string.indexOf('e')) : string.length;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-frac
|
||||
-webkit-font-feature-settings 'tnum'
|
||||
-moz-font-feature-settings 'tnum'
|
||||
font-feature-settings 'tnum'
|
||||
font-variant-numeric tabular-nums
|
||||
|
||||
> :first-child
|
||||
visibility hidden
|
||||
</style>
|
@ -33,6 +33,7 @@ import fileTypeIcon from './file-type-icon.vue';
|
||||
import emoji from './emoji.vue';
|
||||
import welcomeTimeline from './welcome-timeline.vue';
|
||||
import userList from './user-list.vue';
|
||||
import frac from './frac.vue';
|
||||
import uiInput from './ui/input.vue';
|
||||
import uiButton from './ui/button.vue';
|
||||
import uiHorizonGroup from './ui/horizon-group.vue';
|
||||
@ -82,6 +83,7 @@ Vue.component('mk-file-type-icon', fileTypeIcon);
|
||||
Vue.component('mk-emoji', emoji);
|
||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
||||
Vue.component('mk-user-list', userList);
|
||||
Vue.component('mk-frac', frac);
|
||||
Vue.component('ui-input', uiInput);
|
||||
Vue.component('ui-button', uiButton);
|
||||
Vue.component('ui-horizon-group', uiHorizonGroup);
|
||||
|
@ -59,9 +59,12 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$root.new(ImageViewer, {
|
||||
const viewer = this.$root.new(ImageViewer, {
|
||||
image: this.image
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
viewer.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4,16 +4,7 @@
|
||||
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
|
||||
<p v-if="!$root.isMobile">{{ title }}</p>
|
||||
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
|
||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
|
||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
|
||||
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" :title="$t('@.reactions.hmm')" v-particle><mk-reaction-icon reaction="hmm"/></button>
|
||||
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" :title="$t('@.reactions.surprise')" v-particle><mk-reaction-icon reaction="surprise"/></button>
|
||||
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" :title="$t('@.reactions.congrats')" v-particle><mk-reaction-icon reaction="congrats"/></button>
|
||||
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" :title="$t('@.reactions.angry')" v-particle><mk-reaction-icon reaction="angry"/></button>
|
||||
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" :title="$t('@.reactions.confused')" v-particle><mk-reaction-icon reaction="confused"/></button>
|
||||
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
|
||||
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
|
||||
<button v-for="(reaction, i) in $store.state.settings.reactions" :key="reaction" @click="react(reaction)" @mouseover="onMouseover" @mouseout="onMouseout" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction" v-particle><mk-reaction-icon :reaction="reaction"/></button>
|
||||
</div>
|
||||
<div v-if="enableEmojiReaction" class="text">
|
||||
<input v-model="text" :placeholder="$t('input-reaction-placeholder')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||
|
@ -1,22 +1,27 @@
|
||||
<template>
|
||||
<transition name="zoom-in-top">
|
||||
<div class="buebdbiu" ref="popover" v-if="show">
|
||||
<i18n path="few-users" v-if="users.length <= 10">
|
||||
<span slot="users">
|
||||
<b v-for="u in users" :key="u.id" style="margin-right: 8px;">
|
||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||
</b>
|
||||
</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
<i18n path="many-users" v-if="10 < users.length">
|
||||
<span slot="users">{{ users.slice(0, 10).join(', ') }}</span>
|
||||
<span slot="ommited">{{ count - 10 }}</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="zoom-in-top">
|
||||
<div class="buebdbiu" ref="popover" v-if="show">
|
||||
<i18n path="few-users" v-if="users.length <= 10">
|
||||
<span slot="users">
|
||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||
</b>
|
||||
</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
<i18n path="many-users" v-if="10 < users.length">
|
||||
<span slot="users">
|
||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||
</b>
|
||||
</span>
|
||||
<span slot="omitted">{{ count - 10 }}</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -82,7 +87,7 @@ export default Vue.extend({
|
||||
position absolute
|
||||
max-width 240px
|
||||
font-size 0.8em
|
||||
padding 5px 8px
|
||||
padding 6px 8px
|
||||
background $bgcolor
|
||||
text-align center
|
||||
color var(--text)
|
||||
|
@ -108,6 +108,13 @@
|
||||
<ui-switch v-if="$root.isMobile" v-model="disableViaMobile">{{ $t('@._settings.disable-via-mobile') }}</ui-switch>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('@._settings.reactions') }}</header>
|
||||
<ui-textarea v-model="reactions">
|
||||
{{ $t('@._settings.reactions') }}<template #desc>{{ $t('@._settings.reactions-description') }}</template>
|
||||
</ui-textarea>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('@._settings.timeline') }}</header>
|
||||
<ui-switch v-model="showMyRenotes">{{ $t('@._settings.show-my-renotes') }}</ui-switch>
|
||||
@ -407,6 +414,11 @@ export default Vue.extend({
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
|
||||
},
|
||||
|
||||
reactions: {
|
||||
get() { return this.$store.state.settings.reactions.join('\n'); },
|
||||
set(value: string) { this.$store.dispatch('settings/set', { key: 'reactions', value: value.split('\n') }); }
|
||||
},
|
||||
|
||||
useShadow: {
|
||||
get() { return this.$store.state.device.useShadow; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'useShadow', value }); }
|
||||
|
@ -6,7 +6,10 @@
|
||||
<div class="value" ref="passwordMetar"></div>
|
||||
</div>
|
||||
<span class="label" ref="label"><slot></slot></span>
|
||||
<span class="title" ref="title"><slot name="title"></slot></span>
|
||||
<span class="title" ref="title">
|
||||
<slot name="title"></slot>
|
||||
<span class="warning" v-if="invalid"><fa :icon="['fa', 'exclamation-circle']"/>{{ $refs.input.validationMessage }}</span>
|
||||
</span>
|
||||
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||
<template v-if="type != 'file'">
|
||||
<input v-if="debounce" ref="input"
|
||||
@ -158,6 +161,7 @@ export default Vue.extend({
|
||||
return {
|
||||
v: this.value,
|
||||
focused: false,
|
||||
invalid: false,
|
||||
passwordStrength: '',
|
||||
id: Math.random().toString()
|
||||
};
|
||||
@ -200,6 +204,8 @@ export default Vue.extend({
|
||||
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
||||
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
|
||||
}
|
||||
|
||||
this.invalid = this.$refs.input.validity.badInput;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -365,6 +371,17 @@ root(fill)
|
||||
//will-change transform
|
||||
transform-origin top left
|
||||
transform scale(.75)
|
||||
white-space nowrap
|
||||
width 133%
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
|
||||
> .warning
|
||||
margin-left 0.5em
|
||||
color var(--infoWarnFg)
|
||||
|
||||
> svg
|
||||
margin-right 0.1em
|
||||
|
||||
> input
|
||||
display block
|
||||
|
@ -176,6 +176,9 @@ root(fill)
|
||||
color rgba(#000, 0.54)
|
||||
pointer-events none
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> *
|
||||
display block
|
||||
min-width 16px
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ui-container :body-togglable="true">
|
||||
<ui-container :body-togglable="true" :expanded="expanded">
|
||||
<template #header><slot></slot></template>
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="description" v-if="user.description" :title="user.description">
|
||||
<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :plain="true" :nowrap="true"/>
|
||||
</div>
|
||||
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
|
||||
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching">
|
||||
@ -50,7 +50,11 @@ export default Vue.extend({
|
||||
iconOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
@ -134,7 +138,7 @@ export default Vue.extend({
|
||||
font-size 14px
|
||||
padding-right 40px
|
||||
|
||||
> .follow-button
|
||||
> .koudoku-button
|
||||
position absolute
|
||||
top 8px
|
||||
right 0
|
||||
|
@ -112,10 +112,13 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
menu() {
|
||||
this.$root.new(XUserMenu, {
|
||||
const w = this.$root.new(XUserMenu, {
|
||||
source: this.$refs.menu,
|
||||
user: this.user
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -240,7 +240,7 @@ class Autocomplete {
|
||||
// キャレットを戻す
|
||||
this.vm.$nextTick(() => {
|
||||
this.textarea.focus();
|
||||
const pos = trimmedBefore.length + (value.startsWith(':') ? value.length : 1);
|
||||
const pos = trimmedBefore.length + value.length;
|
||||
this.textarea.setSelectionRange(pos, pos);
|
||||
});
|
||||
}
|
||||
|
@ -1,15 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<ui-container :show-header="false" v-if="meta && stats">
|
||||
<div class="kpdsmpnk" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
|
||||
<div>
|
||||
<router-link to="/explore" class="title">{{ $t('explore', { host: meta.name || 'Misskey' }) }}</router-link>
|
||||
<span>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ui-container>
|
||||
<div class="localfedi7" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
|
||||
<header>{{ $t('explore', { host: meta.name }) }}</header>
|
||||
<div>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</div>
|
||||
</div>
|
||||
|
||||
<ui-container :body-togglable="true" :expanded="tag == null" ref="tags">
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :pagination="pinnedUsers" :expanded="false">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="popularUsers" :expanded="false">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyUpdatedUsers" :expanded="false">
|
||||
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyRegisteredUsers" :expanded="false">
|
||||
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
|
||||
</mk-user-list>
|
||||
</template>
|
||||
|
||||
<div class="localfedi7" v-if="tag == null" :style="{ backgroundImage: `url(/assets/fedi.jpg)` }">
|
||||
<header>{{ $t('explore-fediverse') }}</header>
|
||||
</div>
|
||||
|
||||
<ui-container :body-togglable="true" :expanded="false" ref="tags">
|
||||
<template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template>
|
||||
|
||||
<div class="vxjfqztj">
|
||||
@ -18,25 +33,18 @@
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}-local`">
|
||||
<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}`">
|
||||
<fa :icon="faHashtag" fixed-width/>{{ tag }}
|
||||
</mk-user-list>
|
||||
<mk-user-list v-if="tag != null" :pagination="tagRemoteUsers" :key="`${tag}-remote`">
|
||||
<fa :icon="faHashtag" fixed-width/>{{ tag }} ({{ $t('federated') }})
|
||||
</mk-user-list>
|
||||
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :pagination="pinnedUsers">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="popularUsers">
|
||||
<mk-user-list :pagination="popularUsersF" :expanded="false">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyUpdatedUsers">
|
||||
<mk-user-list :pagination="recentlyUpdatedUsersF" :expanded="false">
|
||||
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyRegisteredUsers">
|
||||
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
|
||||
<mk-user-list :pagination="recentlyRegisteredUsersF" :expanded="false">
|
||||
<fa :icon="faRocket" fixed-width/>{{ $t('recently-discovered-users') }}
|
||||
</mk-user-list>
|
||||
</template>
|
||||
</div>
|
||||
@ -45,7 +53,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { faChartLine, faPlus, faHashtag } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faChartLine, faPlus, faHashtag, faRocket } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -58,6 +66,12 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
inject: {
|
||||
inNakedDeckColumn: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
pinnedUsers: { endpoint: 'pinned-users' },
|
||||
@ -75,12 +89,25 @@ export default Vue.extend({
|
||||
state: 'alive',
|
||||
sort: '+createdAt',
|
||||
} },
|
||||
popularUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
state: 'alive',
|
||||
origin: 'combined',
|
||||
sort: '+follower',
|
||||
} },
|
||||
recentlyUpdatedUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'combined',
|
||||
sort: '+updatedAt',
|
||||
} },
|
||||
recentlyRegisteredUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'combined',
|
||||
sort: '+createdAt',
|
||||
} },
|
||||
tagsLocal: [],
|
||||
tagsRemote: [],
|
||||
stats: null,
|
||||
meta: null,
|
||||
num: Vue.filter('number'),
|
||||
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag
|
||||
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag, faRocket
|
||||
};
|
||||
},
|
||||
|
||||
@ -91,21 +118,7 @@ export default Vue.extend({
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
tagRemoteUsers(): any {
|
||||
return {
|
||||
endpoint: 'hashtags/users',
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'remote',
|
||||
origin: 'combined',
|
||||
sort: '+follower',
|
||||
}
|
||||
};
|
||||
@ -152,6 +165,28 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.localfedi7
|
||||
overflow hidden
|
||||
background var(--face)
|
||||
color #fff
|
||||
text-shadow 0 0 8px #000
|
||||
border-radius 6px
|
||||
padding 16px
|
||||
margin-top 16px
|
||||
margin-bottom 16px
|
||||
height 80px
|
||||
background-position 50%
|
||||
background-size cover
|
||||
> header
|
||||
font-size 20px
|
||||
font-weight bold
|
||||
> div
|
||||
font-size 14px
|
||||
opacity 0.8
|
||||
|
||||
.localfedi7:first-child
|
||||
margin-top 0
|
||||
|
||||
.vxjfqztj
|
||||
padding 16px
|
||||
|
||||
@ -160,35 +195,4 @@ export default Vue.extend({
|
||||
|
||||
&.local
|
||||
font-weight bold
|
||||
|
||||
.kpdsmpnk
|
||||
min-height 100px
|
||||
padding 16px
|
||||
background-position center
|
||||
background-size cover
|
||||
|
||||
&:before
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(0, 0, 0, 0.3)
|
||||
|
||||
> div
|
||||
color #fff
|
||||
text-shadow 0 0 8px #00
|
||||
|
||||
> .title
|
||||
display block
|
||||
font-size 20px
|
||||
font-weight bold
|
||||
color inherit
|
||||
|
||||
> span
|
||||
font-size 14px
|
||||
opacity 0.8
|
||||
|
||||
</style>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<mk-user-name :user="req.follower"/>
|
||||
</router-link>
|
||||
<span>
|
||||
<a @click="accept(req.follower)">{{ $t('accept') }}</a>|<a @click="reject(req.follower)">{{ $t('reject') }}</a>
|
||||
<a @click="accept(req.follower)">{{ $t('accept') }}</a> | <a @click="reject(req.follower)">{{ $t('reject') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
@ -70,5 +70,6 @@ export default Vue.extend({
|
||||
|
||||
> span
|
||||
margin 0 0 0 auto
|
||||
color var(--text)
|
||||
|
||||
</style>
|
||||
|
@ -1,28 +1,36 @@
|
||||
<template>
|
||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi">
|
||||
<ui-container :show-header="false" :naked="props.design == 1">
|
||||
<ui-container :show-header="false" :naked="props.design === 1">
|
||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi-body"
|
||||
:data-found="announcements && announcements.length != 0"
|
||||
:data-found="announcements && announcements.length !== 0"
|
||||
:data-melt="props.design == 1"
|
||||
:data-mobile="platform == 'mobile'"
|
||||
>
|
||||
<div class="icon">
|
||||
<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">
|
||||
<path class="tower" d="M16.04,11.24c1.79,0,3.239-1.45,3.239-3.24S17.83,4.76,16.04,4.76c-1.79,0-3.24,1.45-3.24,3.24 C12.78,9.78,14.24,11.24,16.04,11.24z M16.04,13.84c-0.82,0-1.66-0.2-2.4-0.6L7.34,29.98h2.98l1.72-2h8l1.681,2H24.7L18.42,13.24 C17.66,13.64,16.859,13.84,16.04,13.84z M16.02,14.8l2.02,7.2h-4L16.02,14.8z M12.04,25.98l2-2h4l2,2H12.04z"></path>
|
||||
<path class="wave a" d="M4.66,1.04c-0.508-0.508-1.332-0.508-1.84,0c-1.86,1.92-2.8,4.44-2.8,6.94c0,2.52,0.94,5.04,2.8,6.96 c0.5,0.52,1.32,0.52,1.82,0s0.5-1.36,0-1.88C3.28,11.66,2.6,9.82,2.6,7.98S3.28,4.3,4.64,2.9C5.157,2.391,5.166,1.56,4.66,1.04z"></path>
|
||||
<path class="wave b" d="M9.58,12.22c0.5-0.5,0.5-1.34,0-1.84C8.94,9.72,8.62,8.86,8.62,8s0.32-1.72,0.96-2.38c0.5-0.52,0.5-1.34,0-1.84 C9.346,3.534,9.02,3.396,8.68,3.4c-0.32,0-0.66,0.12-0.9,0.38C6.64,4.94,6.08,6.48,6.08,8s0.58,3.06,1.7,4.22 C8.28,12.72,9.1,12.72,9.58,12.22z"></path>
|
||||
<path class="wave c" d="M22.42,3.78c-0.5,0.5-0.5,1.34,0,1.84c0.641,0.66,0.96,1.52,0.96,2.38s-0.319,1.72-0.96,2.38c-0.5,0.52-0.5,1.34,0,1.84 c0.487,0.497,1.285,0.505,1.781,0.018c0.007-0.006,0.013-0.012,0.02-0.018c1.139-1.16,1.699-2.7,1.699-4.22s-0.561-3.06-1.699-4.22 c-0.494-0.497-1.297-0.5-1.794-0.007C22.424,3.775,22.422,3.778,22.42,3.78z"></path>
|
||||
<path class="wave d" d="M29.18,1.06c-0.479-0.502-1.273-0.522-1.775-0.044c-0.016,0.015-0.029,0.029-0.045,0.044c-0.5,0.52-0.5,1.36,0,1.88 c1.361,1.4,2.041,3.24,2.041,5.08s-0.68,3.66-2.041,5.08c-0.5,0.52-0.5,1.36,0,1.88c0.509,0.508,1.332,0.508,1.841,0 c1.86-1.92,2.8-4.44,2.8-6.96C31.99,5.424,30.98,2.931,29.18,1.06z"></path>
|
||||
</svg>
|
||||
<div class="broadcast-left" v-show="announcements && announcements.length !== 0">
|
||||
<div class="icon">
|
||||
<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">
|
||||
<path class="tower" d="M16.04,11.24c1.79,0,3.239-1.45,3.239-3.24S17.83,4.76,16.04,4.76c-1.79,0-3.24,1.45-3.24,3.24 C12.78,9.78,14.24,11.24,16.04,11.24z M16.04,13.84c-0.82,0-1.66-0.2-2.4-0.6L7.34,29.98h2.98l1.72-2h8l1.681,2H24.7L18.42,13.24 C17.66,13.64,16.859,13.84,16.04,13.84z M16.02,14.8l2.02,7.2h-4L16.02,14.8z M12.04,25.98l2-2h4l2,2H12.04z"></path>
|
||||
<path class="wave a" d="M4.66,1.04c-0.508-0.508-1.332-0.508-1.84,0c-1.86,1.92-2.8,4.44-2.8,6.94c0,2.52,0.94,5.04,2.8,6.96 c0.5,0.52,1.32,0.52,1.82,0s0.5-1.36,0-1.88C3.28,11.66,2.6,9.82,2.6,7.98S3.28,4.3,4.64,2.9C5.157,2.391,5.166,1.56,4.66,1.04z"></path>
|
||||
<path class="wave b" d="M9.58,12.22c0.5-0.5,0.5-1.34,0-1.84C8.94,9.72,8.62,8.86,8.62,8s0.32-1.72,0.96-2.38c0.5-0.52,0.5-1.34,0-1.84 C9.346,3.534,9.02,3.396,8.68,3.4c-0.32,0-0.66,0.12-0.9,0.38C6.64,4.94,6.08,6.48,6.08,8s0.58,3.06,1.7,4.22 C8.28,12.72,9.1,12.72,9.58,12.22z"></path>
|
||||
<path class="wave c" d="M22.42,3.78c-0.5,0.5-0.5,1.34,0,1.84c0.641,0.66,0.96,1.52,0.96,2.38s-0.319,1.72-0.96,2.38c-0.5,0.52-0.5,1.34,0,1.84 c0.487,0.497,1.285,0.505,1.781,0.018c0.007-0.006,0.013-0.012,0.02-0.018c1.139-1.16,1.699-2.7,1.699-4.22s-0.561-3.06-1.699-4.22 c-0.494-0.497-1.297-0.5-1.794-0.007C22.424,3.775,22.422,3.778,22.42,3.78z"></path>
|
||||
<path class="wave d" d="M29.18,1.06c-0.479-0.502-1.273-0.522-1.775-0.044c-0.016,0.015-0.029,0.029-0.045,0.044c-0.5,0.52-0.5,1.36,0,1.88 c1.361,1.4,2.041,3.24,2.041,5.08s-0.68,3.66-2.041,5.08c-0.5,0.52-0.5,1.36,0,1.88c0.509,0.508,1.332,0.508,1.841,0 c1.86-1.92,2.8-4.44,2.8-6.96C31.99,5.424,30.98,2.931,29.18,1.06z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="broadcast-nav" v-show="announcements && announcements.length > 1">
|
||||
<mk-frac class="broadcast-page" :value="i + 1" :total="announcements.length"/>
|
||||
<ui-button class="broadcast-prev" @click="prev" :title="$t('next')"><fa :icon="faAngleLeft"/></ui-button>
|
||||
<ui-button class="broadcast-next" @click="next" :title="$t('prev')"><fa :icon="faAngleRight"/></ui-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="broadcast-right">
|
||||
<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
|
||||
<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
|
||||
<p v-if="!fetching">
|
||||
<mfm v-if="announcements.length != 0" :text="announcements[i].text" :key="i"/>
|
||||
<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
|
||||
<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
|
||||
</p>
|
||||
</div>
|
||||
<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
|
||||
<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
|
||||
<p v-if="!fetching">
|
||||
<mfm v-if="announcements.length != 0" :text="announcements[i].text" :key="i"/>
|
||||
<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
|
||||
<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
|
||||
</p>
|
||||
<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} >></a>
|
||||
</div>
|
||||
</ui-container>
|
||||
</div>
|
||||
@ -30,6 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import define from '../../../common/define-widget';
|
||||
import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
export default define({
|
||||
@ -43,7 +52,8 @@ export default define({
|
||||
return {
|
||||
i: 0,
|
||||
fetching: true,
|
||||
announcements: []
|
||||
announcements: [],
|
||||
faAngleLeft, faAngleRight
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -54,14 +64,21 @@ export default define({
|
||||
},
|
||||
methods: {
|
||||
next() {
|
||||
if (this.i == this.announcements.length - 1) {
|
||||
if (this.i === this.announcements.length - 1) {
|
||||
this.i = 0;
|
||||
} else {
|
||||
this.i++;
|
||||
}
|
||||
},
|
||||
prev() {
|
||||
if (this.i === 0) {
|
||||
this.i = this.announcements.length - 1;
|
||||
} else {
|
||||
this.i--;
|
||||
}
|
||||
},
|
||||
func() {
|
||||
if (this.props.design == 1) {
|
||||
if (this.props.design === 1) {
|
||||
this.props.design = 0;
|
||||
} else {
|
||||
this.props.design++;
|
||||
@ -74,82 +91,115 @@ export default define({
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.anltbovirfeutcigvwgmgxipejaeozxi-body
|
||||
display flex
|
||||
padding 10px
|
||||
background var(--announcementsBg)
|
||||
|
||||
&[data-melt]
|
||||
background transparent
|
||||
|
||||
&[data-found]
|
||||
padding-left 50px
|
||||
> .broadcast-left
|
||||
width 32px
|
||||
margin-right 8px
|
||||
|
||||
> .icon
|
||||
display block
|
||||
> svg
|
||||
fill currentColor
|
||||
color var(--announcementsTitle)
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
> .wave
|
||||
opacity 1
|
||||
|
||||
> .icon
|
||||
display none
|
||||
float left
|
||||
margin-left -40px
|
||||
&.a
|
||||
animation wave 20s ease-in-out 2.1s infinite
|
||||
&.b
|
||||
animation wave 20s ease-in-out 2s infinite
|
||||
&.c
|
||||
animation wave 20s ease-in-out 2s infinite
|
||||
&.d
|
||||
animation wave 20s ease-in-out 2.1s infinite
|
||||
|
||||
> svg
|
||||
fill currentColor
|
||||
color #4078c0
|
||||
@keyframes wave
|
||||
0%
|
||||
opacity 1
|
||||
1.5%
|
||||
opacity 0
|
||||
3.5%
|
||||
opacity 0
|
||||
5%
|
||||
opacity 1
|
||||
6.5%
|
||||
opacity 0
|
||||
8.5%
|
||||
opacity 0
|
||||
10%
|
||||
opacity 1
|
||||
|
||||
> .wave
|
||||
opacity 1
|
||||
> .broadcast-nav
|
||||
display flex
|
||||
flex-wrap wrap
|
||||
padding 1px 0 2px
|
||||
|
||||
&.a
|
||||
animation wave 20s ease-in-out 2.1s infinite
|
||||
&.b
|
||||
animation wave 20s ease-in-out 2s infinite
|
||||
&.c
|
||||
animation wave 20s ease-in-out 2s infinite
|
||||
&.d
|
||||
animation wave 20s ease-in-out 2.1s infinite
|
||||
> .broadcast-page
|
||||
width 100%
|
||||
color var(--announcementsTitle)
|
||||
text-align center
|
||||
font-size .6rem
|
||||
|
||||
@keyframes wave
|
||||
0%
|
||||
opacity 1
|
||||
1.5%
|
||||
opacity 0
|
||||
3.5%
|
||||
opacity 0
|
||||
5%
|
||||
opacity 1
|
||||
6.5%
|
||||
opacity 0
|
||||
8.5%
|
||||
opacity 0
|
||||
10%
|
||||
opacity 1
|
||||
> .broadcast-prev,
|
||||
> .broadcast-next
|
||||
flex 1
|
||||
width 50%
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
font-size .9rem
|
||||
line-height 1.3em
|
||||
color var(--link)
|
||||
background transparent
|
||||
cursor pointer
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-size 0.95em
|
||||
font-weight normal
|
||||
color var(--announcementsTitle)
|
||||
&:focus
|
||||
&:after
|
||||
top -1px
|
||||
right -1px
|
||||
bottom -1px
|
||||
left -1px
|
||||
|
||||
> p
|
||||
display block
|
||||
z-index 1
|
||||
margin 0
|
||||
font-size 0.7em
|
||||
color var(--announcementsText)
|
||||
&.round:focus:after
|
||||
border-radius 5px
|
||||
|
||||
&.fetching
|
||||
text-align center
|
||||
> .broadcast-prev
|
||||
padding-right 3px
|
||||
|
||||
> a
|
||||
display block
|
||||
font-size 0.7em
|
||||
> .broadcast-next
|
||||
padding-left 3px
|
||||
|
||||
> .broadcast-right
|
||||
flex 1
|
||||
word-break break-word
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-size .975em
|
||||
font-weight normal
|
||||
line-height 1.3em
|
||||
color var(--announcementsTitle)
|
||||
padding-bottom 2px
|
||||
|
||||
&[data-mobile]
|
||||
> p
|
||||
color #fff
|
||||
display block
|
||||
z-index 1
|
||||
margin 0
|
||||
font-size .8em
|
||||
color var(--announcementsText)
|
||||
width 100%
|
||||
|
||||
&.fetching
|
||||
text-align center
|
||||
|
||||
&[data-mobile]
|
||||
> p
|
||||
color #fff
|
||||
|
||||
</style>
|
||||
|
@ -9,7 +9,6 @@ import VueRouter from 'vue-router';
|
||||
import './style.styl';
|
||||
|
||||
import init from '../init';
|
||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||
import composeNotification from '../common/scripts/compose-notification';
|
||||
|
||||
import MkHome from './views/home/home.vue';
|
||||
@ -203,13 +202,6 @@ init(async (launch, os) => {
|
||||
// Launch the app
|
||||
const [app, _] = launch(router);
|
||||
|
||||
if (os.store.getters.isSignedIn) {
|
||||
/**
|
||||
* Fuck AD Block
|
||||
*/
|
||||
fuckAdBlock(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Notification
|
||||
*/
|
||||
|
@ -37,7 +37,6 @@ import i18n from '../../../i18n';
|
||||
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
||||
import updateAvatar from '../../api/update-avatar';
|
||||
import updateBanner from '../../api/update-banner';
|
||||
import { appendQuery } from '../../../../../prelude/url';
|
||||
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -87,7 +86,8 @@ export default Vue.extend({
|
||||
action: this.copyUrl
|
||||
}, {
|
||||
type: 'link',
|
||||
href: appendQuery(this.file.url, 'download'),
|
||||
href: this.file.url,
|
||||
target: '_blank',
|
||||
text: this.$t('contextmenu.download'),
|
||||
icon: 'download',
|
||||
download: this.file.name
|
||||
|
@ -53,10 +53,13 @@ export default Vue.extend({
|
||||
start = videoTag.currentTime
|
||||
videoTag.pause()
|
||||
}
|
||||
this.$root.new(MkMediaVideoDialog, {
|
||||
const viewer = this.$root.new(MkMediaVideoDialog, {
|
||||
video: this.video,
|
||||
start,
|
||||
})
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
viewer.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="kedshtep" :class="{ naked, inNakedDeckColumn, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||
<header v-if="showHeader">
|
||||
<header v-if="showHeader" :class="{ bodyTogglable }" @click="toggleContent(!showBody)">
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<slot name="func"></slot>
|
||||
<button v-if="bodyTogglable" @click="toggleContent(!showBody)">
|
||||
<button v-if="bodyTogglable">
|
||||
<template v-if="showBody"><fa icon="angle-up"/></template>
|
||||
<template v-else><fa icon="angle-down"/></template>
|
||||
</button>
|
||||
@ -47,6 +47,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggleContent(show: boolean) {
|
||||
if (!this.bodyTogglable) return;
|
||||
this.showBody = show;
|
||||
this.$emit('toggle', show);
|
||||
}
|
||||
@ -77,6 +78,9 @@ export default Vue.extend({
|
||||
> header
|
||||
background var(--faceHeader)
|
||||
|
||||
&.bodyTogglable
|
||||
cursor pointer
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
@ -120,6 +124,9 @@ export default Vue.extend({
|
||||
color var(--text)
|
||||
background var(--deckColumnBg)
|
||||
|
||||
&.bodyTogglable
|
||||
cursor pointer
|
||||
|
||||
> button
|
||||
position absolute
|
||||
top 0
|
||||
|
@ -21,7 +21,7 @@
|
||||
<p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
|
||||
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@ -156,7 +156,7 @@ export default Vue.extend({
|
||||
font-size 1em
|
||||
color var(--primary)
|
||||
|
||||
> .follow-button
|
||||
> .koudoku-button
|
||||
position absolute
|
||||
top 8px
|
||||
right 8px
|
||||
|
@ -106,10 +106,13 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
menu() {
|
||||
this.$root.new(XUserMenu, {
|
||||
const w = this.$root.new(XUserMenu, {
|
||||
source: this.$refs.menu,
|
||||
user: this.user
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -57,7 +57,6 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { gcd } from '../../../../../prelude/math';
|
||||
import { appendQuery } from '../../../../../prelude/url';
|
||||
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -91,7 +90,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
dlUrl(): string {
|
||||
return appendQuery(this.file.url, 'download');
|
||||
return this.file.url;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="ukygtjoj" :class="{ naked, inNakedDeckColumn, hideHeader: !showHeader, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||
<header v-if="showHeader">
|
||||
<header v-if="showHeader" @click="() => showBody = !showBody">
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<slot name="func"></slot>
|
||||
<button v-if="bodyTogglable" @click="() => showBody = !showBody">
|
||||
<button v-if="bodyTogglable">
|
||||
<template v-if="showBody"><fa icon="angle-up"/></template>
|
||||
<template v-else><fa icon="angle-down"/></template>
|
||||
</button>
|
||||
@ -47,6 +47,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggleContent(show: boolean) {
|
||||
if (!this.bodyTogglable) return;
|
||||
this.showBody = show;
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ const defaultSettings = {
|
||||
uploadFolder: null,
|
||||
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
|
||||
pasteDialog: false,
|
||||
reactions: ['like', 'love', 'laugh', 'hmm', 'surprise', 'congrats', 'angry', 'confused', 'rip', 'pudding']
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
|
@ -4,13 +4,53 @@
|
||||
|
||||
import composeNotification from './common/scripts/compose-notification';
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const version = _VERSION_;
|
||||
const cacheName = `mk-cache-${version}`;
|
||||
|
||||
const apiUrl = `${location.origin}/api/`;
|
||||
|
||||
// インストールされたとき
|
||||
self.addEventListener('install', ev => {
|
||||
console.info('installed');
|
||||
|
||||
ev.waitUntil(Promise.all([
|
||||
self.skipWaiting(), // Force activate
|
||||
]));
|
||||
ev.waitUntil(
|
||||
caches.open(cacheName)
|
||||
.then(cache => {
|
||||
return cache.addAll([
|
||||
"/",
|
||||
`/assets/desktop.${version}.js`,
|
||||
`/assets/mobile.${version}.js`,
|
||||
"/assets/error.jpg"
|
||||
]);
|
||||
})
|
||||
.then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', ev => {
|
||||
ev.waitUntil(
|
||||
caches.keys()
|
||||
.then(cacheNames => Promise.all(
|
||||
cacheNames
|
||||
.filter((v) => v !== cacheName)
|
||||
.map(name => caches.delete(name))
|
||||
))
|
||||
.then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', ev => {
|
||||
if (ev.request.method !== 'GET' || ev.request.url.startsWith(apiUrl)) return;
|
||||
ev.respondWith(
|
||||
caches.match(ev.request)
|
||||
.then(response => {
|
||||
return response || fetch(ev.request);
|
||||
})
|
||||
.catch(() => {
|
||||
return caches.match("/");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// プッシュ通知を受け取ったとき
|
||||
|
BIN
src/client/assets/fedi.jpg
Normal file
BIN
src/client/assets/fedi.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
@ -27,7 +27,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
|
||||
* `pos`として位置情報が含まれている
|
||||
|
||||
## 位置の計算法
|
||||
8x8のマップを考える場合、各マスの位置(Posと呼びます)は次のようになっています:
|
||||
8x8のマップを考える場合、各マスの位置(インデックスと呼びます)は次のようになっています:
|
||||
```
|
||||
+--+--+--+--+--+--+--+--+
|
||||
| 0| 1| 2| 3| 4| 5| 6| 7|
|
||||
@ -38,7 +38,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
|
||||
...
|
||||
```
|
||||
|
||||
### X,Y座標 から Pos に変換する
|
||||
### X,Y座標 から インデックス に変換する
|
||||
```
|
||||
pos = x + (y * mapWidth)
|
||||
```
|
||||
@ -47,7 +47,7 @@ pos = x + (y * mapWidth)
|
||||
mapWidth = map[0].length
|
||||
```
|
||||
|
||||
### Pos から X,Y座標 に変換する
|
||||
### インデックス から X,Y座標 に変換する
|
||||
```
|
||||
x = pos % mapWidth
|
||||
y = Math.floor(pos / mapWidth)
|
||||
|
1655
src/emojilist.json
Normal file
1655
src/emojilist.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -145,9 +145,10 @@ export const mfmLanguage = P.createLanguage({
|
||||
if (!match) return P.makeFailure(i, 'not a hashtag');
|
||||
let hashtag = match[1];
|
||||
hashtag = removeOrphanedBrackets(hashtag);
|
||||
if (hashtag.match(/^(\u20e3|\ufe0f)/)) return P.makeFailure(i, 'not a hashtag');
|
||||
if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag');
|
||||
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a hashtag');
|
||||
if (hashtag.length > 50) return P.makeFailure(i, 'not a hashtag');
|
||||
if (Array.from(hashtag || '').length > 128) return P.makeFailure(i, 'not a hashtag');
|
||||
return P.makeSuccess(i + ('#' + hashtag).length, createLeaf('hashtag', { hashtag: hashtag }));
|
||||
}),
|
||||
url: () => {
|
||||
@ -166,10 +167,7 @@ export const mfmLanguage = P.createLanguage({
|
||||
url = match[0];
|
||||
}
|
||||
url = removeOrphanedBrackets(url);
|
||||
while (url.endsWith('.') || url.endsWith(',')) {
|
||||
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
|
||||
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
|
||||
}
|
||||
url = url.replace(/[.,]*$/, '');
|
||||
return P.makeSuccess(i + url.length, url);
|
||||
}).map(x => createLeaf('url', { url: x }));
|
||||
},
|
||||
|
22
src/misc/app-lock.ts
Normal file
22
src/misc/app-lock.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import redis from '../db/redis';
|
||||
import { promisify } from 'util';
|
||||
|
||||
/**
|
||||
* Retry delay (ms) for lock acquisition
|
||||
*/
|
||||
const retryDelay = 100;
|
||||
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void>
|
||||
= redis
|
||||
? promisify(require('redis-lock')(redis, retryDelay))
|
||||
: async () => () => { };
|
||||
|
||||
/**
|
||||
* Get AP Object lock
|
||||
* @param uri AP object ID
|
||||
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
||||
* @returns Unlock function
|
||||
*/
|
||||
export function getApLock(uri: string, timeout = 30 * 1000) {
|
||||
return lock(`ap-object:${uri}`, timeout);
|
||||
}
|
File diff suppressed because one or more lines are too long
7
src/misc/emojilist.ts
Normal file
7
src/misc/emojilist.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
|
||||
export const emojilist = require('../emojilist.json') as {
|
||||
name: string;
|
||||
keywords: string[];
|
||||
char: string;
|
||||
category: 'people' | 'animals_and_nature' | 'food_and_drink' | 'activity' | 'travel_and_places' | 'objects' | 'symbols' | 'flags';
|
||||
}[];
|
8
src/misc/hard-limits.ts
Normal file
8
src/misc/hard-limits.ts
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
// If you change DB_* values, you must also change the DB schema.
|
||||
|
||||
/**
|
||||
* Maximum note text length that can be stored in DB.
|
||||
* Surrogate pairs count as one
|
||||
*/
|
||||
export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
|
@ -1,4 +1 @@
|
||||
export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@12.0.1';
|
||||
// https://cdn.jsdelivr.net/npm/twemoji@12.0.1
|
||||
// https://cdnjs.cloudflare.com/ajax/libs/twemoji/12.0.1
|
||||
// https://twemoji.maxcdn.com
|
||||
export const twemojiSvgBase = 'https://twemoji.maxcdn.com/v/latest/svg';
|
||||
|
@ -21,23 +21,23 @@ export class NotificationRepository extends Repository<Notification> {
|
||||
userId: notification.notifierId,
|
||||
user: Users.pack(notification.notifier || notification.notifierId),
|
||||
...(notification.type === 'mention' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
} : {}),
|
||||
...(notification.type === 'reply' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
} : {}),
|
||||
...(notification.type === 'renote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
} : {}),
|
||||
...(notification.type === 'quote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
} : {}),
|
||||
...(notification.type === 'reaction' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
reaction: notification.reaction
|
||||
} : {}),
|
||||
...(notification.type === 'pollVote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId!),
|
||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||
choice: notification.choice
|
||||
} : {})
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import { EntityRepository, Repository, In } from 'typeorm';
|
||||
import { EntityRepository, Repository, In, Not } from 'typeorm';
|
||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages } from '..';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
@ -56,6 +56,10 @@ export class UserRepository extends Repository<User> {
|
||||
}
|
||||
|
||||
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
|
||||
const mute = await Mutings.find({
|
||||
muterId: userId
|
||||
});
|
||||
|
||||
const joinings = await UserGroupJoinings.find({ userId: userId });
|
||||
|
||||
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
|
||||
@ -66,11 +70,11 @@ export class UserRepository extends Repository<User> {
|
||||
.getOne().then(x => x != null)));
|
||||
|
||||
const [withUser, withGroups] = await Promise.all([
|
||||
// TODO: ミュートを考慮
|
||||
MessagingMessages.count({
|
||||
where: {
|
||||
recipientId: userId,
|
||||
isRead: false
|
||||
isRead: false,
|
||||
...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}),
|
||||
},
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
|
@ -120,3 +120,11 @@ export function cumulativeSum(xs: number[]): number[] {
|
||||
export function fromEntries(xs: [string, any][]): { [x: string]: any; } {
|
||||
return xs.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as { [x: string]: any; });
|
||||
}
|
||||
|
||||
export function toArray<T>(x: T | T[] | undefined): T[] {
|
||||
return Array.isArray(x) ? x : x != null ? [x] : [];
|
||||
}
|
||||
|
||||
export function toSingle<T>(x: T | T[] | undefined): T | undefined {
|
||||
return Array.isArray(x) ? x[0] : x;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import announceNote from './note';
|
||||
import { IAnnounce, INote, validPost, getApId } from '../../type';
|
||||
import { IAnnounce, validPost, getApId } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
const logger = apLogger;
|
||||
@ -23,7 +23,7 @@ export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> =>
|
||||
}
|
||||
|
||||
if (validPost.includes(object.type)) {
|
||||
announceNote(resolver, actor, activity, object as INote);
|
||||
announceNote(resolver, actor, activity, object);
|
||||
} else {
|
||||
logger.warn(`Unknown announce type: ${object.type}`);
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
import Resolver from '../../resolver';
|
||||
import post from '../../../../services/note/create';
|
||||
import { IRemoteUser, User } from '../../../../models/entities/user';
|
||||
import { IAnnounce, INote, getApId, getApIds } from '../../type';
|
||||
import { IAnnounce, IObject, getApId, getApIds } from '../../type';
|
||||
import { fetchNote, resolveNote } from '../../models/note';
|
||||
import { resolvePerson } from '../../models/person';
|
||||
import { apLogger } from '../../logger';
|
||||
import { extractDbHost } from '../../../../misc/convert-host';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { getApLock } from '../../../../misc/app-lock';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
/**
|
||||
* アナウンスアクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: INote): Promise<void> {
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: IObject): Promise<void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
// アナウンサーが凍結されていたらスキップ
|
||||
@ -25,47 +26,53 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
// Announce対象をresolve
|
||||
let renote;
|
||||
try {
|
||||
renote = await resolveNote(note);
|
||||
} catch (e) {
|
||||
// 対象が4xxならスキップ
|
||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
||||
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
|
||||
// Announce対象をresolve
|
||||
let renote;
|
||||
try {
|
||||
renote = await resolveNote(note);
|
||||
} catch (e) {
|
||||
// 対象が4xxならスキップ
|
||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
||||
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
|
||||
return;
|
||||
}
|
||||
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||
|
||||
//#region Visibility
|
||||
const to = getApIds(activity.to);
|
||||
const cc = getApIds(activity.cc);
|
||||
|
||||
const visibility = getVisibility(to, cc, actor);
|
||||
|
||||
let visibleUsers: User[] = [];
|
||||
if (visibility == 'specified') {
|
||||
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
|
||||
}
|
||||
//#endergion
|
||||
|
||||
await post(actor, {
|
||||
createdAt: activity.published ? new Date(activity.published) : null,
|
||||
renote,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
uri
|
||||
});
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
|
||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||
|
||||
//#region Visibility
|
||||
const to = getApIds(activity.to);
|
||||
const cc = getApIds(activity.cc);
|
||||
|
||||
const visibility = getVisibility(to, cc, actor);
|
||||
|
||||
let visibleUsers: User[] = [];
|
||||
if (visibility == 'specified') {
|
||||
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
|
||||
}
|
||||
//#endergion
|
||||
|
||||
await post(actor, {
|
||||
createdAt: activity.published ? new Date(activity.published) : null,
|
||||
renote,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
uri
|
||||
});
|
||||
}
|
||||
|
||||
type visibility = 'public' | 'home' | 'followers' | 'specified';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import config from '../../../../config';
|
||||
import { IBlock } from '../../type';
|
||||
import { IBlock, getApId } from '../../type';
|
||||
import block from '../../../../services/blocking/create';
|
||||
import { apLogger } from '../../logger';
|
||||
import { Users } from '../../../../models';
|
||||
@ -8,10 +8,9 @@ import { IRemoteUser } from '../../../../models/entities/user';
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||
if (id == null) throw new Error('missing id');
|
||||
const id = getApId(activity.object);
|
||||
|
||||
const uri = activity.id || activity;
|
||||
const uri = getApId(activity);
|
||||
|
||||
logger.info(`Block: ${uri}`);
|
||||
|
||||
|
@ -1,13 +1,23 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { createNote, fetchNote } from '../../models/note';
|
||||
import { getApId, IObject } from '../../type';
|
||||
import { getApLock } from '../../../../misc/app-lock';
|
||||
|
||||
/**
|
||||
* 投稿作成アクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist == null) {
|
||||
await createNote(note);
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise<void> {
|
||||
const uri = getApId(note);
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist == null) {
|
||||
await createNote(note);
|
||||
}
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Object } from '../type';
|
||||
import { IObject, isCreate, isDelete, isUpdate, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection } from '../type';
|
||||
import { IRemoteUser } from '../../../models/entities/user';
|
||||
import create from './create';
|
||||
import performDeleteActivity from './delete';
|
||||
@ -13,68 +13,53 @@ import add from './add';
|
||||
import remove from './remove';
|
||||
import block from './block';
|
||||
import { apLogger } from '../logger';
|
||||
import Resolver from '../resolver';
|
||||
import { toArray } from '../../../prelude/array';
|
||||
|
||||
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
||||
export async function performActivity(actor: IRemoteUser, activity: IObject) {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
const resolver = new Resolver();
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
const act = await resolver.resolve(item);
|
||||
try {
|
||||
await performOneActivity(actor, act);
|
||||
} catch (e) {
|
||||
apLogger.error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await performOneActivity(actor, activity);
|
||||
}
|
||||
}
|
||||
|
||||
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
|
||||
switch (activity.type) {
|
||||
case 'Create':
|
||||
if (isCreate(activity)) {
|
||||
await create(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Delete':
|
||||
} else if (isDelete(activity)) {
|
||||
await performDeleteActivity(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Update':
|
||||
} else if (isUpdate(activity)) {
|
||||
await performUpdateActivity(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Follow':
|
||||
} else if (isFollow(activity)) {
|
||||
await follow(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Accept':
|
||||
} else if (isAccept(activity)) {
|
||||
await accept(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Reject':
|
||||
} else if (isReject(activity)) {
|
||||
await reject(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Add':
|
||||
} else if (isAdd(activity)) {
|
||||
await add(actor, activity).catch(err => apLogger.error(err));
|
||||
break;
|
||||
|
||||
case 'Remove':
|
||||
} else if (isRemove(activity)) {
|
||||
await remove(actor, activity).catch(err => apLogger.error(err));
|
||||
break;
|
||||
|
||||
case 'Announce':
|
||||
} else if (isAnnounce(activity)) {
|
||||
await announce(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Like':
|
||||
} else if (isLike(activity)) {
|
||||
await like(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Undo':
|
||||
} else if (isUndo(activity)) {
|
||||
await undo(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Block':
|
||||
} else if (isBlock(activity)) {
|
||||
await block(actor, activity);
|
||||
break;
|
||||
|
||||
case 'Collection':
|
||||
case 'OrderedCollection':
|
||||
// TODO
|
||||
break;
|
||||
|
||||
default:
|
||||
} else {
|
||||
apLogger.warn(`unknown activity type: ${(activity as any).type}`);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default self;
|
||||
}
|
||||
|
16
src/remote/activitypub/kernel/undo/announce.ts
Normal file
16
src/remote/activitypub/kernel/undo/announce.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Notes } from '../../../../models';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IAnnounce, getApId } from '../../type';
|
||||
import deleteNote from '../../../../services/note/delete';
|
||||
|
||||
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
const note = await Notes.findOne({
|
||||
uri
|
||||
});
|
||||
|
||||
if (!note) return;
|
||||
|
||||
await deleteNote(actor, note);
|
||||
};
|
@ -1,8 +1,9 @@
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IUndo, IFollow, IBlock, ILike } from '../../type';
|
||||
import { IUndo, IFollow, IBlock, ILike, IAnnounce } from '../../type';
|
||||
import unfollow from './follow';
|
||||
import unblock from './block';
|
||||
import undoLike from './like';
|
||||
import { undoAnnounce } from './announce';
|
||||
import Resolver from '../../resolver';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
@ -38,5 +39,8 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
|
||||
case 'Like':
|
||||
undoLike(actor, object as ILike);
|
||||
break;
|
||||
case 'Announce':
|
||||
undoAnnounce(actor, object as IAnnounce);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@ -22,6 +22,7 @@ import { Emoji } from '../../../models/entities/emoji';
|
||||
import { genId } from '../../../misc/gen-id';
|
||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
import { getApLock } from '../../../misc/app-lock';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@ -33,7 +34,7 @@ export function validateNote(object: any, uri: string) {
|
||||
}
|
||||
|
||||
if (!validPost.includes(object.type)) {
|
||||
return new Error(`invalid Note: invalied object type ${object.type}`);
|
||||
return new Error(`invalid Note: invalid object type ${object.type}`);
|
||||
}
|
||||
|
||||
if (object.id && extractDbHost(object.id) !== expectHost) {
|
||||
@ -257,30 +258,24 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await fetchNote(uri);
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
//#endregion
|
||||
try {
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await fetchNote(uri);
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||
return await createNote(uri, resolver, true).catch(e => {
|
||||
if (e.name === 'duplicated') {
|
||||
return fetchNote(uri).then(note => {
|
||||
if (note == null) {
|
||||
throw new Error('something happened');
|
||||
} else {
|
||||
return note;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||
return await createNote(uri, resolver, true);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
|
||||
|
@ -26,6 +26,8 @@ import { UserProfile } from '../../../models/entities/user-profile';
|
||||
import { validActor } from '../../../remote/activitypub/type';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
import { toArray } from '../../../prelude/array';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
/**
|
||||
@ -132,7 +134,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||
|
||||
const { fields } = analyzeAttachments(person.attachment || []);
|
||||
|
||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
||||
|
||||
const isBot = object.type == 'Service';
|
||||
|
||||
@ -305,7 +307,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||
|
||||
const { fields, services } = analyzeAttachments(person.attachment || []);
|
||||
|
||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
||||
|
||||
const updates = {
|
||||
lastFetchedAt: new Date(),
|
||||
@ -463,8 +465,7 @@ export async function updateFeatured(userId: User['id']) {
|
||||
|
||||
// Resolve to Object(may be Note) arrays
|
||||
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
|
||||
const items = await resolver.resolve(unresolvedItems);
|
||||
if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
|
||||
const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x)));
|
||||
|
||||
// Resolve and regist Notes
|
||||
const limit = promiseLimit<Note | null>(2);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Object } from './type';
|
||||
import { IObject } from './type';
|
||||
import { IRemoteUser } from '../../models/entities/user';
|
||||
import kernel from './kernel';
|
||||
import { performActivity } from './kernel';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
||||
await kernel(actor, activity);
|
||||
export default async (actor: IRemoteUser, activity: IObject): Promise<void> => {
|
||||
await performActivity(actor, activity);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as request from 'request-promise-native';
|
||||
import { IObject } from './type';
|
||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type';
|
||||
import config from '../../config';
|
||||
|
||||
export default class Resolver {
|
||||
@ -14,31 +14,19 @@ export default class Resolver {
|
||||
return Array.from(this.history);
|
||||
}
|
||||
|
||||
public async resolveCollection(value: any) {
|
||||
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
|
||||
const collection = typeof value === 'string'
|
||||
? await this.resolve(value)
|
||||
: value;
|
||||
|
||||
switch (collection.type) {
|
||||
case 'Collection': {
|
||||
collection.objects = collection.items;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'OrderedCollection': {
|
||||
collection.objects = collection.orderedItems;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`unknown collection type: ${collection.type}`);
|
||||
}
|
||||
if (isCollectionOrOrderedCollection(collection)) {
|
||||
return collection;
|
||||
} else {
|
||||
throw new Error(`unknown collection type: ${collection.type}`);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public async resolve(value: any): Promise<IObject> {
|
||||
public async resolve(value: string | IObject): Promise<IObject> {
|
||||
if (value == null) {
|
||||
throw new Error('resolvee is null (or undefined)');
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export type obj = { [x: string]: any };
|
||||
export type ApObject = IObject | string | (IObject | string)[];
|
||||
|
||||
export interface IObject {
|
||||
'@context': string | obj | obj[];
|
||||
@ -6,9 +7,9 @@ export interface IObject {
|
||||
id?: string;
|
||||
summary?: string;
|
||||
published?: string;
|
||||
cc?: IObject | string | (IObject | string)[];
|
||||
to?: IObject | string | (IObject | string)[];
|
||||
attributedTo: IObject | string | (IObject | string)[];
|
||||
cc?: ApObject;
|
||||
to?: ApObject;
|
||||
attributedTo: ApObject;
|
||||
attachment?: any[];
|
||||
inReplyTo?: any;
|
||||
replies?: ICollection;
|
||||
@ -26,7 +27,7 @@ export interface IObject {
|
||||
/**
|
||||
* Get array of ActivityStreams Objects id
|
||||
*/
|
||||
export function getApIds(value: IObject | string | (IObject | string)[] | undefined): string[] {
|
||||
export function getApIds(value: ApObject | undefined): string[] {
|
||||
if (value == null) return [];
|
||||
const array = Array.isArray(value) ? value : [value];
|
||||
return array.map(x => getApId(x));
|
||||
@ -35,7 +36,7 @@ export function getApIds(value: IObject | string | (IObject | string)[] | undefi
|
||||
/**
|
||||
* Get first ActivityStreams Object id
|
||||
*/
|
||||
export function getOneApId(value: IObject | string | (IObject | string)[]): string {
|
||||
export function getOneApId(value: ApObject): string {
|
||||
const firstOne = Array.isArray(value) ? value[0] : value;
|
||||
return getApId(firstOne);
|
||||
}
|
||||
@ -59,13 +60,13 @@ export interface IActivity extends IObject {
|
||||
export interface ICollection extends IObject {
|
||||
type: 'Collection';
|
||||
totalItems: number;
|
||||
items: IObject | string | IObject[] | string[];
|
||||
items: ApObject;
|
||||
}
|
||||
|
||||
export interface IOrderedCollection extends IObject {
|
||||
type: 'OrderedCollection';
|
||||
totalItems: number;
|
||||
orderedItems: IObject | string | IObject[] | string[];
|
||||
orderedItems: ApObject;
|
||||
}
|
||||
|
||||
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video'];
|
||||
@ -170,18 +171,15 @@ export interface IBlock extends IActivity {
|
||||
type: 'Block';
|
||||
}
|
||||
|
||||
export type Object =
|
||||
ICollection |
|
||||
IOrderedCollection |
|
||||
ICreate |
|
||||
IDelete |
|
||||
IUpdate |
|
||||
IUndo |
|
||||
IFollow |
|
||||
IAccept |
|
||||
IReject |
|
||||
IAdd |
|
||||
IRemove |
|
||||
ILike |
|
||||
IAnnounce |
|
||||
IBlock;
|
||||
export const isCreate = (object: IObject): object is ICreate => object.type === 'Create';
|
||||
export const isDelete = (object: IObject): object is IDelete => object.type === 'Delete';
|
||||
export const isUpdate = (object: IObject): object is IUpdate => object.type === 'Update';
|
||||
export const isUndo = (object: IObject): object is IUndo => object.type === 'Undo';
|
||||
export const isFollow = (object: IObject): object is IFollow => object.type === 'Follow';
|
||||
export const isAccept = (object: IObject): object is IAccept => object.type === 'Accept';
|
||||
export const isReject = (object: IObject): object is IReject => object.type === 'Reject';
|
||||
export const isAdd = (object: IObject): object is IAdd => object.type === 'Add';
|
||||
export const isRemove = (object: IObject): object is IRemove => object.type === 'Remove';
|
||||
export const isLike = (object: IObject): object is ILike => object.type === 'Like';
|
||||
export const isAnnounce = (object: IObject): object is IAnnounce => object.type === 'Announce';
|
||||
export const isBlock = (object: IObject): object is IBlock => object.type === 'Block';
|
||||
|
@ -65,7 +65,7 @@ export async function resolveUser(username: string, host: string | null, option?
|
||||
// validate uri
|
||||
const uri = new URL(self.href);
|
||||
if (uri.hostname !== host) {
|
||||
throw new Error(`Invalied uri`);
|
||||
throw new Error(`Invalid uri`);
|
||||
}
|
||||
|
||||
await Users.update({
|
||||
|
@ -40,5 +40,5 @@ function genUrl(query: string) {
|
||||
return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` });
|
||||
}
|
||||
|
||||
throw new Error(`Invalied query (${query})`);
|
||||
throw new Error(`Invalid query (${query})`);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Router from 'koa-router';
|
||||
import * as Router from '@koa/router';
|
||||
import * as json from 'koa-json-body';
|
||||
import * as httpSignature from 'http-signature';
|
||||
|
||||
@ -23,7 +23,7 @@ const router = new Router();
|
||||
|
||||
//#region Routing
|
||||
|
||||
function inbox(ctx: Router.IRouterContext) {
|
||||
function inbox(ctx: Router.RouterContext) {
|
||||
let signature;
|
||||
|
||||
ctx.req.headers.authorization = `Signature ${ctx.req.headers.signature}`;
|
||||
@ -40,18 +40,21 @@ function inbox(ctx: Router.IRouterContext) {
|
||||
ctx.status = 202;
|
||||
}
|
||||
|
||||
function isActivityPubReq(ctx: Router.IRouterContext) {
|
||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
|
||||
function isActivityPubReq(ctx: Router.RouterContext) {
|
||||
ctx.response.vary('Accept');
|
||||
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json');
|
||||
return ['application/activity+json', 'application/ld+json'].includes(accepted as string);
|
||||
const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
|
||||
return typeof accepted === 'string' && !accepted.match(/html/);
|
||||
}
|
||||
|
||||
export function setResponseType(ctx: Router.IRouterContext) {
|
||||
const accpet = ctx.accepts('application/activity+json', 'application/ld+json');
|
||||
if (accpet === 'application/ld+json') {
|
||||
ctx.response.type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
export function setResponseType(ctx: Router.RouterContext) {
|
||||
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
|
||||
if (accept === LD_JSON) {
|
||||
ctx.response.type = LD_JSON;
|
||||
} else {
|
||||
ctx.response.type = 'application/activity+json; charset=utf-8';
|
||||
ctx.response.type = ACTIVITY_JSON;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +149,7 @@ router.get('/users/:user/publickey', async ctx => {
|
||||
});
|
||||
|
||||
// user
|
||||
async function userInfo(ctx: Router.IRouterContext, user: User) {
|
||||
async function userInfo(ctx: Router.RouterContext, user: User | undefined) {
|
||||
if (user == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
@ -165,7 +168,7 @@ router.get('/users/:user', async (ctx, next) => {
|
||||
const user = await Users.findOne({
|
||||
id: userId,
|
||||
host: null
|
||||
}).then(ensure);
|
||||
});
|
||||
|
||||
await userInfo(ctx, user);
|
||||
});
|
||||
@ -176,7 +179,7 @@ router.get('/@:user', async (ctx, next) => {
|
||||
const user = await Users.findOne({
|
||||
usernameLower: ctx.params.user.toLowerCase(),
|
||||
host: null
|
||||
}).then(ensure);
|
||||
});
|
||||
|
||||
await userInfo(ctx, user);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Router from 'koa-router';
|
||||
import * as Router from '@koa/router';
|
||||
import config from '../../config';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
@ -7,7 +7,7 @@ import renderNote from '../../remote/activitypub/renderer/note';
|
||||
import { Users, Notes, UserNotePinings } from '../../models';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
|
||||
export default async (ctx: Router.IRouterContext) => {
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Verify user
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Router from 'koa-router';
|
||||
import * as Router from '@koa/router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../misc/cafy-id';
|
||||
@ -11,7 +11,7 @@ import { setResponseType } from '../activitypub';
|
||||
import { Users, Followings } from '../../models';
|
||||
import { LessThan } from 'typeorm';
|
||||
|
||||
export default async (ctx: Router.IRouterContext) => {
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'cursor' parameter
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Router from 'koa-router';
|
||||
import * as Router from '@koa/router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../misc/cafy-id';
|
||||
@ -12,7 +12,7 @@ import { Users, Followings } from '../../models';
|
||||
import { LessThan, FindConditions } from 'typeorm';
|
||||
import { Following } from '../../models/entities/following';
|
||||
|
||||
export default async (ctx: Router.IRouterContext) => {
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'cursor' parameter
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Router from 'koa-router';
|
||||
import * as Router from '@koa/router';
|
||||
import config from '../../config';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../misc/cafy-id';
|
||||
@ -17,7 +17,7 @@ import { Brackets } from 'typeorm';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
|
||||
export default async (ctx: Router.IRouterContext) => {
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
|
@ -6,7 +6,7 @@ import call from './call';
|
||||
import { ApiError } from './error';
|
||||
|
||||
export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) => {
|
||||
const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body;
|
||||
const body = ctx.request.body;
|
||||
|
||||
const reply = (x?: any, y?: ApiError) => {
|
||||
if (x == null) {
|
||||
@ -31,7 +31,7 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res)
|
||||
// Authentication
|
||||
authenticate(body['i']).then(([user, app]) => {
|
||||
// API invoking
|
||||
call(endpoint.name, user, app, body, (ctx.req as any).file).then((res: any) => {
|
||||
call(endpoint.name, user, app, body, (ctx as any).file).then((res: any) => {
|
||||
reply(res);
|
||||
}).catch((e: ApiError) => {
|
||||
reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e);
|
||||
|
@ -33,6 +33,7 @@ export const meta = {
|
||||
state: {
|
||||
validator: $.optional.str.or([
|
||||
'all',
|
||||
'available',
|
||||
'admin',
|
||||
'moderator',
|
||||
'adminOrModerator',
|
||||
@ -67,6 +68,7 @@ export default define(meta, async (ps, me) => {
|
||||
const query = Users.createQueryBuilder('user');
|
||||
|
||||
switch (ps.state) {
|
||||
case 'available': query.where('user.isSuspended = FALSE'); break;
|
||||
case 'admin': query.where('user.isAdmin = TRUE'); break;
|
||||
case 'moderator': query.where('user.isModerator = TRUE'); break;
|
||||
case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break;
|
||||
|
@ -3,6 +3,7 @@ import define from '../../define';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { Meta } from '../../../../models/entities/meta';
|
||||
import { insertModerationLog } from '../../../../services/insert-moderation-log';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@ -121,7 +122,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
maxNoteTextLength: {
|
||||
validator: $.optional.num.min(0),
|
||||
validator: $.optional.num.min(0).max(DB_MAX_NOTE_TEXT_LENGTH),
|
||||
desc: {
|
||||
'ja-JP': '投稿の最大文字数'
|
||||
}
|
||||
@ -430,15 +431,15 @@ export default define(meta, async (ps, me) => {
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.pinnedUsers)) {
|
||||
set.pinnedUsers = ps.pinnedUsers;
|
||||
set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.hiddenTags)) {
|
||||
set.hiddenTags = ps.hiddenTags;
|
||||
set.hiddenTags = ps.hiddenTags.filter(Boolean);
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.blockedHosts)) {
|
||||
set.blockedHosts = ps.blockedHosts;
|
||||
set.blockedHosts = ps.blockedHosts.filter(Boolean);
|
||||
}
|
||||
|
||||
if (ps.mascotImageUrl !== undefined) {
|
||||
@ -501,8 +502,8 @@ export default define(meta, async (ps, me) => {
|
||||
set.maintainerEmail = ps.maintainerEmail;
|
||||
}
|
||||
|
||||
if (ps.langs !== undefined) {
|
||||
set.langs = ps.langs;
|
||||
if (Array.isArray(ps.langs)) {
|
||||
set.langs = ps.langs.filter(Boolean);
|
||||
}
|
||||
|
||||
if (ps.summalyProxy !== undefined) {
|
||||
|
@ -257,7 +257,7 @@ export default define(meta, async (ps, user, app) => {
|
||||
if (newDescription != null) {
|
||||
const tokens = parse(newDescription);
|
||||
emojis = emojis.concat(extractEmojis(tokens!));
|
||||
tags = extractHashtags(tokens!).map(tag => tag.toLowerCase());
|
||||
tags = extractHashtags(tokens!).map(tag => tag.toLowerCase()).splice(0, 32);
|
||||
}
|
||||
|
||||
updates.emojis = emojis;
|
||||
|
@ -195,10 +195,7 @@ export default define(meta, async (ps, user) => {
|
||||
const mute = await Mutings.find({
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
const mutedUserIds = mute.map(m => m.muteeId.toString());
|
||||
if (mutedUserIds.indexOf(user.id) != -1) {
|
||||
return;
|
||||
}
|
||||
if (mute.map(m => m.muteeId).includes(user.id)) return;
|
||||
//#endregion
|
||||
|
||||
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
|
@ -7,6 +7,7 @@ import * as pkg from '../../../../package.json';
|
||||
import { Emojis } from '../../../models';
|
||||
import { getConnection } from 'typeorm';
|
||||
import redis from '../../../db/redis';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../misc/hard-limits';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@ -138,7 +139,7 @@ export default define(meta, async (ps, me) => {
|
||||
bannerUrl: instance.bannerUrl,
|
||||
errorImageUrl: instance.errorImageUrl,
|
||||
iconUrl: instance.iconUrl,
|
||||
maxNoteTextLength: instance.maxNoteTextLength,
|
||||
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
|
||||
emojis: emojis.map(e => ({
|
||||
id: e.id,
|
||||
aliases: e.aliases,
|
||||
|
@ -10,6 +10,7 @@ import { User } from '../../../../models/entities/user';
|
||||
import { Users, DriveFiles, Notes } from '../../../../models';
|
||||
import { DriveFile } from '../../../../models/entities/drive-file';
|
||||
import { Note } from '../../../../models/entities/note';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
|
||||
|
||||
let maxNoteTextLength = 1000;
|
||||
|
||||
@ -55,7 +56,9 @@ export const meta = {
|
||||
|
||||
text: {
|
||||
validator: $.optional.nullable.str.pipe(text =>
|
||||
length(text.trim()) <= maxNoteTextLength && text.trim() != ''
|
||||
text.trim() != ''
|
||||
&& length(text.trim()) <= maxNoteTextLength
|
||||
&& Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit
|
||||
),
|
||||
default: null as any,
|
||||
desc: {
|
||||
|
@ -74,5 +74,5 @@ export default define(meta, async (ps, me) => {
|
||||
}
|
||||
|
||||
// Pull the user
|
||||
await UserGroupJoinings.delete({ userId: user.id });
|
||||
await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id });
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user