Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b71a602107 | ||
![]() |
e6ce0dd43a | ||
![]() |
094a5214f1 | ||
![]() |
0932fcd114 | ||
![]() |
f26643cea3 | ||
![]() |
63b1689155 | ||
![]() |
59dc929431 | ||
![]() |
deaadc33db | ||
![]() |
0ba92a4f29 | ||
![]() |
7dc5009ec7 | ||
![]() |
0ee827afd3 | ||
![]() |
551d1b7f86 | ||
![]() |
7ed1b695f5 | ||
![]() |
7ad31b9b33 | ||
![]() |
edd8992f7f | ||
![]() |
96fe42cfcb | ||
![]() |
c3872b4a38 | ||
![]() |
762945113d | ||
![]() |
037e9230fc | ||
![]() |
3f59ebf986 | ||
![]() |
e51e1d2b09 | ||
![]() |
26cc49eb69 | ||
![]() |
7987bb491c | ||
![]() |
16b7ac5a87 | ||
![]() |
5932cb8609 | ||
![]() |
dfe694d39f | ||
![]() |
278624f2c8 | ||
![]() |
899f42c070 | ||
![]() |
8ce1d4d6a3 | ||
![]() |
52225d703b | ||
![]() |
81739af7cb | ||
![]() |
25473222cc | ||
![]() |
0b7be70935 | ||
![]() |
818b71abd6 | ||
![]() |
25575e8510 | ||
![]() |
6c85adcf23 | ||
![]() |
5dc92d7a40 | ||
![]() |
4e2b966b80 | ||
![]() |
d34f8c3cb9 | ||
![]() |
9049ecb1cf | ||
![]() |
7bebea087c | ||
![]() |
1c79e30436 | ||
![]() |
1d7933349b | ||
![]() |
d002f67140 | ||
![]() |
da3447765b | ||
![]() |
cbf5663179 | ||
![]() |
b217fba235 | ||
![]() |
7f7e6d5aba | ||
![]() |
87c5a9d9a6 | ||
![]() |
8ca1fe3f0a | ||
![]() |
763ae8f1a6 | ||
![]() |
c65256d02b | ||
![]() |
bd2ac515d1 | ||
![]() |
681f372889 | ||
![]() |
c2eec272e6 | ||
![]() |
bd720491a9 | ||
![]() |
a408226509 | ||
![]() |
c015e99e6e | ||
![]() |
de47a17be7 | ||
![]() |
d38fc490ad | ||
![]() |
662167e792 | ||
![]() |
36c91f03d9 | ||
![]() |
33ccee26b5 |
10
.github/ISSUE_TEMPLATE/01_bug-report.md
vendored
10
.github/ISSUE_TEMPLATE/01_bug-report.md
vendored
@@ -7,24 +7,24 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
## 💡 Summary
|
||||
|
||||
<!-- Tell us what the bug is -->
|
||||
|
||||
## Expected Behavior
|
||||
## 🙂 Expected Behavior
|
||||
|
||||
<!--- Tell us what should happen -->
|
||||
|
||||
## Actual Behavior
|
||||
## ☹️ Actual Behavior
|
||||
|
||||
<!--- Tell us what happens instead of the expected behavior -->
|
||||
|
||||
## Steps to Reproduce
|
||||
## 📝 Steps to Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Environment
|
||||
## 📌 Environment
|
||||
|
||||
<!-- Tell us where on the platform it happens -->
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -5,6 +5,54 @@ If you encounter any problems with updating, please try the following:
|
||||
1. `npm run clean` or `npm run cleanall`
|
||||
2. Retry update (Don't forget `npm i`)
|
||||
|
||||
10.99.0
|
||||
----------
|
||||
* manifest.json にインスタンス名を反映させるように
|
||||
* Metaに投稿やユーザーのIDを設定するように
|
||||
* 設定でポートが指定されていない場合、環境変数を参照するように
|
||||
* フォローインポートで途中にエラーになるユーザーがいると途中で終了してしまう問題を修正
|
||||
* フォローインポートで自分が含まれていた場合自分をフォローしてしまう問題を修正
|
||||
* ServiceWorkerの設定がUIで有効にならない問題を修正
|
||||
* ユーザー一覧でのユーザーの自己紹介が複数行になることがある問題を修正
|
||||
* フォローインポートでAPI limitに達していても正常にリクエストされたように表示されてしまう問題を修正
|
||||
* DBに保存されたrepository urlを変更する方法がない問題を修正
|
||||
* デスクトップDeckだとviaが投稿内に2箇所表示される問題を修正
|
||||
* デザインの調整
|
||||
* 依存関係の更新
|
||||
* ローカリゼーション
|
||||
|
||||
10.98.3
|
||||
----------
|
||||
* リアクションのカスタム絵文字の情報がNoteに添付されない問題を修正
|
||||
* フォルダーの移動をするとき親フォルダーに自分自身を指定できてしまう問題を修正
|
||||
* デザインの調整
|
||||
|
||||
10.98.2
|
||||
----------
|
||||
* 他のインスタンスから添付画像が見れない問題を修正
|
||||
|
||||
10.98.1
|
||||
----------
|
||||
* ドライブのファイルのサムネイルが表示されない問題を修正
|
||||
* APでカスタム絵文字を送る時に常にimage/pngで送っている問題を修正
|
||||
* いくらいじってもページリロードするとmisskeyのテーマがdark(future)になっちゃう問題を修正
|
||||
|
||||
10.98.0
|
||||
----------
|
||||
* ドライブのファイルダウンロード時に元のファイル名を尊重するように
|
||||
* ドライブで画像以外のファイルを分かりやすく表示するように
|
||||
* TwemojiのCDNを変更
|
||||
* モバイルで通知の設定がない問題を修正
|
||||
* デザインの調整
|
||||
|
||||
10.97.2
|
||||
----------
|
||||
* ビルド時に警告が出ないように修正
|
||||
|
||||
10.97.1
|
||||
----------
|
||||
* デザインの調整
|
||||
|
||||
10.97.0
|
||||
----------
|
||||
* リアクションに絵文字やカスタム絵文字を使えるように
|
||||
|
16
README.md
16
README.md
@@ -101,42 +101,43 @@ 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?token-time=2145916800&token-hash=HGkZJ7s4bSaQVoOJ5q30mTWHTxDLiw1LuyaogKPLy24%3D" alt="Hiroshi Seki" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" 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/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" 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/weepjp">weep</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
|
||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=0xgcpqvFDqRcV_YIEhcPNVH7gs9sLg_BBnTJXCkN4ao%3D" alt="mydarkstar" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/18833336" alt="itiradi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" 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?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1?token-time=2145916800&token-hash=95p8VdGX45E8BitZR_eOcDlqCjumjzNLBPQJrJdeCpI%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/935a10339daa4ede8e555903a0707060/1?token-time=2145916800&token-hash=3CrpqH-XtKs_NoIlSsTyVs8wCzP1WFCsG2xwps1IJq0%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=18833336">itiradi</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13039004">nemu</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=17463605">Sampot</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>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/935a10339daa4ede8e555903a0707060/1?token-time=2145916800&token-hash=3CrpqH-XtKs_NoIlSsTyVs8wCzP1WFCsG2xwps1IJq0%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3?token-time=2145916800&token-hash=-iJszBqgYBhsM5qMdA1knf9wvprhEfESzKfR2oh7mIA%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="Hekovic" width="100"></td>
|
||||
@@ -145,6 +146,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=uR-48MQ0A4j0irQSrCAQZJ-sJUSs_Fkihlg3-l59b7c%3D" alt="Takashi Shibuya" width="100"></td>
|
||||
</tr><tr>
|
||||
<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/hiratake">Hiratake</a></td>
|
||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
|
||||
@@ -154,7 +156,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:** Tue, 12 Mar 2019 00:50:06 UTC
|
||||
**Last updated:** Fri, 05 Apr 2019 09:39:06 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
@@ -131,7 +131,7 @@ common:
|
||||
other: "Ostatní"
|
||||
appearance: "Vzhled"
|
||||
behavior: "Chování"
|
||||
fetch-on-scroll: "Nekonečné rolování"
|
||||
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"
|
||||
default-note-visibility: "Výchozí viditelnost příspěvku"
|
||||
@@ -436,17 +436,23 @@ common/views/components/user-menu.vue:
|
||||
push-to-list: "Přidat do seznamu"
|
||||
select-list: "Vyberte seznam"
|
||||
report-abuse-reported: "Problém byl nahlášen administrátorovi. Děkujeme za Vaší kooperaci."
|
||||
silence: "Ztlumit"
|
||||
suspend: "Zmrazit"
|
||||
common/views/components/poll.vue:
|
||||
vote-count: "{} hlasů"
|
||||
vote: "Hlasovat"
|
||||
show-result: "Podívat se na výsledky"
|
||||
voted: "Už jste hlasovaly"
|
||||
closed: "Ukončeno"
|
||||
remaining-days: "zbývá {d} dnů, {h} hodin"
|
||||
remaining-hours: "zbývá {h} hodin, a {m} minut"
|
||||
remaining-minutes: "zbývá {m} minut, a {s} sekund"
|
||||
remaining-seconds: "zbývá {s} sekund"
|
||||
common/views/components/poll-editor.vue:
|
||||
no-only-one-choice: "Musíte vybrat alespoň dvě možnosti"
|
||||
destroy: "Zahodit dotazník"
|
||||
infinite: "Nekonečne"
|
||||
at: "Výběr data a času"
|
||||
day: "Ne"
|
||||
common/views/components/emoji-picker.vue:
|
||||
custom-emoji: "Emoji"
|
||||
@@ -603,6 +609,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/pages/not-found.vue:
|
||||
page-not-found: "Stránka nenalezena"
|
||||
desktop:
|
||||
banner: "Baner"
|
||||
avatar-crop-title: "Vyberte část, která se zobrazí jako avatar"
|
||||
@@ -687,10 +695,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Zrušit"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Zprávy:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Zprávy"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Tento příspěvek je soukromý"
|
||||
deleted: "Tento příspěvek byl odstraněn"
|
||||
@@ -796,7 +800,6 @@ desktop/views/components/timeline.vue:
|
||||
local: "Lokální"
|
||||
global: "Globální"
|
||||
mentions: "Zmínění"
|
||||
messages: "Zprávy"
|
||||
list: "Seznamy"
|
||||
hashtag: "Hashtag"
|
||||
add-list: "Přidat do seznamu"
|
||||
@@ -1057,8 +1060,6 @@ desktop/views/pages/user/user.header.vue:
|
||||
posts: "Poznámky"
|
||||
month: "Po"
|
||||
day: "Ne"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Zprávy"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Oznámení"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1139,7 +1140,6 @@ mobile/views/pages/home.vue:
|
||||
local: "Lokální"
|
||||
global: "Globální"
|
||||
mentions: "Zmínění"
|
||||
messages: "Zprávy"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Nebyly nalezeny žádné příspěvky s \"{q}\"."
|
||||
mobile/views/pages/widgets.vue:
|
||||
|
@@ -445,10 +445,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Abbrechen"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Nachrichten:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Nachrichten"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Dieser Post ist privat"
|
||||
deleted: "Dieser Beitrag wurde entfernt"
|
||||
@@ -534,7 +530,6 @@ desktop/views/components/timeline.vue:
|
||||
home: "Home"
|
||||
local: "Lokal"
|
||||
global: "Global"
|
||||
messages: "Nachrichten"
|
||||
list: "Listen"
|
||||
desktop/views/components/ui.header.account.vue:
|
||||
profile: "Dein Profil"
|
||||
@@ -600,8 +595,6 @@ desktop/views/pages/user/user.photos.vue:
|
||||
desktop/views/pages/user/user.header.vue:
|
||||
month: "Mo"
|
||||
day: "So"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Nachrichten"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Benachrichtigungen"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -647,7 +640,6 @@ mobile/views/pages/home.vue:
|
||||
home: "Home"
|
||||
local: "Lokal"
|
||||
global: "Global"
|
||||
messages: "Nachrichten"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Hinzufügen"
|
||||
customization-tips: "Anpassung-Tipps"
|
||||
|
@@ -224,7 +224,7 @@ common:
|
||||
delete: "Delete"
|
||||
loading: "Loading"
|
||||
ok: "Confirm"
|
||||
cancel: "Exit"
|
||||
cancel: "Cancel"
|
||||
update-available-title: "Update available"
|
||||
update-available: "A new version of Misskey is now available({newer}, the current version is {current}). Reload the page to apply updates."
|
||||
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
||||
@@ -483,8 +483,8 @@ common/views/components/user-menu.vue:
|
||||
report-abuse: "Report abuse"
|
||||
report-abuse-detail: "What kind of nuisance did you encounter?"
|
||||
report-abuse-reported: "The issue has been reported to the administrator. Your cooperation is much appreciated."
|
||||
silence: "Mute"
|
||||
unsilence: "Unmute"
|
||||
silence: "Silence"
|
||||
unsilence: "Unsilence"
|
||||
suspend: "Suspend"
|
||||
unsuspend: "Unsuspend"
|
||||
common/views/components/poll.vue:
|
||||
@@ -839,10 +839,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Cancel"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Messages:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Messaging"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Post is private"
|
||||
deleted: "Post has been removed"
|
||||
@@ -992,7 +988,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
messages: "Direct posts"
|
||||
list: "Lists"
|
||||
hashtag: "Hashtag"
|
||||
add-tag-timeline: "Add hashtag cloud"
|
||||
@@ -1114,6 +1110,7 @@ admin/views/instance.vue:
|
||||
disable-global-timeline: "Disable global timeline"
|
||||
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
|
||||
enable-emoji-reaction: "Enable pictograms for reactions"
|
||||
use-star-for-reaction-fallback: "Use the star as fallback for unknown reaction"
|
||||
invite: "Invite"
|
||||
save: "Save"
|
||||
saved: "Saved"
|
||||
@@ -1202,8 +1199,8 @@ admin/views/users.vue:
|
||||
unsuspend: "Unsuspend"
|
||||
unsuspend-confirm: "Are you sure you want to unsuspend this account?"
|
||||
unsuspended: "The user has successfully unsuspended."
|
||||
make-silence: "Mute"
|
||||
unmake-silence: "Unmute"
|
||||
make-silence: "Silence"
|
||||
unmake-silence: "Unsilence"
|
||||
verify: "Verify account"
|
||||
verify-confirm: "Do you want this to be a verified account?"
|
||||
verified: "The account is now being verified"
|
||||
@@ -1388,8 +1385,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-replies: "Posts and replies"
|
||||
with-media: "Media"
|
||||
my-posts: "My posts"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Messaging"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Notifications"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1511,7 +1506,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
messages: "Direct posts"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "No posts contains \"{q}\" found."
|
||||
mobile/views/pages/widgets.vue:
|
||||
|
@@ -595,10 +595,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Cancelar"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Mensajes:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Mensajes"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Esta publicación es privada"
|
||||
deleted: "Esta publicación ha sido removida"
|
||||
@@ -714,7 +710,6 @@ desktop/views/components/timeline.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
messages: "Mensajes"
|
||||
list: "Listas"
|
||||
hashtag: "Hashtags"
|
||||
list-name: "Nombre de lista"
|
||||
@@ -841,8 +836,6 @@ desktop/views/pages/user/user.photos.vue:
|
||||
desktop/views/pages/user/user.header.vue:
|
||||
month: "lunes"
|
||||
day: "domingo"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Mensajes"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Notificaciones"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -907,7 +900,6 @@ mobile/views/pages/home.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
messages: "Mensajes"
|
||||
mobile/views/pages/widgets.vue:
|
||||
dashboard: "Panel de control"
|
||||
add-widget: "Agregar"
|
||||
|
@@ -29,6 +29,10 @@ common:
|
||||
2fa: "Authentification à deux facteurs"
|
||||
customize-home: "Personnaliser la disposition de votre accueil"
|
||||
featured-notes: "Les notes mises en avant"
|
||||
dark-mode: "Mode nuit"
|
||||
signin: "Se connecter"
|
||||
signup: "S'enregistrer"
|
||||
signout: "Se déconnecter"
|
||||
got-it: "J’ai compris !"
|
||||
customization-tips:
|
||||
title: "Conseils de personnalisation"
|
||||
@@ -111,17 +115,54 @@ common:
|
||||
d: "Désirez-vous publier quelques mots ?"
|
||||
e: "Écrivez ici"
|
||||
f: "En attente de vos écrits"
|
||||
settings: "Paramètres"
|
||||
_settings:
|
||||
profile: "Votre profil"
|
||||
notification: "Notifications"
|
||||
apps: "Applications"
|
||||
tags: "Hashtags"
|
||||
blocking: "En cours blocage"
|
||||
security: "Sécurité"
|
||||
signin: "Historique des connexions"
|
||||
password: "Mot de passe"
|
||||
other: "Avancé"
|
||||
appearance: "Apparence"
|
||||
behavior: "Comportement"
|
||||
fetch-on-scroll: "Chargement automatique lors du défilement"
|
||||
note-visibility: "Visibilité de la publication"
|
||||
default-note-visibility: "Visibilité par défaut"
|
||||
remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
|
||||
web-search-engine: "Moteur de recherche Web"
|
||||
web-search-engine-desc: "Exemple : https://www.google.com/?#q={{query}}"
|
||||
show-via: "Afficher via"
|
||||
reduce-motion: "Réduire les animations dans l’interface utilisateur"
|
||||
this-setting-is-this-device-only: "Uniquement sur cet appareil"
|
||||
use-os-default-emojis: "Utiliser les émojis standards du système"
|
||||
line-width: "Epaisseur du trait"
|
||||
line-width-thin: "Fine"
|
||||
line-width-normal: "Normale"
|
||||
line-width-thick: "Épaisse"
|
||||
font-size: "Taille du texte"
|
||||
font-size-medium: "Normale"
|
||||
font-size-x-large: "Large"
|
||||
deck-column-align-center: "Centrer"
|
||||
deck-column-align-left: "À gauche"
|
||||
deck-column-align-flexible: "Flexible"
|
||||
deck-column-width: "Largeur des colonnes du Deck"
|
||||
deck-column-width-normal: "Normale"
|
||||
timeline: "Fil d’actualité"
|
||||
navbar-position-top: "en haut"
|
||||
navbar-position-left: "À gauche"
|
||||
navbar-position-right: "à droite"
|
||||
post-style-standard: "Standard"
|
||||
post-style-smart: "Intelligent"
|
||||
notification-position: "Afficher les notifications"
|
||||
notification-position-bottom: "en bas"
|
||||
notification-position-top: "en haut"
|
||||
search: "Recherche"
|
||||
delete: "Supprimer"
|
||||
loading: "Chargement en cours …"
|
||||
cancel: "Quitter"
|
||||
update-available-title: "Mise à jour disponible"
|
||||
update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour."
|
||||
my-token-regenerated: "Votre jeton vient d’être généré, vous allez maintenant être déconnecté."
|
||||
@@ -392,6 +433,10 @@ common/views/components/poll-editor.vue:
|
||||
remove: "Supprimer ce choix"
|
||||
add: "+ Ajouter un choix"
|
||||
destroy: "Annuler ce sondage"
|
||||
interval: "Durée"
|
||||
unit: "Unité"
|
||||
second: "secondes"
|
||||
minute: "Minutes"
|
||||
day: "D"
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "Choisissez votre réaction"
|
||||
@@ -710,10 +755,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Annuler"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Messages :"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Messagerie"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "cette publication est privée"
|
||||
deleted: "cette publication a été supprimée"
|
||||
@@ -863,7 +904,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
messages: "Messages directs"
|
||||
list: "Listes"
|
||||
hashtag: "Hashtag"
|
||||
add-tag-timeline: "Ajouter un fil de hashtags"
|
||||
@@ -927,6 +968,7 @@ admin/views/dashboard.vue:
|
||||
this-instance: "Cette instance"
|
||||
federated: "Fédérées"
|
||||
admin/views/queue.vue:
|
||||
title: "File d'attente"
|
||||
remove-all-jobs: "Enlever toutes les tâches en attente"
|
||||
admin/views/abuse.vue:
|
||||
title: "Abus"
|
||||
@@ -1243,8 +1285,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-replies: "Publications et réponses"
|
||||
with-media: "Média"
|
||||
my-posts: "Mes Messages"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Messagerie"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Notifications"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1365,7 +1405,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
messages: "Messages directs"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Aucune publication ayant pour hashtag « {q} » n’a été trouvée."
|
||||
mobile/views/pages/widgets.vue:
|
||||
|
@@ -925,12 +925,6 @@ desktop/views/input-dialog.vue:
|
||||
cancel: "キャンセル"
|
||||
ok: "決定"
|
||||
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "メッセージ:"
|
||||
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "メッセージ"
|
||||
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "この投稿は非公開です"
|
||||
deleted: "この投稿は削除されました"
|
||||
@@ -1100,7 +1094,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
messages: "ダイレクト投稿"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1535,9 +1529,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-media: "メディア"
|
||||
my-posts: "私の投稿"
|
||||
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "メッセージ"
|
||||
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "通知"
|
||||
|
||||
@@ -1685,7 +1676,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
messages: "ダイレクト投稿"
|
||||
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||
|
@@ -652,10 +652,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "やめとくわ"
|
||||
ok: "これや!"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "メッセージ:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "メッセージ"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "この投稿は見せられへんわ"
|
||||
deleted: "この投稿なんか無くなってもうたわ"
|
||||
@@ -799,7 +795,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あんた宛て"
|
||||
messages: "メッセージ"
|
||||
messages: "ダイレクト投稿"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグ増やす"
|
||||
@@ -1061,8 +1057,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
default: "投稿"
|
||||
with-replies: "投稿と返信"
|
||||
with-media: "メディア"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "メッセージ"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "通知"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1183,7 +1177,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あんた宛て"
|
||||
messages: "メッセージ"
|
||||
messages: "ダイレクト投稿"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿はあらへんかった。"
|
||||
mobile/views/pages/widgets.vue:
|
||||
|
@@ -7,7 +7,7 @@ common:
|
||||
about: "Misskey를 찾아주셔서 감사합니다. Misskey는 지구에서 태어난 <b>분산 마이크로 블로그 SNS </b> 입니다. Fediverse(다양한 SNS로 구성되는 우주)에 존재하는 다른 SNS와 상호 연결되어 있습니다. 잠시 도시의 번잡함에서 벗어나 새로운 인터넷에 다이브 해 보지 않겠습니까."
|
||||
intro:
|
||||
title: "Misskey란?"
|
||||
about: "Misskey는 오픈소스 <b>분산형 마이크로블로그 SNS</b>입니다. 다양하고 폭넓게 커스터마이징할 수 있는 UI, 글에 대한 반응, 파일을 관리할 수 있는 드라이브 등의 선진적인 기능을 갖추고 있습니다. 더하여 Fediverse라고 부르는 네트워크에 연결할 수 있어 다른 SNS와도 주고받을 수 있습니다. 예를 들자면, 당신이 무언가를 게시하면, 해당 게시물은 Misskey 뿐만 아니라 다른 SNS에도 전해집니다. 살짝 어떤 행성에서 다른 행성으로 전파를 발신하고 있는 모습을 상상해주세요."
|
||||
about: "Misskey는 오픈소스 <b>분산형 마이크로블로그 SNS</b>입니다. 다양하고 폭넓게 커스터마이징할 수 있는 UI, 글에 대한 리액션, 파일을 관리할 수 있는 드라이브 등의 선진적인 기능을 갖추고 있습니다. 더하여 Fediverse라고 부르는 네트워크에 연결할 수 있어 다른 SNS와도 주고받을 수 있습니다. 예를 들자면, 당신이 무언가를 게시하면, 해당 게시물은 Misskey 뿐만 아니라 다른 SNS에도 전해집니다. 살짝 어떤 행성에서 다른 행성으로 전파를 발신하고 있는 모습을 상상해주세요."
|
||||
features: "특징"
|
||||
rich-contents: "글"
|
||||
rich-contents-desc: "자신의 생각, 화제의 사건, 모두와 공유하고 싶은 것을 올려주세요. 필요한 경우 다양한 스타일을 사용하여 글을 장식하거나 마음에 드는 이미지, 영상 등의 파일이나 투표를 올리는 것도 가능합니다."
|
||||
@@ -520,7 +520,7 @@ common/views/components/poll-editor.vue:
|
||||
hour: "시간"
|
||||
day: "일"
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "반응 선택"
|
||||
choose-reaction: "리액션 선택"
|
||||
common/views/components/emoji-picker.vue:
|
||||
custom-emoji: "커스텀 이모지"
|
||||
people: "사람들"
|
||||
@@ -839,10 +839,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "취소"
|
||||
ok: "확인"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "메시지:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "메시지"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "이 글은 비공개입니다"
|
||||
deleted: "이 글은 삭제되었습니다"
|
||||
@@ -992,7 +988,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "소셜"
|
||||
global: "글로벌"
|
||||
mentions: "받은 멘션"
|
||||
messages: "메시지"
|
||||
messages: "다이렉트 게시글"
|
||||
list: "리스트"
|
||||
hashtag: "해시태그"
|
||||
add-tag-timeline: "해시태그 추가"
|
||||
@@ -1113,6 +1109,8 @@ admin/views/instance.vue:
|
||||
disable-local-timeline: "로컬 타임라인 비활성화"
|
||||
disable-global-timeline: "글로벌 타임라인 비활성화"
|
||||
disabling-timelines-info: "이 타임라인들을 비활성화해도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
||||
enable-emoji-reaction: "리액션에 이모지를 사용할 수 있게 함"
|
||||
use-star-for-reaction-fallback: "알 수 없는 리액션을 star로 대체하여 사용"
|
||||
invite: "초대"
|
||||
save: "저장"
|
||||
saved: "저장하였습니다"
|
||||
@@ -1387,8 +1385,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-replies: "글과 답글"
|
||||
with-media: "미디어"
|
||||
my-posts: "내 글"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "메시지"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "알림"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1510,7 +1506,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "소셜"
|
||||
global: "글로벌"
|
||||
mentions: "받은 멘션"
|
||||
messages: "메시지"
|
||||
messages: "다이렉트 게시글"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "해시태그 \"{q}\"가 붙은 글을 찾을 수 없습니다."
|
||||
mobile/views/pages/widgets.vue:
|
||||
|
@@ -307,10 +307,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Annuleren"
|
||||
ok: "Oké"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Berichten:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Gesprekken"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "(dit bericht is privé)"
|
||||
location: "Locatie"
|
||||
@@ -394,7 +390,6 @@ desktop/views/components/timeline.vue:
|
||||
home: "Startpagina"
|
||||
local: "Lokaal"
|
||||
global: "Algemeen"
|
||||
messages: "Gesprekken"
|
||||
list: "Lijsten"
|
||||
desktop/views/components/ui.header.account.vue:
|
||||
profile: "Je profiel"
|
||||
@@ -497,8 +492,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
default: "Berichten"
|
||||
with-replies: "Berichten en antwoorden"
|
||||
with-media: "Media"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Gesprekken"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Meldingen"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -571,7 +564,6 @@ mobile/views/pages/home.vue:
|
||||
home: "Startpagina"
|
||||
local: "Lokaal"
|
||||
global: "Algemeen"
|
||||
messages: "Gesprekken"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Toevoegen"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
|
@@ -244,8 +244,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Avbryt"
|
||||
ok: "Ok"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Samtaler"
|
||||
desktop/views/components/note-detail.vue:
|
||||
location: "Lokasjon"
|
||||
desktop/views/components/note.vue:
|
||||
@@ -290,7 +288,6 @@ desktop/views/components/timeline.vue:
|
||||
home: "Hjem"
|
||||
local: "Lokalt"
|
||||
global: "Globalt"
|
||||
messages: "Samtaler"
|
||||
list: "Lister"
|
||||
list-name: "Liste navn"
|
||||
desktop/views/components/ui.header.vue:
|
||||
@@ -394,8 +391,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
default: "Innlegg"
|
||||
with-replies: "Innlegg og svar"
|
||||
with-media: "Media"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Melding"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Notifikasjon"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -456,7 +451,6 @@ mobile/views/pages/home.vue:
|
||||
home: "Hjem"
|
||||
local: "Lokalt"
|
||||
global: "Globalt"
|
||||
messages: "Samtaler"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Legg til"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
|
@@ -146,9 +146,11 @@ common:
|
||||
choose-wallpaper: "Wybierz tapetę"
|
||||
timeline: "Oś czasu"
|
||||
sound: "Dźwięk"
|
||||
volume: "Głośność"
|
||||
test: "Test"
|
||||
update: "Aktualizacja Misskey"
|
||||
version: "Wersja:"
|
||||
do-update: "Sprawdź dostępność nowych aktualizacji"
|
||||
navbar-position-left: "Z lewej"
|
||||
search: "Szukaj"
|
||||
delete: "Usuń"
|
||||
@@ -677,10 +679,6 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Anuluj"
|
||||
ok: "OK"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "Wiadomości:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "Wiadomości"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "ten wpis jest prywatny"
|
||||
deleted: "ten wpis został usunięty"
|
||||
@@ -815,7 +813,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "Wspomnienia"
|
||||
messages: "Wiadomości"
|
||||
messages: "Bezpośrednie wpisy"
|
||||
list: "Listy"
|
||||
hashtag: "Hashtag"
|
||||
add-tag-timeline: "Dodaj hashtag"
|
||||
@@ -1009,8 +1007,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-replies: "Wpisy i odpowiedzi"
|
||||
with-media: "Multimedia"
|
||||
my-posts: "Moje wpisy"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "Wiadomości"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "Powiadomienia"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1126,7 +1122,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "Wspomnienia"
|
||||
messages: "Wiadomości"
|
||||
messages: "Bezpośrednie wpisy"
|
||||
mobile/views/pages/widgets.vue:
|
||||
dashboard: "Kokpit"
|
||||
add-widget: "Dodaj"
|
||||
|
@@ -577,7 +577,7 @@ common/views/components/notification-settings.vue:
|
||||
mark-as-read-all-unread-notes: "将所有帖子标为已读"
|
||||
mark-as-read-all-talk-messages: "将所有对话标为已读"
|
||||
auto-watch: "自动查看帖子"
|
||||
auto-watch-desc: "自动接收有关您做出反应或回复的帖子的通知。"
|
||||
auto-watch-desc: "自动接收有关您做出回应或回复的帖子的通知。"
|
||||
common/views/components/integration-settings.vue:
|
||||
title: "服务合作"
|
||||
connect: "连接"
|
||||
@@ -839,22 +839,18 @@ desktop/views/components/home.vue:
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "取消"
|
||||
ok: "确定"
|
||||
desktop/views/components/messaging-room-window.vue:
|
||||
title: "信息:"
|
||||
desktop/views/components/messaging-window.vue:
|
||||
title: "正在聊天"
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "私密投稿"
|
||||
deleted: "投稿已删除"
|
||||
location: "位置信息"
|
||||
renote: "转发"
|
||||
add-reaction: "添加一个反应"
|
||||
undo-reaction: "取消反应"
|
||||
add-reaction: "回应"
|
||||
undo-reaction: "取消回应"
|
||||
desktop/views/components/note.vue:
|
||||
reply: "回复"
|
||||
renote: "Renote"
|
||||
add-reaction: "添加一个反应"
|
||||
undo-reaction: "取消反应"
|
||||
add-reaction: "回应"
|
||||
undo-reaction: "取消回应"
|
||||
detail: "详细信息"
|
||||
private: "这个投稿是私密的"
|
||||
deleted: "投稿已删除"
|
||||
@@ -992,7 +988,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "社交"
|
||||
global: "全球"
|
||||
mentions: "提到的"
|
||||
messages: "信息"
|
||||
messages: "直接发布"
|
||||
list: "列表"
|
||||
hashtag: "标签"
|
||||
add-tag-timeline: "添加标签"
|
||||
@@ -1113,6 +1109,8 @@ admin/views/instance.vue:
|
||||
disable-local-timeline: "停用本地时间线功能"
|
||||
disable-global-timeline: "禁用全局时间线"
|
||||
disabling-timelines-info: "即使禁用时间线,管理员和版主仍然可用。"
|
||||
enable-emoji-reaction: "在回应上使用表情符号"
|
||||
use-star-for-reaction-fallback: "使用默认的star来表示未知的回应"
|
||||
invite: "邀请"
|
||||
save: "保存"
|
||||
saved: "保存完毕"
|
||||
@@ -1387,8 +1385,6 @@ desktop/views/pages/user/user.timeline.vue:
|
||||
with-replies: "帖子与回复"
|
||||
with-media: "媒体"
|
||||
my-posts: "我的帖子"
|
||||
desktop/views/widgets/messaging.vue:
|
||||
title: "信息"
|
||||
desktop/views/widgets/notifications.vue:
|
||||
title: "通知"
|
||||
desktop/views/widgets/polls.vue:
|
||||
@@ -1452,7 +1448,7 @@ mobile/views/components/note.vue:
|
||||
location: "位置信息"
|
||||
mobile/views/components/note-detail.vue:
|
||||
reply: "回复"
|
||||
reaction: "反应"
|
||||
reaction: "回应"
|
||||
private: "这个帖子是私密的"
|
||||
deleted: "帖子已删除"
|
||||
location: "位置信息"
|
||||
@@ -1510,7 +1506,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "信息"
|
||||
messages: "直接发布"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "没有找到带有主题标签“{q}”的帖子"
|
||||
mobile/views/pages/widgets.vue:
|
||||
@@ -1639,7 +1635,7 @@ dev/views/new-app.vue:
|
||||
account-read: "查看账户信息"
|
||||
account-write: "修改账户信息"
|
||||
note-write: "投稿。"
|
||||
reaction-write: "添加或删除反应。"
|
||||
reaction-write: "添加或删除回应。"
|
||||
following-write: "关注和不关注"
|
||||
drive-read: "查看网盘"
|
||||
drive-write: "管理网盘文件。"
|
||||
|
35
package.json
35
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.97.0",
|
||||
"version": "10.99.0",
|
||||
"codename": "nighthike",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -77,6 +77,7 @@
|
||||
"@types/qrcode": "1.3.0",
|
||||
"@types/ratelimiter": "2.1.28",
|
||||
"@types/redis": "2.8.10",
|
||||
"@types/rename": "1.0.1",
|
||||
"@types/request": "2.48.1",
|
||||
"@types/request-promise-native": "1.0.15",
|
||||
"@types/request-stats": "3.0.0",
|
||||
@@ -95,7 +96,7 @@
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
"animejs": "3.0.1",
|
||||
"apexcharts": "3.6.2",
|
||||
"apexcharts": "3.6.5",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
@@ -106,7 +107,8 @@
|
||||
"chai": "4.2.0",
|
||||
"chai-http": "4.2.1",
|
||||
"chalk": "2.4.2",
|
||||
"commander": "2.19.0",
|
||||
"commander": "2.20.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "2.1.1",
|
||||
"cssnano": "4.1.10",
|
||||
@@ -122,7 +124,7 @@
|
||||
"eslint-plugin-vue": "5.2.2",
|
||||
"eventemitter3": "3.1.0",
|
||||
"feed": "2.0.4",
|
||||
"file-type": "10.9.0",
|
||||
"file-type": "10.10.0",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gulp": "4.0.0",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
@@ -142,7 +144,7 @@
|
||||
"insert-text-at-cursor": "0.1.2",
|
||||
"is-root": "2.0.0",
|
||||
"is-svg": "4.0.0",
|
||||
"js-yaml": "3.12.2",
|
||||
"js-yaml": "3.13.0",
|
||||
"jsdom": "14.0.0",
|
||||
"json5": "2.1.0",
|
||||
"json5-loader": "1.0.1",
|
||||
@@ -158,7 +160,7 @@
|
||||
"koa-router": "7.4.0",
|
||||
"koa-send": "5.0.0",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.1.5",
|
||||
"koa-views": "6.2.0",
|
||||
"langmap": "0.0.16",
|
||||
"loader-utils": "1.2.3",
|
||||
"lookup-dns-cache": "2.1.0",
|
||||
@@ -167,7 +169,7 @@
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
"moment": "2.24.0",
|
||||
"mongodb": "3.1.13",
|
||||
"mongodb": "3.2.2",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nan": "2.12.1",
|
||||
@@ -180,7 +182,7 @@
|
||||
"parsimmon": "1.12.0",
|
||||
"portscanner": "2.2.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"prismjs": "1.15.0",
|
||||
"prismjs": "1.16.0",
|
||||
"progress-bar-webpack-plugin": "1.12.1",
|
||||
"promise-any": "0.2.0",
|
||||
"promise-limit": "2.7.0",
|
||||
@@ -193,6 +195,7 @@
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.1.10",
|
||||
"redis": "2.8.0",
|
||||
"rename": "1.0.4",
|
||||
"request": "2.88.0",
|
||||
"request-promise-native": "1.0.7",
|
||||
"request-stats": "3.0.0",
|
||||
@@ -200,7 +203,7 @@
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"seedrandom": "2.4.4",
|
||||
"sharp": "0.21.3",
|
||||
"sharp": "0.22.0",
|
||||
"showdown": "1.9.0",
|
||||
"showdown-highlightjs-extension": "0.1.2",
|
||||
"speakeasy": "2.0.0",
|
||||
@@ -227,22 +230,22 @@
|
||||
"v-animate-css": "0.0.3",
|
||||
"v-debounce": "0.1.2",
|
||||
"video-thumbnail-generator": "1.1.3",
|
||||
"vue": "2.6.8",
|
||||
"vue": "2.6.10",
|
||||
"vue-color": "2.7.0",
|
||||
"vue-content-loading": "1.5.3",
|
||||
"vue-cropperjs": "3.0.0",
|
||||
"vue-i18n": "8.9.0",
|
||||
"vue-i18n": "8.10.0",
|
||||
"vue-js-modal": "1.3.28",
|
||||
"vue-json-pretty": "1.4.1",
|
||||
"vue-json-pretty": "1.6.0",
|
||||
"vue-loader": "15.7.0",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
"vue-prism-component": "1.1.1",
|
||||
"vue-router": "3.0.2",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.13",
|
||||
"vue-template-compiler": "2.6.8",
|
||||
"vuedraggable": "2.19.2",
|
||||
"vue-svg-inline-loader": "1.2.15",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.20.0",
|
||||
"vuewordcloud": "18.7.11",
|
||||
"vuex": "3.1.0",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
@@ -251,7 +254,7 @@
|
||||
"webpack": "4.28.4",
|
||||
"webpack-cli": "3.2.3",
|
||||
"websocket": "1.0.28",
|
||||
"ws": "6.2.0",
|
||||
"ws": "6.2.1",
|
||||
"xev": "2.0.1"
|
||||
}
|
||||
}
|
||||
|
14
src/@types/deepcopy.d.ts
vendored
14
src/@types/deepcopy.d.ts
vendored
@@ -1,17 +1,19 @@
|
||||
declare module 'deepcopy';
|
||||
|
||||
declare namespace deepcopy {
|
||||
declare module 'deepcopy' {
|
||||
type DeepcopyCustomizerValueType = 'Object';
|
||||
|
||||
type DeepcopyCustomizer<T> = (
|
||||
value: T,
|
||||
valueType: DeepcopyCustomizerValueType) => T;
|
||||
|
||||
interface DeepcopyOptions<T> {
|
||||
interface IDeepcopyOptions<T> {
|
||||
customizer: DeepcopyCustomizer<T>;
|
||||
}
|
||||
|
||||
export function deepcopy<T>(
|
||||
function deepcopy<T>(
|
||||
value: T,
|
||||
options?: DeepcopyOptions<T> | DeepcopyCustomizer<T>): T;
|
||||
options?: IDeepcopyOptions<T> | DeepcopyCustomizer<T>): T;
|
||||
|
||||
namespace deepcopy {} // Hack
|
||||
|
||||
export = deepcopy;
|
||||
}
|
||||
|
2
src/@types/koa-slow.d.ts
vendored
2
src/@types/koa-slow.d.ts
vendored
@@ -8,7 +8,7 @@ declare module 'koa-slow' {
|
||||
|
||||
function slow(options?: ISlowOptions): Middleware;
|
||||
|
||||
namespace slow { } // Hack
|
||||
namespace slow {} // Hack
|
||||
|
||||
export = slow;
|
||||
}
|
||||
|
@@ -245,6 +245,7 @@ export default Vue.extend({
|
||||
federationInstancesChart(total: boolean): any {
|
||||
return {
|
||||
series: [{
|
||||
name: 'Instances',
|
||||
data: this.format(total
|
||||
? this.stats.federation.instance.total
|
||||
: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))
|
||||
|
@@ -30,6 +30,7 @@
|
||||
import Vue from 'vue';
|
||||
import * as emojilib from 'emojilib';
|
||||
import contains from '../../../common/scripts/contains';
|
||||
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||
|
||||
type EmojiDef = {
|
||||
emoji: string;
|
||||
@@ -54,7 +55,7 @@ const emjdb: EmojiDef[] = lib.map((x: any) => ({
|
||||
emoji: x[1].char,
|
||||
name: x[0],
|
||||
aliasOf: null,
|
||||
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
|
||||
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||
}));
|
||||
|
||||
for (const x of lib as any) {
|
||||
@@ -64,7 +65,7 @@ for (const x of lib as any) {
|
||||
emoji: x[1].char,
|
||||
name: k,
|
||||
aliasOf: x[0],
|
||||
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
|
||||
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
187
src/client/app/common/views/components/drive-file-thumbnail.vue
Normal file
187
src/client/app/common/views/components/drive-file-thumbnail.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`">
|
||||
<img
|
||||
:src="file.url"
|
||||
:alt="file.name"
|
||||
:title="file.name"
|
||||
@load="onThumbnailLoaded"
|
||||
v-if="detail && is === 'image'"/>
|
||||
<video
|
||||
:src="file.url"
|
||||
ref="volumectrl"
|
||||
preload="metadata"
|
||||
controls
|
||||
v-else-if="detail && is === 'video'"/>
|
||||
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
|
||||
<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
|
||||
<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
|
||||
|
||||
<audio
|
||||
:src="file.url"
|
||||
ref="volumectrl"
|
||||
preload="metadata"
|
||||
controls
|
||||
v-else-if="detail && is === 'audio'"/>
|
||||
<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/>
|
||||
|
||||
<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/>
|
||||
<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/>
|
||||
<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/>
|
||||
<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/>
|
||||
<fa :icon="faFile" class="icon" v-else/>
|
||||
|
||||
<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import anime from 'animejs';
|
||||
import {
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileImage,
|
||||
faMusic,
|
||||
faFileVideo,
|
||||
faFileCsv,
|
||||
faFilePdf,
|
||||
faFileArchive,
|
||||
faFilm
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
fit: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
detail: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isContextmenuShowing: false,
|
||||
isDragging: false,
|
||||
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileImage,
|
||||
faMusic,
|
||||
faFileVideo,
|
||||
faFileCsv,
|
||||
faFilePdf,
|
||||
faFileArchive,
|
||||
faFilm
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' {
|
||||
if (this.file.type.startsWith('image/')) return 'image';
|
||||
if (this.file.type.startsWith('video/')) return 'video';
|
||||
if (this.file.type === 'audio/midi') return 'midi';
|
||||
if (this.file.type.startsWith('audio/')) return 'audio';
|
||||
if (this.file.type.endsWith('/csv')) return 'csv';
|
||||
if (this.file.type.endsWith('/pdf')) return 'pdf';
|
||||
if (this.file.type.startsWith('text/')) return 'textfile';
|
||||
if ([
|
||||
"application/zip",
|
||||
"application/x-cpio",
|
||||
"application/x-bzip",
|
||||
"application/x-bzip2",
|
||||
"application/java-archive",
|
||||
"application/x-rar-compressed",
|
||||
"application/x-tar",
|
||||
"application/gzip",
|
||||
"application/x-7z-compressed"
|
||||
].some(e => e === this.file.type)) return 'archive';
|
||||
return 'unknown';
|
||||
},
|
||||
isThumbnailAvailable(): boolean {
|
||||
return this.file.thumbnailUrl
|
||||
? this.file.thumbnailUrl.endsWith('?thumbnail')
|
||||
? (this.is === 'image' || this.is === 'video')
|
||||
: true
|
||||
: false;
|
||||
},
|
||||
background(): string {
|
||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
|
||||
? `rgb(${this.file.properties.avgColor.join(',')})`
|
||||
: 'transparent';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
|
||||
if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume;
|
||||
},
|
||||
methods: {
|
||||
onThumbnailLoaded() {
|
||||
if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) {
|
||||
anime({
|
||||
targets: this.$refs.thumbnail,
|
||||
backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
}
|
||||
},
|
||||
volumechange() {
|
||||
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
|
||||
this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume });
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.zdjebgpv
|
||||
display flex
|
||||
|
||||
> img,
|
||||
> .icon
|
||||
pointer-events none
|
||||
|
||||
> .icon-sub
|
||||
position absolute
|
||||
width 30%
|
||||
height auto
|
||||
margin 0
|
||||
right 4%
|
||||
bottom 4%
|
||||
|
||||
> *
|
||||
margin auto
|
||||
|
||||
&:not(.detail)
|
||||
> img
|
||||
height 100%
|
||||
width 100%
|
||||
object-fit cover
|
||||
|
||||
> .icon
|
||||
height 65%
|
||||
width 65%
|
||||
|
||||
> video,
|
||||
> audio
|
||||
width 100%
|
||||
|
||||
&.detail
|
||||
> .icon
|
||||
height 100px
|
||||
width 100px
|
||||
margin 16px
|
||||
|
||||
> *:not(.icon)
|
||||
max-height 300px
|
||||
max-width 100%
|
||||
height 100%
|
||||
object-fit contain
|
||||
|
||||
</style>
|
@@ -10,6 +10,7 @@ import Vue from 'vue';
|
||||
// スクリプトサイズがデカい
|
||||
//import { lib } from 'emojilib';
|
||||
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
||||
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@@ -77,7 +78,7 @@ export default Vue.extend({
|
||||
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
|
||||
codes = codes.filter(x => x && x.length);
|
||||
|
||||
this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`;
|
||||
this.url = `${twemojiBase}/2/svg/${codes.join('-')}.svg`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a class="a" :href="repo" target="_blank" title="View source on GitHub">
|
||||
<a class="a" :href="repositoryUrl" target="_blank" title="View source on GitHub">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path>
|
||||
@@ -15,12 +15,6 @@ export default Vue.extend({
|
||||
return {
|
||||
repositoryUrl: 'https://github.com/syuilo/misskey'
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
if (meta.maintainer)
|
||||
this.repositoryUrl = meta.maintainer.repository_url;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -23,12 +23,6 @@ export default Vue.extend({
|
||||
repositoryUrl: 'https://github.com/syuilo/misskey',
|
||||
feedbackUrl: 'https://github.com/syuilo/misskey/issues/new'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
if (meta.maintainer.repository_url) this.repositoryUrl = meta.maintainer.repository_url;
|
||||
if (meta.maintainer.feedback_url) this.feedbackUrl = meta.maintainer.feedback_url;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -278,7 +278,7 @@ export default Vue.extend({
|
||||
border-bottom solid var(--lineWidth) var(--faceDivider)
|
||||
|
||||
> .buttons
|
||||
padding 4px
|
||||
padding 4px 4px 8px 4px
|
||||
width 216px
|
||||
text-align center
|
||||
|
||||
@@ -316,7 +316,7 @@ export default Vue.extend({
|
||||
|
||||
> .text
|
||||
width 216px
|
||||
padding 4px 8px 8px 8px
|
||||
padding 0 8px 8px 8px
|
||||
|
||||
> input
|
||||
width 100%
|
||||
|
@@ -305,11 +305,16 @@ export default Vue.extend({
|
||||
this.exportTarget == 'user-lists' ? 'i/import-user-lists' :
|
||||
null, {
|
||||
fileId: file.id
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'info',
|
||||
text: this.$t('import-requested')
|
||||
});
|
||||
}).catch((e: any) => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.message
|
||||
});
|
||||
|
||||
this.$root.dialog({
|
||||
type: 'info',
|
||||
text: this.$t('import-requested')
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@@ -159,7 +159,7 @@
|
||||
</template>
|
||||
|
||||
<template v-if="page == null || page == 'notification'">
|
||||
<x-notification v-show="page == 'notification'"/>
|
||||
<x-notification/>
|
||||
</template>
|
||||
|
||||
<template v-if="page == null || page == 'drive'">
|
||||
|
@@ -3,12 +3,14 @@
|
||||
<ol v-if="uploads.length > 0">
|
||||
<li v-for="ctx in uploads" :key="ctx.id">
|
||||
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
|
||||
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
|
||||
<p class="status">
|
||||
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
|
||||
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
|
||||
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
|
||||
</p>
|
||||
<div class="top">
|
||||
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
|
||||
<p class="status">
|
||||
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
|
||||
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
|
||||
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress>
|
||||
<div class="progress initing" v-if="ctx.progress == undefined"></div>
|
||||
<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div>
|
||||
@@ -116,12 +118,17 @@ export default Vue.extend({
|
||||
list-style none
|
||||
|
||||
> li
|
||||
display block
|
||||
display grid
|
||||
margin 8px 0 0 0
|
||||
padding 0
|
||||
height 36px
|
||||
width: 100%
|
||||
box-shadow 0 -1px 0 var(--primaryAlpha01)
|
||||
border-top solid 8px transparent
|
||||
grid-template-columns 36px calc(100% - 44px)
|
||||
grid-template-rows 1fr 8px
|
||||
column-gap 8px
|
||||
box-sizing content-box
|
||||
|
||||
&:first-child
|
||||
margin 0
|
||||
@@ -130,68 +137,62 @@ export default Vue.extend({
|
||||
|
||||
> .img
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 36px
|
||||
height 36px
|
||||
background-size cover
|
||||
background-position center center
|
||||
grid-column 1 / 2
|
||||
grid-row 1 / 3
|
||||
|
||||
> .name
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 44px
|
||||
margin 0
|
||||
padding 0
|
||||
max-width 256px
|
||||
font-size 0.8em
|
||||
color var(--primaryAlpha07)
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow hidden
|
||||
|
||||
> [data-icon]
|
||||
margin-right 4px
|
||||
|
||||
> .status
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 0.8em
|
||||
|
||||
> .initing
|
||||
color var(--primaryAlpha05)
|
||||
|
||||
> .kb
|
||||
color var(--primaryAlpha05)
|
||||
|
||||
> .percentage
|
||||
display inline-block
|
||||
width 48px
|
||||
text-align right
|
||||
> .top
|
||||
display flex
|
||||
grid-column 2 / 3
|
||||
grid-row 1 / 2
|
||||
|
||||
> .name
|
||||
display block
|
||||
padding 0 8px 0 0
|
||||
margin 0
|
||||
font-size 0.8em
|
||||
color var(--primaryAlpha07)
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow hidden
|
||||
flex-shrink 1
|
||||
|
||||
&:after
|
||||
content '%'
|
||||
> [data-icon]
|
||||
margin-right 4px
|
||||
|
||||
> .status
|
||||
display block
|
||||
margin 0 0 0 auto
|
||||
padding 0
|
||||
font-size 0.8em
|
||||
flex-shrink 0
|
||||
|
||||
> .initing
|
||||
color var(--primaryAlpha05)
|
||||
|
||||
> .kb
|
||||
color var(--primaryAlpha05)
|
||||
|
||||
> .percentage
|
||||
display inline-block
|
||||
width 48px
|
||||
text-align right
|
||||
|
||||
color var(--primaryAlpha07)
|
||||
|
||||
&:after
|
||||
content '%'
|
||||
|
||||
> progress
|
||||
display block
|
||||
position absolute
|
||||
bottom 0
|
||||
right 0
|
||||
margin 0
|
||||
width calc(100% - 44px)
|
||||
height 8px
|
||||
background transparent
|
||||
border none
|
||||
border-radius 4px
|
||||
overflow hidden
|
||||
grid-column 2 / 3
|
||||
grid-row 2 / 3
|
||||
z-index 2
|
||||
|
||||
&::-webkit-progress-value
|
||||
background var(--primary)
|
||||
@@ -201,12 +202,6 @@ export default Vue.extend({
|
||||
|
||||
> .progress
|
||||
display block
|
||||
position absolute
|
||||
bottom 0
|
||||
right 0
|
||||
margin 0
|
||||
width calc(100% - 44px)
|
||||
height 8px
|
||||
border none
|
||||
border-radius 4px
|
||||
background linear-gradient(
|
||||
@@ -221,6 +216,9 @@ export default Vue.extend({
|
||||
)
|
||||
background-size 32px 32px
|
||||
animation bg 1.5s linear infinite
|
||||
grid-column 2 / 3
|
||||
grid-row 2 / 3
|
||||
z-index 1
|
||||
|
||||
&.initing
|
||||
opacity 0.3
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<p class="username">@{{ user | acct }}</p>
|
||||
</div>
|
||||
<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" :should-break="false"/>
|
||||
<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :should-break="false" :plain-text="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p>
|
||||
</template>
|
||||
<template v-else-if="item.type == 'link'">
|
||||
<a :href="item.href" :target="item.target" @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
|
||||
<a :href="item.href" :target="item.target" @click="click(item)" :download="item.download"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
|
||||
</template>
|
||||
<template v-else-if="item.type == 'nest'">
|
||||
<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<template #header><fa icon="crop"/>{{ title }}</template>
|
||||
<div class="body">
|
||||
<vue-cropper ref="cropper"
|
||||
:src="image.url"
|
||||
:src="imageUrl"
|
||||
:view-mode="1"
|
||||
:aspect-ratio="aspectRatio"
|
||||
:container-style="{ width: '100%', 'max-height': '400px' }"
|
||||
@@ -21,6 +21,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import * as url from '../../../../../prelude/url';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/crop-window.vue'),
|
||||
@@ -41,6 +42,13 @@ export default Vue.extend({
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return `/proxy/?${url.query({
|
||||
url: this.image.url
|
||||
})}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
ok() {
|
||||
(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => {
|
||||
|
@@ -21,9 +21,9 @@
|
||||
<img src="/assets/label-red.svg"/>
|
||||
<p>{{ $t('nsfw') }}</p>
|
||||
</div>
|
||||
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
||||
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
|
||||
</div>
|
||||
|
||||
<x-file-thumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||
|
||||
<p class="name">
|
||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
|
||||
@@ -34,14 +34,18 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import anime from 'animejs';
|
||||
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({
|
||||
i18n: i18n('desktop/views/components/drive.file.vue'),
|
||||
props: ['file'],
|
||||
components: {
|
||||
XFileThumbnail
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isContextmenuShowing: false,
|
||||
@@ -57,11 +61,6 @@ export default Vue.extend({
|
||||
},
|
||||
title(): string {
|
||||
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
|
||||
},
|
||||
background(): string {
|
||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
|
||||
? `rgb(${this.file.properties.avgColor.join(',')})`
|
||||
: 'transparent';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -88,9 +87,10 @@ export default Vue.extend({
|
||||
action: this.copyUrl
|
||||
}, {
|
||||
type: 'link',
|
||||
href: `${this.file.url}?download`,
|
||||
href: appendQuery(this.file.url, 'download'),
|
||||
text: this.$t('contextmenu.download'),
|
||||
icon: 'download',
|
||||
download: this.file.name
|
||||
}, null, {
|
||||
type: 'item',
|
||||
text: this.$t('@.delete'),
|
||||
@@ -205,7 +205,7 @@ export default Vue.extend({
|
||||
<style lang="stylus" scoped>
|
||||
.gvfdktuvdgwhmztnuekzkswkjygptfcv
|
||||
padding 8px 0 0 0
|
||||
height 180px
|
||||
min-height 180px
|
||||
border-radius 4px
|
||||
|
||||
&, *
|
||||
@@ -254,6 +254,9 @@ export default Vue.extend({
|
||||
> .name
|
||||
color var(--primaryForeground)
|
||||
|
||||
> .thumbnail
|
||||
color var(--primaryForeground)
|
||||
|
||||
&[data-is-contextmenu-showing]
|
||||
&:after
|
||||
content ""
|
||||
@@ -319,18 +322,7 @@ export default Vue.extend({
|
||||
width 128px
|
||||
height 128px
|
||||
margin auto
|
||||
|
||||
> img
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
bottom 0
|
||||
margin auto
|
||||
max-width 128px
|
||||
max-height 128px
|
||||
pointer-events none
|
||||
color var(--driveFileIcon)
|
||||
|
||||
> .name
|
||||
display block
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
||||
<template #header><fa icon="comments"/> {{ $t('title') }} <mk-user-name :user="user"/></template>
|
||||
<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name :user="user"/></template>
|
||||
<x-messaging-room :user="user" :class="$style.content"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
@@ -12,7 +12,7 @@ import { url } from '../../../config';
|
||||
import getAcct from '../../../../../misc/acct/render';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/messaging-room-window.vue'),
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
|
||||
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</template>
|
||||
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template>
|
||||
<x-messaging :class="$style.content" @navigate="navigate"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
@@ -11,7 +11,7 @@ import i18n from '../../../i18n';
|
||||
import MkMessagingRoomWindow from './messaging-room-window.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/messaging-window.vue'),
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||
},
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<article class="article">
|
||||
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="appearNote"/>
|
||||
<mk-note-header class="header" :note="appearNote" :mini="narrow"/>
|
||||
<div class="body" v-if="appearNote.deletedAt == null">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" />
|
||||
|
@@ -12,7 +12,7 @@ import parseAcct from '../../../../../misc/acct/parse';
|
||||
import getUserName from '../../../../../misc/get-user-name';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('.vue'),
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||
},
|
||||
@@ -51,7 +51,7 @@ export default Vue.extend({
|
||||
this.user = user;
|
||||
this.fetching = false;
|
||||
|
||||
document.title = `メッセージ: ${getUserName(this.user)}`;
|
||||
document.title = this.$t('@.messaging') + ': ' + getUserName(this.user);
|
||||
|
||||
Progress.done();
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mkw-messaging">
|
||||
<ui-container :show-header="props.design == 0">
|
||||
<template #header><fa icon="comments"/>{{ $t('title') }}</template>
|
||||
<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template>
|
||||
<template #func><button @click="add"><fa icon="plus"/></button></template>
|
||||
|
||||
<x-messaging ref="index" compact @navigate="navigate"/>
|
||||
@@ -21,7 +21,7 @@ export default define({
|
||||
design: 0
|
||||
})
|
||||
}).extend({
|
||||
i18n: i18n('desktop/views/widgets/messaging.vue'),
|
||||
i18n: i18n(''),
|
||||
components: {
|
||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||
},
|
||||
|
@@ -1,9 +1,9 @@
|
||||
@charset "utf-8";
|
||||
|
||||
/**
|
||||
* Boot screen style
|
||||
*/
|
||||
|
||||
@charset 'utf-8';
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
@@ -16,11 +16,11 @@ import App from './app.vue';
|
||||
import checkForUpdate from './common/scripts/check-for-update';
|
||||
import MiOS from './mios';
|
||||
import { version, codename, lang, locale } from './config';
|
||||
import { builtinThemes, applyTheme, blackTheme } from './theme';
|
||||
import { builtinThemes, applyTheme, futureTheme } from './theme';
|
||||
import Dialog from './common/views/components/dialog.vue';
|
||||
|
||||
if (localStorage.getItem('theme') == null) {
|
||||
applyTheme(blackTheme);
|
||||
applyTheme(futureTheme);
|
||||
}
|
||||
|
||||
//#region FontAwesome
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="pyvicwrksnfyhpfgkjwqknuururpaztw">
|
||||
<div class="preview">
|
||||
<img v-if="kind == 'image'" ref="img"
|
||||
:src="file.url"
|
||||
:alt="file.name"
|
||||
:title="file.name"
|
||||
:style="style">
|
||||
<x-file-thumbnail class="preview" :file="file" :detail="true"/>
|
||||
<template v-if="kind != 'image'"><fa icon="file"/></template>
|
||||
<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height">
|
||||
<span class="size">
|
||||
@@ -38,7 +34,7 @@
|
||||
<div class="menu">
|
||||
<div>
|
||||
<ui-input readonly :value="file.url">URL</ui-input>
|
||||
<ui-button link :href="`${file.url}?download`" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
|
||||
<ui-button link :href="dlUrl" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
|
||||
<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
|
||||
<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
|
||||
<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button>
|
||||
@@ -61,11 +57,17 @@
|
||||
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({
|
||||
i18n: i18n('mobile/views/components/drive.file-detail.vue'),
|
||||
props: ['file'],
|
||||
|
||||
components: {
|
||||
XFileThumbnail
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
gcd,
|
||||
@@ -86,6 +88,10 @@ export default Vue.extend({
|
||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
|
||||
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
|
||||
} : {};
|
||||
},
|
||||
|
||||
dlUrl(): string {
|
||||
return appendQuery(this.file.url, 'download');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -142,12 +148,13 @@ export default Vue.extend({
|
||||
padding 8px
|
||||
background var(--bg)
|
||||
|
||||
> img
|
||||
display block
|
||||
> .preview
|
||||
width fit-content
|
||||
max-width 100%
|
||||
max-height 300px
|
||||
margin 0 auto
|
||||
box-shadow 1px 1px 4px rgba(#000, 0.2)
|
||||
overflow hidden
|
||||
color var(--driveFileIcon)
|
||||
|
||||
> footer
|
||||
padding 8px 8px 0 8px
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
|
||||
<div class="container">
|
||||
<div class="thumbnail" :style="thumbnail"></div>
|
||||
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
|
||||
<div class="body">
|
||||
<p class="name">
|
||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||
@@ -26,9 +26,14 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/drive.file.vue'),
|
||||
props: ['file'],
|
||||
components: {
|
||||
XFileThumbnail
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSelected: false
|
||||
@@ -37,12 +42,6 @@ export default Vue.extend({
|
||||
computed: {
|
||||
browser(): any {
|
||||
return this.$parent;
|
||||
},
|
||||
thumbnail(): any {
|
||||
return {
|
||||
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
||||
'background-image': `url(${this.file.thumbnailUrl})`
|
||||
};
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -74,9 +73,12 @@ export default Vue.extend({
|
||||
pointer-events none
|
||||
|
||||
> .container
|
||||
display grid
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
padding 16px
|
||||
grid-template-columns 64px 1fr
|
||||
grid-column-gap 10px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
@@ -84,18 +86,13 @@ export default Vue.extend({
|
||||
clear both
|
||||
|
||||
> .thumbnail
|
||||
display block
|
||||
float left
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
color var(--driveFileIcon)
|
||||
|
||||
> .body
|
||||
display block
|
||||
float left
|
||||
width calc(100% - 74px)
|
||||
margin-left 10px
|
||||
word-break break-all
|
||||
|
||||
> .name
|
||||
display block
|
||||
@@ -104,8 +101,7 @@ export default Vue.extend({
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color var(--text)
|
||||
text-overflow ellipsis
|
||||
overflow-wrap break-word
|
||||
word-break break-word
|
||||
|
||||
> .ext
|
||||
opacity 0.5
|
||||
@@ -154,6 +150,6 @@ export default Vue.extend({
|
||||
background var(--primary)
|
||||
|
||||
&, *
|
||||
color #fff !important
|
||||
color var(--primaryForeground) !important
|
||||
|
||||
</style>
|
||||
|
@@ -26,6 +26,7 @@ button
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
user-select none
|
||||
|
||||
&[disabled]
|
||||
cursor default
|
||||
|
@@ -10,26 +10,26 @@ export type Theme = {
|
||||
props: { [key: string]: string };
|
||||
};
|
||||
|
||||
export const lightTheme: Theme = require('../theme/light.json5');
|
||||
export const darkTheme: Theme = require('../theme/dark.json5');
|
||||
export const pinkTheme: Theme = require('../theme/pink.json5');
|
||||
export const blackTheme: Theme = require('../theme/black.json5');
|
||||
export const halloweenTheme: Theme = require('../theme/halloween.json5');
|
||||
export const cafeTheme: Theme = require('../theme/cafe.json5');
|
||||
export const japaneseSushiSetTheme: Theme = require('../theme/japanese-sushi-set.json5');
|
||||
export const gruvboxDarkTheme: Theme = require('../theme/gruvbox-dark.json5');
|
||||
export const monokaiTheme: Theme = require('../theme/monokai.json5');
|
||||
export const colorfulTheme: Theme = require('../theme/colorful.json5');
|
||||
export const rainyTheme: Theme = require('../theme/rainy.json5');
|
||||
export const mauveTheme: Theme = require('../theme/mauve.json5');
|
||||
export const grayTheme: Theme = require('../theme/gray.json5');
|
||||
export const tweetDeckTheme: Theme = require('../theme/tweet-deck.json5');
|
||||
export const lightTheme: Theme = require('../themes/light.json5');
|
||||
export const darkTheme: Theme = require('../themes/dark.json5');
|
||||
export const lavenderTheme: Theme = require('../themes/lavender.json5');
|
||||
export const futureTheme: Theme = require('../themes/future.json5');
|
||||
export const halloweenTheme: Theme = require('../themes/halloween.json5');
|
||||
export const cafeTheme: Theme = require('../themes/cafe.json5');
|
||||
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
|
||||
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
|
||||
export const monokaiTheme: Theme = require('../themes/monokai.json5');
|
||||
export const colorfulTheme: Theme = require('../themes/colorful.json5');
|
||||
export const rainyTheme: Theme = require('../themes/rainy.json5');
|
||||
export const mauveTheme: Theme = require('../themes/mauve.json5');
|
||||
export const grayTheme: Theme = require('../themes/gray.json5');
|
||||
export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5');
|
||||
|
||||
export const builtinThemes = [
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
pinkTheme,
|
||||
blackTheme,
|
||||
lavenderTheme,
|
||||
futureTheme,
|
||||
halloweenTheme,
|
||||
cafeTheme,
|
||||
japaneseSushiSetTheme,
|
||||
|
@@ -153,6 +153,8 @@
|
||||
messagingRoomMessageBg: '$secondary',
|
||||
messagingRoomMessageFg: '#fff',
|
||||
|
||||
driveFileIcon: '$text',
|
||||
|
||||
formButtonBorder: 'rgba(255, 255, 255, 0.1)',
|
||||
formButtonHoverBg: ':alpha<0.2<$primary',
|
||||
formButtonHoverBorder: ':alpha<0.5<$primary',
|
@@ -8,23 +8,23 @@
|
||||
base: 'dark',
|
||||
|
||||
vars: {
|
||||
c0: '#0c0c0c',
|
||||
c0: '#0e0e0e',
|
||||
c1: 'rgb(255, 105, 78)',
|
||||
c2: 'rgb(99, 197, 210)',
|
||||
c4: 'rgb(253, 254, 214)',
|
||||
c3: 'rgb(204, 254, 253)',
|
||||
primary: '$c1',
|
||||
secondary: '#131313',
|
||||
secondary: '#191919',
|
||||
text: '$c3',
|
||||
},
|
||||
|
||||
props: {
|
||||
bg: '$c0',
|
||||
noteText: '$c4',
|
||||
noteHeaderAcct: '$c4',
|
||||
noteHeaderInfo: '$c4',
|
||||
noteHeaderAcct: ':alpha<0.65<$c4',
|
||||
noteHeaderInfo: ':alpha<0.5<$c4',
|
||||
subNoteText: ':alpha<0.7<$c4',
|
||||
renoteGradient: 'rgba(0, 0, 0, 0)',
|
||||
renoteGradient: '$secondary',
|
||||
renoteText: '$c2',
|
||||
quoteBorder: '$c2',
|
||||
mfmHashtag: '$c1',
|
@@ -153,6 +153,8 @@
|
||||
messagingRoomMessageBg: '#eee',
|
||||
messagingRoomMessageFg: '#333',
|
||||
|
||||
driveFileIcon: '$text',
|
||||
|
||||
formButtonBorder: 'rgba(0, 0, 0, 0.1)',
|
||||
formButtonHoverBg: ':alpha<0.12<$primary',
|
||||
formButtonHoverBorder: ':alpha<0.3<$primary',
|
@@ -29,6 +29,8 @@ export default function load() {
|
||||
|
||||
config.url = normalizeUrl(config.url);
|
||||
|
||||
config.port = config.port || parseInt(process.env.PORT, 10);
|
||||
|
||||
mixin.host = url.host;
|
||||
mixin.hostname = url.hostname;
|
||||
mixin.scheme = url.protocol.replace(/:$/, '');
|
||||
|
6
src/misc/content-disposition.ts
Normal file
6
src/misc/content-disposition.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
const cd = require('content-disposition');
|
||||
|
||||
export function contentDisposition(type: 'inline' | 'attachment', filename: string): string {
|
||||
const fallback = filename.replace(/[^\w.-]/g, '_');
|
||||
return cd(filename, { type, fallback });
|
||||
}
|
10
src/misc/create-temp.ts
Normal file
10
src/misc/create-temp.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
export function createTemp(): Promise<[string, any]> {
|
||||
return new Promise<[string, any]>((res, rej) => {
|
||||
tmp.file((e, path, fd, cleanup) => {
|
||||
if (e) return rej(e);
|
||||
res([path, cleanup]);
|
||||
});
|
||||
});
|
||||
}
|
31
src/misc/detect-mine.ts
Normal file
31
src/misc/detect-mine.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as fs from 'fs';
|
||||
import fileType from 'file-type';
|
||||
import checkSvg from '../misc/check-svg';
|
||||
|
||||
export async function detectMine(path: string) {
|
||||
return new Promise<[string, string]>((res, rej) => {
|
||||
const readable = fs.createReadStream(path);
|
||||
readable
|
||||
.on('error', rej)
|
||||
.once('data', (buffer: Buffer) => {
|
||||
readable.destroy();
|
||||
const type = fileType(buffer);
|
||||
if (type) {
|
||||
if (type.mime == 'application/xml' && checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
res([type.mime, type.ext]);
|
||||
}
|
||||
} else if (checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
// 種類が同定できなかったら application/octet-stream にする
|
||||
res(['application/octet-stream', null]);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
// maybe 0 bytes
|
||||
res(['application/octet-stream', null]);
|
||||
});
|
||||
});
|
||||
}
|
15
src/misc/detect-url-mine.ts
Normal file
15
src/misc/detect-url-mine.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createTemp } from './create-temp';
|
||||
import { downloadUrl } from './donwload-url';
|
||||
import { detectMine } from './detect-mine';
|
||||
|
||||
export async function detectUrlMine(url: string) {
|
||||
const [path, cleanup] = await createTemp();
|
||||
|
||||
try {
|
||||
await downloadUrl(url, path);
|
||||
const [type] = await detectMine(path);
|
||||
return type;
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
61
src/misc/donwload-url.ts
Normal file
61
src/misc/donwload-url.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as fs from 'fs';
|
||||
import * as URL from 'url';
|
||||
import * as request from 'request';
|
||||
import config from '../config';
|
||||
import chalk from 'chalk';
|
||||
import Logger from '../services/logger';
|
||||
|
||||
export async function downloadUrl(url: string, path: string) {
|
||||
const logger = new Logger('download-url');
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||
|
||||
const writable = fs.createWriteStream(path);
|
||||
|
||||
writable.on('finish', () => {
|
||||
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||
res();
|
||||
});
|
||||
|
||||
writable.on('error', error => {
|
||||
logger.error(`Download failed: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
rej(error);
|
||||
});
|
||||
|
||||
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||
|
||||
const req = request({
|
||||
url: requestUrl,
|
||||
proxy: config.proxy,
|
||||
timeout: 10 * 1000,
|
||||
headers: {
|
||||
'User-Agent': config.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writable);
|
||||
|
||||
req.on('response', response => {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(`Got ${response.statusCode} (${url})`);
|
||||
writable.close();
|
||||
rej(response.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
logger.error(`Failed to start download: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
writable.close();
|
||||
rej(error);
|
||||
});
|
||||
|
||||
logger.succ(`Downloaded to: ${path}`);
|
||||
});
|
||||
}
|
@@ -1,79 +1,25 @@
|
||||
import * as tmp from 'tmp';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import chalk from 'chalk';
|
||||
import * as request from 'request';
|
||||
import Logger from '../services/logger';
|
||||
import config from '../config';
|
||||
import { createTemp } from './create-temp';
|
||||
import { downloadUrl } from './donwload-url';
|
||||
|
||||
const logger = new Logger('download-text-file');
|
||||
|
||||
export async function downloadTextFile(url: string): Promise<string> {
|
||||
// Create temp file
|
||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||
tmp.file((e, path, fd, cleanup) => {
|
||||
if (e) return rej(e);
|
||||
res([path, cleanup]);
|
||||
});
|
||||
});
|
||||
const [path, cleanup] = await createTemp();
|
||||
|
||||
logger.info(`Temp file is ${path}`);
|
||||
|
||||
// write content at URL to temp file
|
||||
await new Promise((res, rej) => {
|
||||
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||
try {
|
||||
// write content at URL to temp file
|
||||
await downloadUrl(url, path);
|
||||
|
||||
const writable = fs.createWriteStream(path);
|
||||
const text = await util.promisify(fs.readFile)(path, 'utf8');
|
||||
|
||||
writable.on('finish', () => {
|
||||
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||
res();
|
||||
});
|
||||
|
||||
writable.on('error', error => {
|
||||
logger.error(`Download failed: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
rej(error);
|
||||
});
|
||||
|
||||
const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||
|
||||
const req = request({
|
||||
url: requestUrl,
|
||||
proxy: config.proxy,
|
||||
timeout: 10 * 1000,
|
||||
headers: {
|
||||
'User-Agent': config.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writable);
|
||||
|
||||
req.on('response', response => {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(`Got ${response.statusCode} (${url})`);
|
||||
writable.close();
|
||||
rej(response.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
logger.error(`Failed to start download: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
writable.close();
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
|
||||
logger.succ(`Downloaded to: ${path}`);
|
||||
|
||||
const text = await util.promisify(fs.readFile)(path, 'utf8');
|
||||
|
||||
cleanup();
|
||||
|
||||
return text;
|
||||
return text;
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ export async function toDbReaction(reaction: string, enableEmoji = true): Promis
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const custom = reaction.match(/:([\w+-]+):/);
|
||||
const custom = reaction.match(/^:([\w+-]+):$/);
|
||||
if (custom) {
|
||||
const emoji = await Emoji.findOne({
|
||||
host: null,
|
||||
|
4
src/misc/twemoji-base.ts
Normal file
4
src/misc/twemoji-base.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@11.3.0';
|
||||
// https://cdn.jsdelivr.net/npm/twemoji@11.3.0
|
||||
// https://cdnjs.cloudflare.com/ajax/libs/twemoji/11.3.0
|
||||
// https://twemoji.maxcdn.com
|
@@ -17,4 +17,5 @@ export type IEmoji = {
|
||||
updatedAt?: Date;
|
||||
/** AP object id */
|
||||
uri?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import $ from 'cafy';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import Reaction from './note-reaction';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
const NoteReaction = db.get<INoteReaction>('noteReactions');
|
||||
@@ -31,11 +29,11 @@ export const pack = (
|
||||
|
||||
// Populate the reaction if 'reaction' is ID
|
||||
if (isObjectId(reaction)) {
|
||||
_reaction = await Reaction.findOne({
|
||||
_reaction = await NoteReaction.findOne({
|
||||
_id: reaction
|
||||
});
|
||||
} else if (typeof reaction === 'string') {
|
||||
_reaction = await Reaction.findOne({
|
||||
_reaction = await NoteReaction.findOne({
|
||||
_id: new mongo.ObjectID(reaction)
|
||||
});
|
||||
} else {
|
||||
|
@@ -7,7 +7,7 @@ import { length } from 'stringz';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packApp } from './app';
|
||||
import PollVote from './poll-vote';
|
||||
import Reaction from './note-reaction';
|
||||
import NoteReaction from './note-reaction';
|
||||
import { packMany as packFileMany, IDriveFile } from './drive-file';
|
||||
import Following from './following';
|
||||
import Emoji from './emoji';
|
||||
@@ -259,7 +259,7 @@ export const pack = async (
|
||||
fields: { _id: false }
|
||||
});
|
||||
} else {
|
||||
_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts)]));
|
||||
_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts).map(x => x.replace(/:/g, ''))]));
|
||||
|
||||
_note.emojis = Emoji.find({
|
||||
name: { $in: _note.emojis },
|
||||
@@ -357,7 +357,7 @@ export const pack = async (
|
||||
if (meId) {
|
||||
// Fetch my reaction
|
||||
_note.myReaction = (async () => {
|
||||
const reaction = await Reaction
|
||||
const reaction = await NoteReaction
|
||||
.findOne({
|
||||
userId: meId,
|
||||
noteId: id,
|
||||
|
@@ -5,3 +5,7 @@ export function query(obj: {}): string {
|
||||
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>));
|
||||
}
|
||||
|
||||
export function appendQuery(url: string, query: string): string {
|
||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||
}
|
||||
|
@@ -28,26 +28,41 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
|
||||
|
||||
const csv = await downloadTextFile(url);
|
||||
|
||||
let linenum = 0;
|
||||
|
||||
for (const line of csv.trim().split('\n')) {
|
||||
const { username, host } = parseAcct(line.trim());
|
||||
linenum++;
|
||||
|
||||
let target = isSelfHost(host) ? await User.findOne({
|
||||
host: null,
|
||||
usernameLower: username.toLowerCase()
|
||||
}) : await User.findOne({
|
||||
host: toDbHost(host),
|
||||
usernameLower: username.toLowerCase()
|
||||
});
|
||||
try {
|
||||
const { username, host } = parseAcct(line.trim());
|
||||
|
||||
if (host == null && target == null) continue;
|
||||
let target = isSelfHost(host) ? await User.findOne({
|
||||
host: null,
|
||||
usernameLower: username.toLowerCase()
|
||||
}) : await User.findOne({
|
||||
host: toDbHost(host),
|
||||
usernameLower: username.toLowerCase()
|
||||
});
|
||||
|
||||
if (target == null) {
|
||||
target = await resolveUser(username, host);
|
||||
if (host == null && target == null) continue;
|
||||
|
||||
if (target == null) {
|
||||
target = await resolveUser(username, host);
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw `cannot resolve user: @${username}@${host}`;
|
||||
}
|
||||
|
||||
// skip myself
|
||||
if (target._id.equals(job.data.user._id)) continue;
|
||||
|
||||
logger.info(`Follow[${linenum}] ${target._id} ...`);
|
||||
|
||||
follow(user, target);
|
||||
} catch (e) {
|
||||
logger.warn(`Error in line:${linenum} ${e}`);
|
||||
}
|
||||
|
||||
logger.info(`Follow ${target._id} ...`);
|
||||
|
||||
follow(user, target);
|
||||
}
|
||||
|
||||
logger.succ('Imported');
|
||||
|
@@ -8,7 +8,7 @@ export default (emoji: IEmoji) => ({
|
||||
updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString,
|
||||
icon: {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png', //Mei-TODO
|
||||
mediaType: emoji.type || 'image/png',
|
||||
url: emoji.url
|
||||
}
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import Emoji from '../../../../../models/emoji';
|
||||
import define from '../../../define';
|
||||
import { detectUrlMine } from '../../../../../misc/detect-url-mine';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -29,12 +30,15 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const type = await detectUrlMine(ps.url);
|
||||
|
||||
const emoji = await Emoji.insert({
|
||||
updatedAt: new Date(),
|
||||
name: ps.name,
|
||||
host: null,
|
||||
aliases: ps.aliases,
|
||||
url: ps.url
|
||||
url: ps.url,
|
||||
type,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@@ -2,6 +2,7 @@ import $ from 'cafy';
|
||||
import Emoji from '../../../../../models/emoji';
|
||||
import define from '../../../define';
|
||||
import ID from '../../../../../misc/cafy-id';
|
||||
import { detectUrlMine } from '../../../../../misc/detect-url-mine';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -39,12 +40,15 @@ export default define(meta, async (ps) => {
|
||||
|
||||
if (emoji == null) throw new Error('emoji not found');
|
||||
|
||||
const type = await detectUrlMine(ps.url);
|
||||
|
||||
await Emoji.update({ _id: emoji._id }, {
|
||||
$set: {
|
||||
updatedAt: new Date(),
|
||||
name: ps.name,
|
||||
aliases: ps.aliases,
|
||||
url: ps.url
|
||||
url: ps.url,
|
||||
type,
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -83,7 +83,9 @@ export default define(meta, async (ps, user) => {
|
||||
if (ps.name) folder.name = ps.name;
|
||||
|
||||
if (ps.parentId !== undefined) {
|
||||
if (ps.parentId === null) {
|
||||
if (ps.parentId.equals(folder._id)) {
|
||||
throw new ApiError(meta.errors.recursiveNesting);
|
||||
} else if (ps.parentId === null) {
|
||||
folder.parentId = null;
|
||||
} else {
|
||||
// Get parent folder
|
||||
|
@@ -129,6 +129,8 @@ export default define(meta, async (ps, me) => {
|
||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
||||
enableGithubIntegration: instance.enableGithubIntegration,
|
||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
||||
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
};
|
||||
|
||||
if (ps.detail) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import Reaction, { pack } from '../../../../models/note-reaction';
|
||||
import NoteReaction, { pack } from '../../../../models/note-reaction';
|
||||
import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
@@ -87,7 +87,7 @@ export default define(meta, async (ps, user) => {
|
||||
};
|
||||
}
|
||||
|
||||
const reactions = await Reaction.find(query, {
|
||||
const reactions = await NoteReaction.find(query, {
|
||||
limit: ps.limit,
|
||||
skip: ps.offset,
|
||||
sort: sort
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import * as Koa from 'koa';
|
||||
import * as send from 'koa-send';
|
||||
import * as mongodb from 'mongodb';
|
||||
import * as rename from 'rename';
|
||||
import DriveFile, { getDriveFileBucket } from '../../models/drive-file';
|
||||
import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||
import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
|
||||
import { serverLogger } from '..';
|
||||
import { contentDisposition } from '../../misc/content-disposition';
|
||||
|
||||
const assets = `${__dirname}/../../server/file/assets/`;
|
||||
|
||||
@@ -62,10 +64,12 @@ export default async function(ctx: Koa.BaseContext) {
|
||||
|
||||
if (thumb != null) {
|
||||
ctx.set('Content-Type', 'image/jpeg');
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}`));
|
||||
const bucket = await getDriveFileThumbnailBucket();
|
||||
ctx.body = bucket.openDownloadStream(thumb._id);
|
||||
} else {
|
||||
if (file.contentType.startsWith('image/')) {
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`));
|
||||
await sendRaw();
|
||||
} else {
|
||||
ctx.status = 404;
|
||||
@@ -79,15 +83,17 @@ export default async function(ctx: Koa.BaseContext) {
|
||||
|
||||
if (web != null) {
|
||||
ctx.set('Content-Type', file.contentType);
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}`));
|
||||
|
||||
const bucket = await getDriveFileWebpublicBucket();
|
||||
ctx.body = bucket.openDownloadStream(web._id);
|
||||
} else {
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`));
|
||||
await sendRaw();
|
||||
}
|
||||
} else {
|
||||
if ('download' in ctx.query) {
|
||||
ctx.set('Content-Disposition', 'attachment');
|
||||
ctx.set('Content-Disposition', contentDisposition('attachment', `${file.filename}`));
|
||||
}
|
||||
|
||||
await sendRaw();
|
||||
|
@@ -1,27 +1,19 @@
|
||||
import * as fs from 'fs';
|
||||
import * as URL from 'url';
|
||||
import * as tmp from 'tmp';
|
||||
import * as Koa from 'koa';
|
||||
import * as request from 'request';
|
||||
import fileType from 'file-type';
|
||||
import { serverLogger } from '..';
|
||||
import config from '../../config';
|
||||
import { IImage, ConvertToPng, ConvertToJpeg } from '../../services/drive/image-processor';
|
||||
import checkSvg from '../../misc/check-svg';
|
||||
import { createTemp } from '../../misc/create-temp';
|
||||
import { downloadUrl } from '../../misc/donwload-url';
|
||||
import { detectMine } from '../../misc/detect-mine';
|
||||
|
||||
export async function proxyMedia(ctx: Koa.BaseContext) {
|
||||
const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
|
||||
|
||||
// Create temp file
|
||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||
tmp.file((e, path, fd, cleanup) => {
|
||||
if (e) return rej(e);
|
||||
res([path, cleanup]);
|
||||
});
|
||||
});
|
||||
const [path, cleanup] = await createTemp();
|
||||
|
||||
try {
|
||||
await fetch(url, path);
|
||||
await downloadUrl(url, path);
|
||||
|
||||
const [type, ext] = await detectMine(path);
|
||||
|
||||
@@ -54,70 +46,3 @@ export async function proxyMedia(ctx: Koa.BaseContext) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetch(url: string, path: string) {
|
||||
await new Promise((res, rej) => {
|
||||
const writable = fs.createWriteStream(path);
|
||||
|
||||
writable.on('finish', () => {
|
||||
res();
|
||||
});
|
||||
|
||||
writable.on('error', error => {
|
||||
rej(error);
|
||||
});
|
||||
|
||||
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||
|
||||
const req = request({
|
||||
url: requestUrl,
|
||||
proxy: config.proxy,
|
||||
timeout: 10 * 1000,
|
||||
headers: {
|
||||
'User-Agent': config.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writable);
|
||||
|
||||
req.on('response', response => {
|
||||
if (response.statusCode !== 200) {
|
||||
writable.close();
|
||||
rej(response.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
writable.close();
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function detectMine(path: string) {
|
||||
return new Promise<[string, string]>((res, rej) => {
|
||||
const readable = fs.createReadStream(path);
|
||||
readable
|
||||
.on('error', rej)
|
||||
.once('data', (buffer: Buffer) => {
|
||||
readable.destroy();
|
||||
const type = fileType(buffer);
|
||||
if (type) {
|
||||
if (type.mime == 'application/xml' && checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
res([type.mime, type.ext]);
|
||||
}
|
||||
} else if (checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
// 種類が同定できなかったら application/octet-stream にする
|
||||
res(['application/octet-stream', null]);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
// maybe 0 bytes
|
||||
res(['application/octet-stream', null]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -73,11 +73,7 @@ router.get(/^\/sw\.(.+?)\.js$/, async ctx => {
|
||||
});
|
||||
|
||||
// Manifest
|
||||
router.get('/manifest.json', async ctx => {
|
||||
await send(ctx as any, '/assets/manifest.json', {
|
||||
root: client
|
||||
});
|
||||
});
|
||||
router.get('/manifest.json', require('./manifest'));
|
||||
|
||||
router.get('/robots.txt', async ctx => {
|
||||
await send(ctx as any, '/assets/robots.txt', {
|
||||
|
16
src/server/web/manifest.ts
Normal file
16
src/server/web/manifest.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as Koa from 'koa';
|
||||
import * as manifest from '../../client/assets/manifest.json';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import fetchMeta from '../../misc/fetch-meta';
|
||||
|
||||
module.exports = async (ctx: Koa.BaseContext) => {
|
||||
const json = deepcopy(manifest);
|
||||
|
||||
const instance = await fetchMeta();
|
||||
|
||||
json.short_name = instance.name || 'Misskey';
|
||||
json.name = instance.name || 'Misskey';
|
||||
|
||||
ctx.set('Cache-Control', 'max-age=300');
|
||||
ctx.body = json;
|
||||
};
|
@@ -19,6 +19,10 @@ block og
|
||||
meta(property='og:image' content= user.avatarUrl)
|
||||
|
||||
block meta
|
||||
meta(name='misskey:user-username' content=user.username)
|
||||
meta(name='misskey:user-id' content=user.id)
|
||||
meta(name='misskey:note-id' content=note.id)
|
||||
|
||||
meta(name='twitter:card' content='summary')
|
||||
|
||||
if user.twitter
|
||||
|
@@ -19,6 +19,9 @@ block og
|
||||
meta(property='og:image' content= img)
|
||||
|
||||
block meta
|
||||
meta(name='misskey:user-username' content=user.username)
|
||||
meta(name='misskey:user-id' content=user.id)
|
||||
|
||||
meta(name='twitter:card' content='summary')
|
||||
|
||||
if user.twitter
|
||||
|
@@ -6,7 +6,6 @@ import * as crypto from 'crypto';
|
||||
import * as Minio from 'minio';
|
||||
import * as uuid from 'uuid';
|
||||
import * as sharp from 'sharp';
|
||||
import fileType from 'file-type';
|
||||
|
||||
import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../models/drive-file';
|
||||
import DriveFolder from '../../models/drive-folder';
|
||||
@@ -25,7 +24,8 @@ import { GenerateVideoThumbnail } from './generate-video-thumbnail';
|
||||
import { driveLogger } from './logger';
|
||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
||||
import Instance from '../../models/instance';
|
||||
import checkSvg from '../../misc/check-svg';
|
||||
import { contentDisposition } from '../../misc/content-disposition';
|
||||
import { detectMine } from '../../misc/detect-mine';
|
||||
|
||||
const logger = driveLogger.createSubLogger('register', 'yellow');
|
||||
|
||||
@@ -69,7 +69,7 @@ async function save(path: string, name: string, type: string, hash: string, size
|
||||
//#region Uploads
|
||||
logger.info(`uploading original: ${key}`);
|
||||
const uploads = [
|
||||
upload(key, fs.createReadStream(path), type)
|
||||
upload(key, fs.createReadStream(path), type, name)
|
||||
];
|
||||
|
||||
if (alts.webpublic) {
|
||||
@@ -77,7 +77,7 @@ async function save(path: string, name: string, type: string, hash: string, size
|
||||
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
||||
|
||||
logger.info(`uploading webpublic: ${webpublicKey}`);
|
||||
uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type));
|
||||
uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name));
|
||||
}
|
||||
|
||||
if (alts.thumbnail) {
|
||||
@@ -198,13 +198,17 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
|
||||
/**
|
||||
* Upload to ObjectStorage
|
||||
*/
|
||||
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string) {
|
||||
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
|
||||
const minio = new Minio.Client(config.drive.config);
|
||||
|
||||
await minio.putObject(config.drive.bucket, key, stream, null, {
|
||||
const metadata = {
|
||||
'Content-Type': type,
|
||||
'Cache-Control': 'max-age=31536000, immutable'
|
||||
});
|
||||
} as Minio.ItemBucketMetadata;
|
||||
|
||||
if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename);
|
||||
|
||||
await minio.putObject(config.drive.bucket, key, stream, null, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,33 +305,6 @@ export default async function(
|
||||
});
|
||||
});
|
||||
|
||||
// Detect content type
|
||||
const detectMime = new Promise<[string, string]>((res, rej) => {
|
||||
const readable = fs.createReadStream(path);
|
||||
readable
|
||||
.on('error', rej)
|
||||
.once('data', (buffer: Buffer) => {
|
||||
readable.destroy();
|
||||
const type = fileType(buffer);
|
||||
if (type) {
|
||||
if (type.mime == 'application/xml' && checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
res([type.mime, type.ext]);
|
||||
}
|
||||
} else if (checkSvg(path)) {
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
// 種類が同定できなかったら application/octet-stream にする
|
||||
res(['application/octet-stream', null]);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
// maybe 0 bytes
|
||||
res(['application/octet-stream', null]);
|
||||
});
|
||||
});
|
||||
|
||||
// Get file size
|
||||
const getFileSize = new Promise<number>((res, rej) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
@@ -336,7 +313,7 @@ export default async function(
|
||||
});
|
||||
});
|
||||
|
||||
const [hash, [mime, ext], size] = await Promise.all([calcHash, detectMime, getFileSize]);
|
||||
const [hash, [mime, ext], size] = await Promise.all([calcHash, detectMine(path), getFileSize]);
|
||||
|
||||
logger.info(`hash: ${hash}, mime: ${mime}, ext: ${ext}, size: ${size}`);
|
||||
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as fs from 'fs';
|
||||
import * as URL from 'url';
|
||||
import * as tmp from 'tmp';
|
||||
import * as request from 'request';
|
||||
|
||||
import { IDriveFile, validateFileName } from '../../models/drive-file';
|
||||
import create from './add-file';
|
||||
import config from '../../config';
|
||||
import { IUser } from '../../models/user';
|
||||
import * as mongodb from 'mongodb';
|
||||
import { driveLogger } from './logger';
|
||||
import chalk from 'chalk';
|
||||
import { createTemp } from '../../misc/create-temp';
|
||||
import { downloadUrl } from '../../misc/donwload-url';
|
||||
|
||||
const logger = driveLogger.createSubLogger('downloader');
|
||||
|
||||
@@ -28,62 +25,10 @@ export default async (
|
||||
}
|
||||
|
||||
// Create temp file
|
||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||
tmp.file((e, path, fd, cleanup) => {
|
||||
if (e) return rej(e);
|
||||
res([path, cleanup]);
|
||||
});
|
||||
});
|
||||
const [path, cleanup] = await createTemp();
|
||||
|
||||
// write content at URL to temp file
|
||||
await new Promise((res, rej) => {
|
||||
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||
|
||||
const writable = fs.createWriteStream(path);
|
||||
|
||||
writable.on('finish', () => {
|
||||
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||
res();
|
||||
});
|
||||
|
||||
writable.on('error', error => {
|
||||
logger.error(`Download failed: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
rej(error);
|
||||
});
|
||||
|
||||
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||
|
||||
const req = request({
|
||||
url: requestUrl,
|
||||
proxy: config.proxy,
|
||||
timeout: 10 * 1000,
|
||||
headers: {
|
||||
'User-Agent': config.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writable);
|
||||
|
||||
req.on('response', response => {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(`Got ${response.statusCode} (${url})`);
|
||||
writable.close();
|
||||
rej(response.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
logger.error(`Failed to start download: ${chalk.cyan(url)}: ${error}`, {
|
||||
url: url,
|
||||
e: error
|
||||
});
|
||||
writable.close();
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
await downloadUrl(url, path);
|
||||
|
||||
let driveFile: IDriveFile;
|
||||
let error;
|
||||
|
@@ -568,7 +568,8 @@ async function publishToFollowers(note: INote, user: IUser, noteActivity: any) {
|
||||
});
|
||||
|
||||
const followers = await Following.find({
|
||||
followeeId: note.userId
|
||||
followeeId: note.userId,
|
||||
followerId: { $ne: note.userId } // バグでフォロワーに自分がいることがあるため
|
||||
});
|
||||
|
||||
const queue: string[] = [];
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { IUser, isLocalUser, isRemoteUser } from '../../../models/user';
|
||||
import Note, { INote } from '../../../models/note';
|
||||
import Reaction from '../../../models/note-reaction';
|
||||
import NoteReaction from '../../../models/note-reaction';
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import renderUndo from '../../../remote/activitypub/renderer/undo';
|
||||
@@ -10,7 +10,7 @@ import { IdentifiableError } from '../../../misc/identifiable-error';
|
||||
|
||||
export default async (user: IUser, note: INote) => {
|
||||
// if already unreacted
|
||||
const exist = await Reaction.findOne({
|
||||
const exist = await NoteReaction.findOne({
|
||||
noteId: note._id,
|
||||
userId: user._id,
|
||||
deletedAt: { $exists: false }
|
||||
@@ -21,7 +21,7 @@ export default async (user: IUser, note: INote) => {
|
||||
}
|
||||
|
||||
// Delete reaction
|
||||
await Reaction.remove({
|
||||
await NoteReaction.remove({
|
||||
_id: exist._id
|
||||
});
|
||||
|
||||
|
14
test/api.ts
14
test/api.ts
@@ -1141,6 +1141,20 @@ describe('API', () => {
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('フォルダが循環するような構造にできない(自身)', async(async () => {
|
||||
const arisugawa = await signup({ username: 'arisugawa' });
|
||||
const folderA = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, arisugawa)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folderA.id,
|
||||
parentId: folderA.id
|
||||
}, arisugawa);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('存在しない親フォルダを設定できない', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
* > mocha test/reaction-lib.ts --require ts-node/register -g 'test name'
|
||||
*/
|
||||
|
||||
/*
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { toDbReaction } from '../src/misc/reaction-lib';
|
||||
@@ -89,3 +90,4 @@ describe('toDbReaction', async () => {
|
||||
assert.strictEqual(await toDbReaction('unknown'), 'like');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
Reference in New Issue
Block a user