Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5b0dfa6130 | ||
![]() |
a664173b5b | ||
![]() |
93a889df82 | ||
![]() |
0c1d3e186b | ||
![]() |
e1cf090440 | ||
![]() |
ff81648134 | ||
![]() |
1eed6911f0 | ||
![]() |
5faf9dc2c9 | ||
![]() |
59a32e9352 | ||
![]() |
bf654c6f42 | ||
![]() |
34c82776fc | ||
![]() |
02bde5150e | ||
![]() |
245b08b624 | ||
![]() |
1e58b3daff | ||
![]() |
4f2b4366f2 | ||
![]() |
7a94117d90 | ||
![]() |
121c7542a0 | ||
![]() |
e0433c6b89 | ||
![]() |
c9bf99fa66 | ||
![]() |
795b56f8ab | ||
![]() |
d2edd67cff | ||
![]() |
1ca6014284 | ||
![]() |
7ff8fb027d | ||
![]() |
d672bb4643 | ||
![]() |
4c6c06c80a | ||
![]() |
97b6af62fe | ||
![]() |
f180f02dd1 | ||
![]() |
3dbd48f744 | ||
![]() |
3c99b2fd12 | ||
![]() |
3d2cfd10bc | ||
![]() |
e563798afb | ||
![]() |
f679926b4f | ||
![]() |
ff20045e1d | ||
![]() |
c81ff10d5b | ||
![]() |
a103ae6755 | ||
![]() |
5c44c75c23 | ||
![]() |
3a973aee69 | ||
![]() |
16d59e9988 | ||
![]() |
93ce25db91 | ||
![]() |
c97a523ada | ||
![]() |
488fe2e7bd | ||
![]() |
8da278a279 | ||
![]() |
da69650505 | ||
![]() |
a85f6edd8a | ||
![]() |
d17c6adba4 | ||
![]() |
2671c6b4f2 | ||
![]() |
a81d5786f3 | ||
![]() |
75e2b075e1 | ||
![]() |
533884dcaa | ||
![]() |
7298b41a16 | ||
![]() |
39561e858a | ||
![]() |
96b88ee369 | ||
![]() |
d94e2025be | ||
![]() |
6ba5968861 | ||
![]() |
cea2d621f2 | ||
![]() |
84be4aa91e |
@@ -120,8 +120,8 @@ autoAdmin: true
|
||||
#clusterLimit: 1
|
||||
|
||||
# Job concurrency per worker
|
||||
# deliverJobConcurrency: 128;
|
||||
# inboxJobConcurrency: 16;
|
||||
# deliverJobConcurrency: 128
|
||||
# inboxJobConcurrency: 16
|
||||
|
||||
# IP address family used for outgoing request (ipv4, ipv6 or dual)
|
||||
#outgoingAddressFamily: ipv4
|
||||
|
@@ -1 +1 @@
|
||||
v12.11.0
|
||||
v12.11.1
|
||||
|
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,6 +1,48 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
11.35.0 (2019/11/01)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* 一対一でのトークを連合するように
|
||||
* カスタム絵文字をカテゴリ分け出来るように
|
||||
* カスタム絵文字をカテゴリ, 名前でソートするように
|
||||
* 絵文字ピッカーに最近使った絵文字を表示するように
|
||||
* 絵文字ピッカーで最後に使ったページを覚えておくように
|
||||
* 絵文字ピッカーで正方形じゃない絵文字のアスペクト比を保持するように
|
||||
* EscやBackspaceでメディアビューワが閉じれるように
|
||||
|
||||
### 🐛Fixes
|
||||
* AP: リモートアカウントを解決するとき、そのActorにendpointsが無いとTypeErrorになる問題を修正
|
||||
* AP: 新規投稿のMFMをHTMLに変換する際、リモートユーザーへのメンションのリンク先を(できれば)urlに
|
||||
* AP: menionのclassにu-urlを付けるように
|
||||
* AP: Noteの添付ファイルを提示する時に添付順を保持するように
|
||||
* リモートユーザーの更新に失敗することがある問題を修正
|
||||
* ミュートしているユーザーのプロフィールを開くと投稿が表示されない問題を修正
|
||||
* チャートで値が減ったときの記録が正しく行われていなかった問題を修正
|
||||
* カスタム絵文字を登録できない場合がある問題を修正
|
||||
* カスタム絵文字を重複登録できないように
|
||||
* みつけるの微修正
|
||||
* プロフィールで他のユーザーにうつってもfieldsが更新されない問題を修正
|
||||
* 絵文字ピッカーでアニメーション停止オプションが効かないのを修正
|
||||
|
||||
11.34.0 (2019/10/14)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* みつけるに連合分を表示するように
|
||||
* リアクションピッカーをカスタマイズできるように
|
||||
* inputで不正な値を入力したときにメッセージを表示するように
|
||||
* ドライブファイルダウンロードURLにdownloadを付けないように
|
||||
* web-pushでProxyを使用するように
|
||||
|
||||
### 🐛Fixes
|
||||
* 投稿本文が合字を含んで8192文字の場合に制限をパスしても投稿できない問題を修正
|
||||
* meta.maxNoteTextLengthをnote.textの最大サイズより大きく設定できる問題を修正
|
||||
* ページ移動等してもメニュー等が閉じずに残ってしまう問題を修正
|
||||
* ページ移動してもメディアビューワーが残ってしまう問題を修正
|
||||
* APに関する問題の修正
|
||||
* オフライン時に大室櫻子が出てこない問題を修正
|
||||
|
||||
11.33.0 (2019/09/27)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM node:12.11.0-alpine AS base
|
||||
FROM node:12.11.1-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
14
README.md
14
README.md
@@ -13,7 +13,7 @@
|
||||
<a href="https://misskey.io">Misskey</a> is a decentralized microblogging platform born on Earth.
|
||||
Since it exists within the Fediverse (a universe where various social media platforms are organized),
|
||||
it is mutually linked with other social media platforms.
|
||||
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://joinmisskey.github.io/">Find an instance!</a>
|
||||
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://join.misskey.page/">Find an instance!</a>
|
||||
</p>
|
||||
|
||||
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
|
||||
@@ -103,14 +103,12 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
----------------------------------------------------------------
|
||||
<!-- PATREON_START -->
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td>
|
||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
||||
@@ -123,6 +121,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
||||
</tr><tr>
|
||||
@@ -131,14 +130,15 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
||||
@@ -146,9 +146,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=5827393">motcha</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
||||
@@ -157,19 +157,17 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
|
||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Wed, 25 Sep 2019 21:27:06 UTC
|
||||
**Last updated:** Thu, 31 Oct 2019 06:09:07 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
@@ -12,7 +12,6 @@ const stylus = require('gulp-stylus');
|
||||
import * as uglifyComposer from 'gulp-uglify/composer';
|
||||
import * as rimraf from 'rimraf';
|
||||
import chalk from 'chalk';
|
||||
const imagemin = require('gulp-imagemin');
|
||||
import * as rename from 'gulp-rename';
|
||||
import * as mocha from 'gulp-mocha';
|
||||
import * as replace from 'gulp-replace';
|
||||
@@ -122,7 +121,6 @@ gulp.task('copy:client', () =>
|
||||
'./src/client/assets/**/*',
|
||||
'./src/client/app/*/assets/**/*'
|
||||
])
|
||||
.pipe(isProduction ? (imagemin as any)() : gutil.noop())
|
||||
.pipe(rename(path => {
|
||||
path.dirname = path.dirname!.replace('assets', '.');
|
||||
}))
|
||||
|
@@ -31,6 +31,7 @@ common:
|
||||
signup: "Registrovat"
|
||||
signout: "Odhlásit"
|
||||
reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?"
|
||||
fetching-as-ap-object: "Načítám data z Fediversu..."
|
||||
delete-confirm: "Opravdu chcete smazat tento příspěvek?"
|
||||
signin-required: "Přihlašte se, prosím"
|
||||
notification-type: "Typy oznámení"
|
||||
@@ -101,6 +102,9 @@ common:
|
||||
"read:reactions": "Prohlížet reakce"
|
||||
"write:reactions": "Narabět s reakcemi"
|
||||
"write:votes": "Hlasovat"
|
||||
"read:pages": "Zhlédnutí stránky"
|
||||
"write:pages": "Upravit stránky"
|
||||
"write:user-groups": "Upravit uživatelskou skupinu"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
|
||||
explore: "Najít uživatele"
|
||||
@@ -115,10 +119,12 @@ common:
|
||||
renote: "Renotovat"
|
||||
posting: "Posílání"
|
||||
attach-media-from-local: "Nahrát soubor z vašeho zařízení"
|
||||
attach-media-from-drive: "Přiložit soubory z vašeho Drivu"
|
||||
insert-a-kao: "v('ω')v"
|
||||
create-poll: "Vytvořit anketu"
|
||||
text-remain: "zbývá ještě {} znaků"
|
||||
recent-tags: "Nejnovější"
|
||||
local-only-message: "Publikovat zprávu pouze lokálně"
|
||||
click-to-tagging: "Klikni pro otágování"
|
||||
visibility: "Viditelnost"
|
||||
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
|
||||
@@ -187,6 +193,7 @@ common:
|
||||
other: "Ostatní"
|
||||
appearance: "Vzhled"
|
||||
behavior: "Chování"
|
||||
reactions: "Reakce"
|
||||
fetch-on-scroll: "Nekonečné načítaní posuvem"
|
||||
fetch-on-scroll-desc: "Pokud budete rolovat dolů po stránce, automaticky bude načten další obsah."
|
||||
note-visibility: "Viditelnost příspěvku"
|
||||
@@ -195,6 +202,8 @@ common:
|
||||
web-search-engine: "Webové vyhledávače"
|
||||
web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}"
|
||||
paste: "Vložit"
|
||||
pasted-file-name-desc: "Například: \"rrrr-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
|
||||
paste-dialog: "Upravit název vloženého souboru"
|
||||
keep-cw: "Zachovat varování o obsahu"
|
||||
keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek."
|
||||
i-like-sushi: "Mam radši sushi (než puding)"
|
||||
@@ -383,6 +392,7 @@ common/views/components/games/reversi/reversi.game.vue:
|
||||
surrender: "Vzdát se"
|
||||
surrendered: "Vzdaním se"
|
||||
looped-map: "Zacyklená mapa"
|
||||
can-put-everywhere: "Lze položit kamkoliv"
|
||||
common/views/components/games/reversi/reversi.index.vue:
|
||||
title: "Misskey Reversi"
|
||||
sub-title: "Hrajte Reversi s Vašimi kamarády!"
|
||||
@@ -432,6 +442,7 @@ common/views/components/connect-failed.troubleshooter.vue:
|
||||
no-server: "Nelze se připojit k serveru Misskey"
|
||||
success: "Úspěšně se podařilo spojit s Misskey serverem"
|
||||
flush: "Vyčistit mezipaměť"
|
||||
set-version: "Vyberte verzi"
|
||||
common/views/components/media-banner.vue:
|
||||
sensitive: "Choulostivý obsah"
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
@@ -485,6 +496,7 @@ common/views/components/messaging.vue:
|
||||
group: "Skupina"
|
||||
start-with-user: "Zahájit konverzaci s uživatelem"
|
||||
start-with-group: "Zahájit skupinovou konverzaci"
|
||||
select-group: "Vybrat skupinu"
|
||||
common/views/components/messaging-room.vue:
|
||||
new-message: "Máte novou zprávu"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
@@ -556,6 +568,7 @@ common/views/components/poll-editor.vue:
|
||||
deadline-date: "Termín ukončení"
|
||||
deadline-time: "Doba trvání"
|
||||
interval: "Trvání"
|
||||
unit: "Jednotka"
|
||||
second: "Sekunda"
|
||||
minute: "Minuta"
|
||||
hour: "Hodina"
|
||||
@@ -574,6 +587,12 @@ common/views/components/emoji-picker.vue:
|
||||
symbols: "Symboly"
|
||||
flags: "Vlajky"
|
||||
common/views/components/settings/app-type.vue:
|
||||
title: "Režim"
|
||||
intro: "Můžete vybrat zdali chcete použít stolní, nebo mobilní vzhled."
|
||||
choices:
|
||||
auto: "Vybrat vzhled automaticky"
|
||||
desktop: "Vždy použít stolní vzhled"
|
||||
mobile: "Vždy použít mobilní vzhled"
|
||||
info: "Pro aktivování změn musíte znovu načíst stránky."
|
||||
common/views/components/signin.vue:
|
||||
username: "Přezdívka"
|
||||
@@ -585,6 +604,7 @@ common/views/components/signin.vue:
|
||||
signin-with-github: "Přihlásit se pomocí účtu GitHub"
|
||||
signin-with-discord: "Přihlásit se pomocí účtu Discord"
|
||||
login-failed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo."
|
||||
enter-2fa-code: "Vložte Váš verifikační kód"
|
||||
common/views/components/signup.vue:
|
||||
invitation-code: "Kód pozvánky"
|
||||
invitation-info: "Pokud máte pozvánku, prosím kontaktujte <a href=\"{}\">administrátora</a>."
|
||||
@@ -606,6 +626,7 @@ common/views/components/signup.vue:
|
||||
password-matched: "OK"
|
||||
password-not-matched: "Neshodují se"
|
||||
recaptcha: "Potvrzení"
|
||||
agree-to: "Souhlasím s {0}."
|
||||
tos: "Podmínky užívání"
|
||||
create: "Vytvořit účet"
|
||||
some-error: "Pokus o vytvoření účtu selhal. Prosím zkuste to znovu."
|
||||
@@ -622,8 +643,10 @@ common/views/components/notification-settings.vue:
|
||||
mark-as-read-all-unread-notes: "Označit všechny příspěvky za přečtené"
|
||||
mark-as-read-all-talk-messages: "Označit všechny zprávy za přečtené"
|
||||
common/views/components/integration-settings.vue:
|
||||
title: "Integrace"
|
||||
connect: "Připojit"
|
||||
disconnect: "Odpojit"
|
||||
connected-to: "Jste připojen k tomuto GitHub účtu"
|
||||
common/views/components/github-setting.vue:
|
||||
description: "Jakmile spojíte Váš GitHub účet s Vaším Misskey účtem, uvidíte informace o Vašem GitHub účtu na Vašem profilu a budete se moci přihlásit skrze GitHub."
|
||||
connected-to: "Je připojen k tomuto GitHub účtu"
|
||||
@@ -643,6 +666,8 @@ common/views/components/uploader.vue:
|
||||
common/views/components/visibility-chooser.vue:
|
||||
public: "Veřejné"
|
||||
home: "Domů"
|
||||
home-desc: "Poslat pouze na domovskou časovou osu"
|
||||
specified: "Přímá"
|
||||
specified-desc: "Poslat pouze zmíněným uživatelům"
|
||||
local-public: "Veřejná (pouze místní)"
|
||||
local-public-desc: "Nepublikovat na vzdálených serverech"
|
||||
@@ -727,12 +752,14 @@ common/views/components/user-groups.vue:
|
||||
group-name: "Název skupiny"
|
||||
owned-groups: "Moje skupiny"
|
||||
invites: "Pozvat"
|
||||
accept-invite: "Přidat se"
|
||||
reject-invite: "Odmítnout"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Načítám"
|
||||
no-broadcasts: "Žádná nová oznámení"
|
||||
have-a-nice-day: "Přejeme Vám příjemný den!"
|
||||
next: "Další"
|
||||
prev: "Předchozí"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "Rok {}"
|
||||
month: "{},"
|
||||
@@ -741,6 +768,7 @@ common/views/widgets/calendar.vue:
|
||||
this-month: "Měsíc:"
|
||||
this-year: "Rok:"
|
||||
common/views/widgets/photo-stream.vue:
|
||||
title: "Foto stream"
|
||||
no-photos: "Žádné obrázky"
|
||||
common/views/widgets/posts-monitor.vue:
|
||||
title: "Grafy příspěvků"
|
||||
@@ -756,6 +784,8 @@ common/views/widgets/memo.vue:
|
||||
save: "Uložit"
|
||||
common/views/widgets/slideshow.vue:
|
||||
no-image: "V této složce nebyly nalezeny žádné fotky."
|
||||
common/views/widgets/tips.vue:
|
||||
tips-line23: "Ai-chan kawaii!"
|
||||
common/views/pages/not-found.vue:
|
||||
page-not-found: "Stránka nenalezena"
|
||||
common/views/pages/follow.vue:
|
||||
@@ -827,6 +857,7 @@ desktop/views/components/drive.folder.vue:
|
||||
input-new-folder-name: "Zadejte nové jméno"
|
||||
else-folders: "Ostatní"
|
||||
desktop/views/components/drive.vue:
|
||||
search: "Vyhledávání"
|
||||
empty-drive-description: "Klikněte pravým tlačítkem myši pro otevření menu, nebo sem přetáhněte soubor pro nahrání."
|
||||
empty-folder: "Tato složka je prázdná"
|
||||
unable-to-process: "Operace nemohla být dokončena."
|
||||
@@ -842,10 +873,13 @@ desktop/views/components/drive.vue:
|
||||
url-upload: "Nahrát z URL"
|
||||
desktop/views/components/media-video.vue:
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
desktop/views/components/followers.vue:
|
||||
empty: "Vypadá to že Vás nikdo nesleduje."
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Hotovo"
|
||||
add-widget: "Přidat widget:"
|
||||
add: "Přidat"
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Zrušit"
|
||||
@@ -853,6 +887,7 @@ desktop/views/input-dialog.vue:
|
||||
desktop/views/components/note-detail.vue:
|
||||
private: "Tento příspěvek je soukromý"
|
||||
deleted: "Tento příspěvek byl odstraněn"
|
||||
location: "Lokace"
|
||||
renote: "Renotovat"
|
||||
add-reaction: "Přidat reakci"
|
||||
undo-reaction: "Odebrat reakci"
|
||||
@@ -867,6 +902,8 @@ desktop/views/components/note.vue:
|
||||
desktop/views/components/notes.vue:
|
||||
error: "Načítání selhalo."
|
||||
retry: "Opakovat"
|
||||
desktop/views/components/notifications.vue:
|
||||
empty: "Žádné nové notifikace!"
|
||||
desktop/views/components/post-form.vue:
|
||||
posted: "Odesláno!"
|
||||
replied: "Odpověděno!"
|
||||
@@ -891,10 +928,29 @@ desktop/views/components/renote-form-window.vue:
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
detail: "Více…"
|
||||
url: "https://www.google.cz/landing/2step/"
|
||||
register: "Přidat zařízení"
|
||||
already-registered: "Toto zařízení je již připojené"
|
||||
unregister: "Odebrat"
|
||||
enter-password: "Prosím zadejte heslo"
|
||||
authenticator: "Nejprve musíte nainstalovat Google Authenticator na Vašem zařízení:"
|
||||
howtoinstall: "Jak nainstalovat"
|
||||
token: "Token"
|
||||
scan: "Poté naskenujte QR kód:"
|
||||
done: "Prosím vložte kód zobrazený na Vašem zařízení:"
|
||||
submit: "Uložit"
|
||||
success: "Nastavení uloženo!"
|
||||
failed: "Nepodařilo se spárovat. Prosím zkontrolujte správnost bezpečnostního kódu."
|
||||
totp-header: "Ověřovací aplikace"
|
||||
security-key-header: "Bezpečnostní klíč"
|
||||
last-used: "Naposledy použito:"
|
||||
activate-key: "Klikněte pro aktivaci bezpečnostního klíče"
|
||||
security-key-name: "Název klíče"
|
||||
key-unregistered: "Bezpečnostní klíč byl odstraněn"
|
||||
common/views/components/media-image.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "Klikněte pro zobrazení"
|
||||
common/views/components/api-settings.vue:
|
||||
caution: "Nepoužívejte tento kód v žádné jiné aplikace nebo ho sdílejte s ostatními (jinak můžete ohrozit svojí bezpečnost)."
|
||||
token: "Token:"
|
||||
enter-password: "Prosím zadejte heslo"
|
||||
console:
|
||||
@@ -976,20 +1032,48 @@ admin/views/index.vue:
|
||||
announcements: "Oznámení"
|
||||
queue: "Fronta úloh"
|
||||
logs: "Logy"
|
||||
db: "Databáze"
|
||||
back-to-misskey: "Zpět na Misskey"
|
||||
admin/views/db.vue:
|
||||
tables: "Tabulky"
|
||||
vacuum: "Vysavač"
|
||||
vacuum-info: "Uklidí databázi. Neohrozí data a sníží využití disku. Tohle se děje automaticky a opakovaně."
|
||||
vacuum-exclamation: "Vysavač může dočasně přetížit databázi a dočasně omezit akce uživatelů."
|
||||
admin/views/dashboard.vue:
|
||||
dashboard: "Kontrolní panel"
|
||||
accounts: "Účty"
|
||||
notes: "Poznámky"
|
||||
drive: "Disk"
|
||||
instances: "Instance"
|
||||
this-instance: "Tato instance"
|
||||
federated: "Z fedivesmíru"
|
||||
admin/views/queue.vue:
|
||||
title: "Ve frontě"
|
||||
remove-all-jobs: "Vyčistit frontu"
|
||||
jobs: "Úkoly"
|
||||
queue: "Ve frontě"
|
||||
domains:
|
||||
inbox: "Obdržené"
|
||||
db: "Databáze"
|
||||
states:
|
||||
active: "V procesu"
|
||||
delayed: "Naplánováno"
|
||||
waiting: "Ve frontě"
|
||||
result-is-truncated: "Zkrácený výsledek"
|
||||
admin/views/logs.vue:
|
||||
logs: "Logy"
|
||||
domain: "Doména"
|
||||
level: "Úroveň"
|
||||
levels:
|
||||
all: "Vše"
|
||||
info: "Informace"
|
||||
success: "Podařilo se"
|
||||
warning: "Varování"
|
||||
error: "Chyba"
|
||||
debug: "Debug"
|
||||
delete-all: "Smazat vše"
|
||||
admin/views/abuse.vue:
|
||||
target: "Cíl"
|
||||
details: "Popis"
|
||||
remove-report: "Odstranit"
|
||||
admin/views/instance.vue:
|
||||
@@ -997,17 +1081,33 @@ admin/views/instance.vue:
|
||||
instance-name: "Název instance"
|
||||
instance-description: "Popis instance"
|
||||
host: "Hostitel"
|
||||
icon-url: "URL ikonky"
|
||||
logo-url: "URL loga"
|
||||
banner-url: "URL pro baner"
|
||||
error-image-url: "URL pro chybový obrázek"
|
||||
languages: "Jazyk této instance"
|
||||
languages-desc: "Můžete nastavit více než jeden, oddělte mezerami."
|
||||
tos-url: "URL pro smluvní podmínky"
|
||||
repository-url: "URL adresa repositáře"
|
||||
feedback-url: "URL pro zpětnou vazbu"
|
||||
maintainer-config: "Informace o administrátorovi"
|
||||
maintainer-name: "Jméno administrátora"
|
||||
maintainer-email: "Kontakt na administrátora"
|
||||
advanced-config: "Další nastavení"
|
||||
object-storage-base-url: "URL"
|
||||
object-storage-prefix: "Předpona"
|
||||
object-storage-endpoint: "Endpoint"
|
||||
object-storage-region: "Region"
|
||||
object-storage-port: "Port"
|
||||
object-storage-access-key: "Přístupový klíč"
|
||||
object-storage-secret-key: "Tajný Klíč (Secret Key)"
|
||||
object-storage-use-ssl: "Použít SSL"
|
||||
object-storage-s3-info-here: "zde"
|
||||
mb: "V megabajtech"
|
||||
recaptcha-config: "nastavení služby reCAPTCHA"
|
||||
recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
|
||||
enable-recaptcha: "povolit reCAPTCHA"
|
||||
recaptcha-secret-key: "Tajný Klíč (Secret Key)"
|
||||
recaptcha-preview: "Náhled"
|
||||
twitter-integration-config: "Nastavení spojení s Twitterem"
|
||||
twitter-integration-info: "The callback URL is set on {url}."
|
||||
@@ -1206,6 +1306,9 @@ desktop/views/widgets/notifications.vue:
|
||||
title: "Oznámení"
|
||||
desktop/views/widgets/polls.vue:
|
||||
title: "Ankety"
|
||||
nothing: "Žádné nové notifikace!"
|
||||
desktop/views/widgets/trends.vue:
|
||||
nothing: "Žádné nové notifikace!"
|
||||
desktop/views/widgets/users.vue:
|
||||
title: "Doporučení uživatelé"
|
||||
mobile/views/components/drive.vue:
|
||||
@@ -1248,6 +1351,8 @@ mobile/views/components/note-sub.vue:
|
||||
admin: "admin"
|
||||
bot: "bot"
|
||||
cat: "kočka"
|
||||
mobile/views/components/notifications.vue:
|
||||
empty: "Žádné nové notifikace!"
|
||||
mobile/views/components/sub-note-content.vue:
|
||||
private: "Tento příspěvek je soukromý"
|
||||
deleted: "Tento příspěvek byl odstraněn"
|
||||
@@ -1291,6 +1396,7 @@ mobile/views/pages/note.vue:
|
||||
mobile/views/pages/games/reversi.vue:
|
||||
reversi: "Reversi"
|
||||
mobile/views/pages/search.vue:
|
||||
search: "Vyhledávání"
|
||||
not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
|
||||
mobile/views/pages/selectdrive.vue:
|
||||
select-file: "Vybrat soubory"
|
||||
|
@@ -166,6 +166,7 @@ common:
|
||||
other: "Andet"
|
||||
appearance: "Udseende"
|
||||
behavior: "Opførsel"
|
||||
reactions: "Reaktion"
|
||||
fetch-on-scroll: "Uendeligt scroll"
|
||||
fetch-on-scroll-desc: "Når du scroller ned ad siden, hentes der automatisk nyt indhold ind"
|
||||
note-visibility: "Post synlighed"
|
||||
|
@@ -91,7 +91,7 @@ common:
|
||||
"write:drive": "Work with the Drive"
|
||||
"read:favorites": "View Favorites"
|
||||
"write:favorites": "Work with Favorites"
|
||||
"read:following": "View Follower info"
|
||||
"read:following": "View your Follow info"
|
||||
"write:following": "Work with Follow info"
|
||||
"read:messaging": "View Messaging"
|
||||
"write:messaging": "Work with Messaging"
|
||||
@@ -103,6 +103,12 @@ common:
|
||||
"read:reactions": "View reactions"
|
||||
"write:reactions": "Work with reactions"
|
||||
"write:votes": "Vote"
|
||||
"read:pages": "View Pages"
|
||||
"write:pages": "Update Pages"
|
||||
"read:page-likes": "View Likes on Pages"
|
||||
"write:page-likes": "Update Likes on Pages"
|
||||
"read:user-groups": "View User groups"
|
||||
"write:user-groups": "Edit User groups"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
|
||||
explore: "Find users"
|
||||
@@ -194,6 +200,8 @@ common:
|
||||
other: "Other"
|
||||
appearance: "Appearance"
|
||||
behavior: "Behavior"
|
||||
reactions: "Reaction"
|
||||
reactions-description: "Customize Emojis of Reaction picker delimited by line breaks"
|
||||
fetch-on-scroll: "Endless loading on scroll"
|
||||
fetch-on-scroll-desc: "When you scroll down the page, it automatically fetches additional content."
|
||||
note-visibility: "Post visibility"
|
||||
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
|
||||
popular-users: "Popular users"
|
||||
recently-updated-users: "Recently active users"
|
||||
recently-registered-users: "Users who joined recently"
|
||||
recently-discovered-users: "Recently Discovered Users"
|
||||
popular-tags: "Popular Tags"
|
||||
federated: "From the fediverse"
|
||||
explore: "Explore {host}"
|
||||
explore-fediverse: "Explore Fediverse"
|
||||
users-info: "Currently, {users} users are registered here"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users} reacted with {reaction}"
|
||||
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "Send a reaction"
|
||||
input-reaction-placeholder: "or input Emoji"
|
||||
common/views/components/emoji-picker.vue:
|
||||
recent-emoji: "Recently used"
|
||||
custom-emoji: "Custom Emoji"
|
||||
no-category: "Uncategorized"
|
||||
people: "People"
|
||||
animals-and-nature: "Animals & Nature"
|
||||
food-and-drink: "Food & drink"
|
||||
@@ -1294,7 +1306,7 @@ admin/views/instance.vue:
|
||||
discord-integration-client-id: "Client ID"
|
||||
discord-integration-client-secret: "Client Secret"
|
||||
proxy-account-config: "Proxy account"
|
||||
proxy-account-info: "Proxy account can follow a remote user to deliver activities if no one in this instance follow him or her. When you add a remote user who is followed by nobody in this instance to your list, in order to get his or her data, proxy account follow him or her instead of your following."
|
||||
proxy-account-info: "A proxy account is an account that acts as a remote follower for users under certain conditions. For example, when a user adds a remote user to the list, the activity will not be delivered to the server if no one is following that user, so the proxy account will follow instead."
|
||||
proxy-account-username: "Proxy account user name"
|
||||
proxy-account-username-desc: "Specify the user name of the account that is used as a proxy."
|
||||
proxy-account-warn: "You must make an account having this username before this action."
|
||||
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
|
||||
title: "Add emoji"
|
||||
name: "Emoji name"
|
||||
name-desc: "You can use the characters a~z 0~9 _"
|
||||
category: "Categories"
|
||||
aliases: "Aliases"
|
||||
aliases-desc: "You can add more than one, separated by spaces."
|
||||
url: "Image URL"
|
||||
|
@@ -99,6 +99,7 @@ common:
|
||||
"read:reactions": "Lire les réactions"
|
||||
"write:reactions": "Gérer vos réactions"
|
||||
"write:votes": "Vote"
|
||||
"read:pages": "Afficher la page"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil."
|
||||
explore: "Trouver des utilisateur·rice·s"
|
||||
@@ -188,6 +189,7 @@ common:
|
||||
other: "Avancé"
|
||||
appearance: "Apparence"
|
||||
behavior: "Comportement"
|
||||
reactions: "Réaction"
|
||||
fetch-on-scroll: "Chargement automatique lors du défilement"
|
||||
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
|
||||
note-visibility: "Visibilité de la publication"
|
||||
|
@@ -212,6 +212,8 @@ common:
|
||||
other: "その他"
|
||||
appearance: "デザイン"
|
||||
behavior: "動作"
|
||||
reactions: "リアクション"
|
||||
reactions-description: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
|
||||
fetch-on-scroll: "スクロールで自動読み込み"
|
||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||
note-visibility: "投稿の公開範囲"
|
||||
@@ -411,9 +413,11 @@ common/views/pages/explore.vue:
|
||||
popular-users: "人気のユーザー"
|
||||
recently-updated-users: "最近投稿したユーザー"
|
||||
recently-registered-users: "新規ユーザー"
|
||||
recently-discovered-users: "最近発見されたユーザー"
|
||||
popular-tags: "人気のタグ"
|
||||
federated: "連合"
|
||||
explore: "{host}を探索"
|
||||
explore-fediverse: "Fediverseを探索"
|
||||
users-info: "現在{users}ユーザーが登録されています"
|
||||
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
@@ -669,7 +673,9 @@ common/views/components/reaction-picker.vue:
|
||||
input-reaction-placeholder: "または絵文字を入力"
|
||||
|
||||
common/views/components/emoji-picker.vue:
|
||||
recent-emoji: "最近使った絵文字"
|
||||
custom-emoji: "カスタム絵文字"
|
||||
no-category: "カテゴリなし"
|
||||
people: "人"
|
||||
animals-and-nature: "動物&自然"
|
||||
food-and-drink: "食べ物&飲み物"
|
||||
@@ -1587,6 +1593,7 @@ admin/views/emoji.vue:
|
||||
title: "絵文字の登録"
|
||||
name: "絵文字名"
|
||||
name-desc: "a~z 0~9 _ の文字が使えます。"
|
||||
category: "カテゴリ"
|
||||
aliases: "エイリアス"
|
||||
aliases-desc: "スペースで区切って複数設定できます。"
|
||||
url: "絵文字画像URL"
|
||||
|
@@ -126,6 +126,7 @@ common:
|
||||
blocking: "ブロック"
|
||||
password: "パスワード"
|
||||
other: "その他"
|
||||
reactions: "リアクション"
|
||||
timeline: "タイムライン"
|
||||
save: "保存"
|
||||
saved: "保存したで!"
|
||||
|
@@ -83,14 +83,14 @@ common:
|
||||
followers: "팔로워"
|
||||
favorites: "즐겨찾기"
|
||||
permissions:
|
||||
"read:account": "계정의 정보를 볼 수 있습니다."
|
||||
"write:account": "계정의 정보를 변경할 수 있습니다."
|
||||
"read:blocks": "차단 목록을 볼 수 있습니다."
|
||||
"write:blocks": "차단 목록을 조작할 수 있습니다."
|
||||
"read:drive": "드라이브의 파일들을 볼 수 있습니다."
|
||||
"write:drive": "드라이브의 파일들을 조작할 수 있습니다."
|
||||
"read:favorites": "즐겨찾기 목록을 볼 수 있습니다."
|
||||
"write:favorites": "즐겨찾기 목록을 조작할 수 있습니다."
|
||||
"read:account": "계정 정보 보기"
|
||||
"write:account": "계정 정보 변경"
|
||||
"read:blocks": "차단 보기"
|
||||
"write:blocks": "차단 수정"
|
||||
"read:drive": "드라이브 보기"
|
||||
"write:drive": "드라이브 수정"
|
||||
"read:favorites": "즐겨찾기 보기"
|
||||
"write:favorites": "즐겨찾기 수정"
|
||||
"read:following": "팔로우 정보 보기"
|
||||
"write:following": "팔로잉, 팔로우 수정"
|
||||
"read:messaging": "대화 보기"
|
||||
@@ -103,6 +103,12 @@ common:
|
||||
"read:reactions": "리액션 보기"
|
||||
"write:reactions": "리액션 수정"
|
||||
"write:votes": "투표하기"
|
||||
"read:pages": "페이지 보기"
|
||||
"write:pages": "페이지 변경"
|
||||
"read:page-likes": "페이지의 좋아요 보기"
|
||||
"write:page-likes": "페이지의 좋아요 변경"
|
||||
"read:user-groups": "유저 그룹 보기"
|
||||
"write:user-groups": "유저 그룹을 변경"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
|
||||
explore: "사용자 탐색"
|
||||
@@ -194,6 +200,8 @@ common:
|
||||
other: "기타"
|
||||
appearance: "디자인"
|
||||
behavior: "동작"
|
||||
reactions: "리액션"
|
||||
reactions-description: "리액션 선택창에 표시할 리액션을 줄바꿈으로 구분해 설정합니다."
|
||||
fetch-on-scroll: "스크롤하여 자동으로 불러오기"
|
||||
fetch-on-scroll-desc: "페이지를 아래로 스크롤하였을 때 자동으로 추가 콘텐츠를 불러옵니다."
|
||||
note-visibility: "게시물의 공개 범위"
|
||||
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
|
||||
popular-users: "인기 사용자"
|
||||
recently-updated-users: "최근 게시한 사용자"
|
||||
recently-registered-users: "신규 사용자"
|
||||
recently-discovered-users: "최근 발견된 유저"
|
||||
popular-tags: "인기 태그"
|
||||
federated: "연합"
|
||||
explore: "{host}을(를) 탐색"
|
||||
explore-fediverse: "연합 우주를 탐색"
|
||||
users-info: "현재 {users} 사용자가 등록되어 있습니다"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users}님이 {reaction} 리액션"
|
||||
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "리액션 선택"
|
||||
input-reaction-placeholder: "또는 이모지 입력"
|
||||
common/views/components/emoji-picker.vue:
|
||||
recent-emoji: "최근 사용한 이모지"
|
||||
custom-emoji: "커스텀 이모지"
|
||||
no-category: "카테고리 없음"
|
||||
people: "사람들"
|
||||
animals-and-nature: "동물 & 자연"
|
||||
food-and-drink: "음식 & 음료"
|
||||
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
|
||||
title: "이모지 등록"
|
||||
name: "이모지 이름"
|
||||
name-desc: "a~z 0~9 _ 의 문자를 사용할 수 있습니다."
|
||||
category: "카테고리"
|
||||
aliases: "별칭"
|
||||
aliases-desc: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||
url: "이모지 이미지 URL"
|
||||
|
@@ -3,16 +3,58 @@ meta:
|
||||
lang: "Nederlands"
|
||||
common:
|
||||
misskey: "Deel alles met anderen die ook Misskey gebruiken."
|
||||
about-title: "Een ster van het fediverse"
|
||||
about: "Bedankt voor het ontdekken van Misskey. Misskey is een <b>gedecentraliseerd microblogging platform</b> geboren op aarde. Omdat het bestaat binnen het Fediverse (een georganiseerd universum van verschillende sociale mediaplatformen), staat het verbonden met andere sociale medieplatformen. Neem een pauze van de stedelijke drukte, en duik in het nieuwe intenet?"
|
||||
intro:
|
||||
title: "Wat is Misskey?"
|
||||
about: "Misskey is een open source <b>gedecentraliseerd blogplatform</b>. Het heeft een gesofisticeerde, volledig aanpasbare gebruikersinterface, uitgebreide reactiemogelijkheden voor posts, gratis geïntegreerd bestandsoplagbeheer en andere geavanceerde mogelijkheden. Daarnaast staat Misskey verbonden aan een netwerksysteem genaam het \"Fediverse\", hiermee kunnen we communiceren met andere gebruikers op andere SNSs. Dit betekent dat wanneer je iets post het niet enkel verstuurd wordt naar andere Misskey gebruikers, maar ook naar gebruikers op Mastodon en Pleroma. Stel je voor dat een planeet een radiosignaal verzendt naar een andere planeet als manier van communiceren."
|
||||
features: "Kenmerken"
|
||||
rich-contents: "Bericht"
|
||||
rich-contents-desc: "Post jouw idee, hot topic, wat je ook maar wil delen. Maak jouw teksten aantrekkelijk met je favoriete foto's, verzend bestanden, zelfs video's, of maak een poll. Dit zijn enkele van de mogelijkheden die Misskey aanbiedt!"
|
||||
reaction: "Reactie"
|
||||
reaction-desc: "Dé makkelijkste manier om jouw gevoelens uit te drukken. Met Misskey kan je verschillende reacties toevoegen aan jouw posts. Andere SNSs hebben enkel maar een \"vind ik leuk\" reactie."
|
||||
ui: "Interface"
|
||||
ui-desc: "Niet één UI past nij iedereen. Daarom heeft Misskey een uitgebreide keuze om de UI naar jouw hand te zetten. Je kan jouw nieuwe thuis zo origineel maken als je zelf wil door jouw tijdslijn aan te passen door widgets te verplaatsen en hun look te veranderen. Zo maak je van Misskey jouw eigen stek."
|
||||
drive: "Drive"
|
||||
drive-desc: "Wil je een foto posten die je reeds het geüpload? Wens je georganiseerde map met zelfgekozen naam maken voor al jouw bestanden? De beste oplossing voor jou is Misskey Drive. Dit maakt het supermakkelijk om jouw bestanden online te delen."
|
||||
application-authorization: "Geauthoriseerde applicaties"
|
||||
close: "Sluiten"
|
||||
do-not-copy-paste: "Gelieve de code hier niet in te geven of te plakken. De account kan gecompromiseerd zijn."
|
||||
load-more: "Laad meer resultaten"
|
||||
enter-password: "Voer het wachtwoord in"
|
||||
2fa: "Tweestapsverificatie"
|
||||
customize-home: "Layout aanpassen"
|
||||
featured-notes: "Uitgelicht"
|
||||
dark-mode: "Donker thema"
|
||||
signin: "Aanmelden"
|
||||
signup: "Registreren"
|
||||
signout: "Afmelden"
|
||||
reload-to-apply-the-setting: "Herlaad de pagina om je aanpassingen te bekijken. Wil je de pagina nu herladen?"
|
||||
fetching-as-ap-object: "Verzenden naar Fediverse"
|
||||
unfollow-confirm: "Wil stoppen met {name} te volgen?"
|
||||
delete-confirm: "Ben je zeker dat je dit bericht wil verwijderen?"
|
||||
signin-required: "Gelieve in te loggen"
|
||||
notification-type: "Notificatietype"
|
||||
notification-types:
|
||||
all: "Alle"
|
||||
pollVote: "Stemmen"
|
||||
follow: "Volgend"
|
||||
receiveFollowRequest: "Volgverzoeken"
|
||||
reply: "Beantwoorden"
|
||||
quote: "Bron"
|
||||
mention: "Vermeldingen"
|
||||
reaction: "Reactie"
|
||||
got-it: "Ik snap het!"
|
||||
customization-tips:
|
||||
title: "Aanpassingstips"
|
||||
gotit: "Ik snap het!"
|
||||
notification:
|
||||
file-uploaded: "Je bestand is geüpload"
|
||||
message-from: "Bericht van {}:"
|
||||
reversi-invited: "Uitgenodigd voor spel"
|
||||
notified-by: "Bemerkt door: {}"
|
||||
reply-from: "Antwoord van: {}"
|
||||
quoted-by: "Geciteerd door: {}"
|
||||
time:
|
||||
unknown: "onbekend"
|
||||
future: "toekomstig"
|
||||
@@ -24,7 +66,13 @@ common:
|
||||
weeks_ago: "{}week/weken geleden"
|
||||
months_ago: "{}maand(en) geleden"
|
||||
years_ago: "{}jaar geleden"
|
||||
month-and-day: "{day} {month}"
|
||||
trash: "Prullenbak"
|
||||
drive: "Drive"
|
||||
pages: "Pagina's"
|
||||
messaging: "Gesprekken"
|
||||
home: "Startpagina"
|
||||
deck: "Deck"
|
||||
timeline: "Tijdlijn"
|
||||
followers: "Volgers"
|
||||
favorites: "Deze notitie toevoegen aan favorieten"
|
||||
@@ -59,7 +107,11 @@ common:
|
||||
profile: "Je profiel"
|
||||
notification: "Meldingen"
|
||||
password: "Wachtwoord"
|
||||
reactions: "Reactie"
|
||||
deck-column-align-center: "Centreren"
|
||||
deck-column-align-left: "Links"
|
||||
timeline: "Tijdlijn"
|
||||
navbar-position-left: "Links"
|
||||
search: "Zoeken"
|
||||
delete: "Verwijderen"
|
||||
loading: "Bezig met laden"
|
||||
@@ -423,11 +475,13 @@ admin/views/index.vue:
|
||||
users: "Gebruiker"
|
||||
admin/views/dashboard.vue:
|
||||
notes: "Bericht"
|
||||
drive: "Drive"
|
||||
admin/views/abuse.vue:
|
||||
remove-report: "Verwijderen"
|
||||
admin/views/charts.vue:
|
||||
notes: "Bericht"
|
||||
users: "Gebruiker"
|
||||
drive: "Drive"
|
||||
admin/views/drive.vue:
|
||||
origin:
|
||||
local: "Lokaal"
|
||||
@@ -563,6 +617,7 @@ mobile/views/pages/home.vue:
|
||||
global: "Algemeen"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Toevoegen"
|
||||
customization-tips: "Aanpassingstips"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
activity: "Activiteit"
|
||||
mobile/views/pages/note.vue:
|
||||
@@ -643,3 +698,4 @@ room:
|
||||
translate: "Verplaatsen"
|
||||
furnitures:
|
||||
moon: "Maan"
|
||||
bin: "Prullenbak"
|
||||
|
@@ -157,6 +157,7 @@ common:
|
||||
other: "Inne"
|
||||
appearance: "Wygląd"
|
||||
behavior: "Zachowanie"
|
||||
reactions: "Reakcja"
|
||||
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
|
||||
note-visibility: "Widoczność wpisów"
|
||||
remember-note-visibility: "Zapamiętaj widoczność wpisów"
|
||||
|
@@ -2,12 +2,12 @@
|
||||
meta:
|
||||
lang: "中文(简体)"
|
||||
common:
|
||||
misskey: "Fediverse中的一颗⭐"
|
||||
about-title: "Fediverse中的一颗⭐"
|
||||
about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于Fediverse(由各种SNS组成的宇宙)中,所以她与其他SNS相互连接。想要远离喧嚣的城市,不如深入这个新的互联网来探索一下吧。"
|
||||
misskey: "联邦宇宙中的一颗⭐"
|
||||
about-title: "联邦宇宙中的一颗⭐"
|
||||
about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于联邦宇宙(由各种SNS组成的宇宙)中,所以她与其他SNS相互连接。为什么不试试远离喧嚣的城市,潜入这片新的网络海洋之中呢?"
|
||||
intro:
|
||||
title: "什么是 Misskey 呢?"
|
||||
about: "Misskey是开源的<b>分散式微博SNS</b>。复杂的完全可定制的Ui,各种各样的帖子反应,提供集成管理系统和其他先进功能的免费文件存储。此外,称为“Fediverse”的网络系统使我们能够与其他SNS的用户进行通信。比如,如果你张贴一些东西,那么你的帖子不仅会发送给Misskey,还会发送到其他SNS平台。想象一下,正如一颗行星和另一颗行星通过电磁波来进行通信一样。"
|
||||
about: "Misskey是开源的<b>分散式微博SNS</b>。丰富且可以高度定制的Ui,对别人的帖子进行各种回应,集成管理系统的网盘等先进功能。此外,称为“联邦世界”的网络系统使我们能够与其他SNS的用户进行通信。比如,如果你发布一个帖子,那么你的帖子不仅会发送给Misskey,还会发送到其他SNS平台。想象一下,正如一颗行星和另一颗行星通过电磁波来进行通信一样。"
|
||||
features: "功能"
|
||||
rich-contents: "发布"
|
||||
rich-contents-desc: "请分享您的想法,热门话题,以及任何您想与大家分享的内容。如果有需要的话,您可以使用各种语法来修饰文章,发布问卷调查,或者添加各种您喜欢的图像和视频等文件。"
|
||||
@@ -16,7 +16,7 @@ common:
|
||||
ui: "交互界面"
|
||||
ui-desc: "世界上没有一个UI可以适合每一个人. 所以, Misskey 提供一个可以高度定制的UI交互界面. 您可以通过编辑, 调整布局, 放置可选择的小部件来轻松定制您的专属UI界面。"
|
||||
drive: "网盘"
|
||||
drive-desc: "想要发布一张您已经上传过的照片吗?想要管理文件或为上传的文件创建文件夹吗?Misskey的网盘是一个最好的解决方案。"
|
||||
drive-desc: "想要发布一张您已经上传过的照片吗?想要管理文件或为上传的文件创建文件夹吗?Misskey的内置网盘将为您完美解决这些问题。简单地分享您的文件。"
|
||||
outro: "Misskey还有其他更多功能,请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS,如果您感觉某个功能不适合自己,试试其他的吧。祝您玩得开心!"
|
||||
application-authorization: "应用程序授权"
|
||||
close: "关闭"
|
||||
@@ -103,6 +103,12 @@ common:
|
||||
"read:reactions": "查看回应"
|
||||
"write:reactions": "回应操作"
|
||||
"write:votes": "投票"
|
||||
"read:pages": "查看页面"
|
||||
"write:pages": "操作页面"
|
||||
"read:page-likes": "查看喜欢的页面"
|
||||
"write:page-likes": "操作喜欢的页面"
|
||||
"read:user-groups": "查看用户组"
|
||||
"write:user-groups": "操作用户组"
|
||||
empty-timeline-info:
|
||||
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
|
||||
explore: "查找用户"
|
||||
@@ -185,7 +191,7 @@ common:
|
||||
profile: "个人资料"
|
||||
notification: "通知"
|
||||
apps: "应用程序"
|
||||
tags: "哈希标签"
|
||||
tags: "话题标签"
|
||||
mute-and-block: "屏蔽/拉黑"
|
||||
blocking: "拉黑"
|
||||
security: "安全性"
|
||||
@@ -194,6 +200,8 @@ common:
|
||||
other: "其他"
|
||||
appearance: "设计"
|
||||
behavior: "行为"
|
||||
reactions: "回应"
|
||||
reactions-description: "快速选择回应中的自定义表情符号,以换行符分隔。"
|
||||
fetch-on-scroll: "向下滚动时自动加载"
|
||||
fetch-on-scroll-desc: "向下滚动页面时,它会自动提取其他内容。"
|
||||
note-visibility: "帖子可见性"
|
||||
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
|
||||
popular-users: "热门用户"
|
||||
recently-updated-users: "活跃用户"
|
||||
recently-registered-users: "新用户"
|
||||
recently-discovered-users: "最近发现的用户"
|
||||
popular-tags: "热门标签"
|
||||
federated: "联合"
|
||||
federated: "联邦"
|
||||
explore: "查找{host}"
|
||||
explore-fediverse: "探索Fediverse"
|
||||
users-info: "当前有{users}个注册用户"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users}作出了{reaction}的回应"
|
||||
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "选择回应"
|
||||
input-reaction-placeholder: "表情符号输入"
|
||||
common/views/components/emoji-picker.vue:
|
||||
recent-emoji: "最近使用的表情符号"
|
||||
custom-emoji: "自定义表情符号"
|
||||
no-category: "未分类"
|
||||
people: "人"
|
||||
animals-and-nature: "动物与自然"
|
||||
food-and-drink: "食物与饮品"
|
||||
@@ -640,7 +652,7 @@ common/views/components/signin.vue:
|
||||
username: "用户名"
|
||||
password: "密码"
|
||||
token: "Token (令牌)"
|
||||
signing-in: "正在登录..."
|
||||
signing-in: "在弄了在弄了..."
|
||||
or: "或者"
|
||||
signin-with-twitter: "用 Twitter 登录"
|
||||
signin-with-github: "用 GitHub 登录"
|
||||
@@ -1170,16 +1182,16 @@ desktop/views/components/window.vue:
|
||||
popout: "弹出"
|
||||
close: "关闭"
|
||||
admin/views/index.vue:
|
||||
dashboard: "Dashboard"
|
||||
instance: "例"
|
||||
emoji: "Emoji"
|
||||
dashboard: "仪表盘"
|
||||
instance: "实例"
|
||||
emoji: "自定义Emoji"
|
||||
moderators: "版主"
|
||||
users: "用户"
|
||||
federation: "联合"
|
||||
federation: "联邦"
|
||||
announcements: "公告"
|
||||
abuse: "举报垃圾信息"
|
||||
queue: "作业队列"
|
||||
logs: "登录"
|
||||
logs: "日志"
|
||||
db: "数据库"
|
||||
back-to-misskey: "返回 Misskey"
|
||||
admin/views/db.vue:
|
||||
@@ -1192,7 +1204,7 @@ admin/views/dashboard.vue:
|
||||
accounts: "账户"
|
||||
notes: "帖子"
|
||||
drive: "网盘"
|
||||
instances: "例子"
|
||||
instances: "实例"
|
||||
this-instance: "此实例"
|
||||
federated: "联合"
|
||||
admin/views/queue.vue:
|
||||
@@ -1213,7 +1225,7 @@ admin/views/queue.vue:
|
||||
result-is-truncated: "结果已省略"
|
||||
other-queues: "其他队列"
|
||||
admin/views/logs.vue:
|
||||
logs: "登录"
|
||||
logs: "日志"
|
||||
domain: "域"
|
||||
level: "级别"
|
||||
levels:
|
||||
@@ -1231,7 +1243,7 @@ admin/views/abuse.vue:
|
||||
details: "详情"
|
||||
remove-report: "删除"
|
||||
admin/views/instance.vue:
|
||||
instance: "例子"
|
||||
instance: "实例"
|
||||
instance-name: "实例名称"
|
||||
instance-description: "实例介绍"
|
||||
host: "主机名"
|
||||
@@ -1435,7 +1447,7 @@ admin/views/moderators.vue:
|
||||
remove: "取消"
|
||||
removed: "取消注册版主"
|
||||
logs:
|
||||
title: "登录"
|
||||
title: "日志"
|
||||
moderator: "版主"
|
||||
type: "操作"
|
||||
at: "日期和时间"
|
||||
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
|
||||
title: "添加emoji"
|
||||
name: "Emoji 名称"
|
||||
name-desc: "你可以使用字符a~z 0~9 _"
|
||||
category: "类别"
|
||||
aliases: "别名"
|
||||
aliases-desc: "您可以添加多个,以空格分隔。"
|
||||
url: "emoji 地址"
|
||||
@@ -1489,7 +1502,7 @@ admin/views/federation.vue:
|
||||
block: "拉黑"
|
||||
marked-as-closed: "标记为已关闭"
|
||||
lookup: "查询"
|
||||
instances: "联合"
|
||||
instances: "联邦"
|
||||
instance-not-registered: "实例未注册"
|
||||
sort: "排序"
|
||||
sorts:
|
||||
@@ -1609,7 +1622,7 @@ desktop/views/widgets/trends.vue:
|
||||
desktop/views/widgets/users.vue:
|
||||
title: "推荐用户"
|
||||
refresh: "更多"
|
||||
no-one: "不是!"
|
||||
no-one: "没有任何推荐用户!"
|
||||
mobile/views/components/drive.vue:
|
||||
used: "已使用"
|
||||
folder-count: "文件夹"
|
||||
|
13
migration/1571220798684-CustomEmojiCategory.ts
Normal file
13
migration/1571220798684-CustomEmojiCategory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class CustomEmojiCategory1571220798684 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "category" character varying(128)`, undefined);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "category"`, undefined);
|
||||
}
|
||||
|
||||
}
|
56
package.json
56
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.33.0",
|
||||
"version": "11.35.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,10 +27,11 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "7.3.0",
|
||||
"@elastic/elasticsearch": "7.4.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.25",
|
||||
"@fortawesome/free-brands-svg-icons": "5.11.2",
|
||||
"@fortawesome/free-regular-svg-icons": "5.11.2",
|
||||
@@ -38,7 +39,7 @@
|
||||
"@fortawesome/vue-fontawesome": "0.1.7",
|
||||
"@koa/cors": "3.0.0",
|
||||
"@koa/multer": "2.0.0",
|
||||
"@koa/router": "8.0.1",
|
||||
"@koa/router": "8.0.2",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.10.3",
|
||||
"@types/cbor": "2.0.0",
|
||||
@@ -69,7 +70,7 @@
|
||||
"@types/koa__router": "8.0.0",
|
||||
"@types/lolex": "3.1.1",
|
||||
"@types/mocha": "5.2.7",
|
||||
"@types/node": "12.7.8",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/nodemailer": "6.2.1",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/oauth": "0.9.1",
|
||||
@@ -95,21 +96,21 @@
|
||||
"@types/tmp": "0.1.0",
|
||||
"@types/uuid": "3.4.5",
|
||||
"@types/web-push": "3.3.0",
|
||||
"@types/webpack": "4.39.1",
|
||||
"@types/webpack": "4.39.3",
|
||||
"@types/webpack-stream": "3.2.10",
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.3",
|
||||
"@typescript-eslint/parser": "2.3.1",
|
||||
"agentkeepalive": "4.0.2",
|
||||
"@typescript-eslint/parser": "2.3.3",
|
||||
"agentkeepalive": "4.1.0",
|
||||
"animejs": "3.1.0",
|
||||
"apexcharts": "3.8.6",
|
||||
"apexcharts": "3.10.0",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.537.0",
|
||||
"aws-sdk": "2.548.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bootstrap": "4.3.1",
|
||||
"bootstrap-vue": "2.0.2",
|
||||
"bootstrap-vue": "2.0.4",
|
||||
"bull": "3.11.0",
|
||||
"cafy": "15.1.1",
|
||||
"cbor": "5.0.1",
|
||||
@@ -125,7 +126,7 @@
|
||||
"deep-equal": "1.1.0",
|
||||
"diskusage": "1.1.3",
|
||||
"double-ended-queue": "2.1.0-0",
|
||||
"eslint": "6.4.0",
|
||||
"eslint": "6.5.1",
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"eventemitter3": "4.0.0",
|
||||
"feed": "4.0.0",
|
||||
@@ -133,8 +134,7 @@
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-imagemin": "6.1.0",
|
||||
"gulp-mocha": "7.0.1",
|
||||
"gulp-mocha": "7.0.2",
|
||||
"gulp-rename": "1.4.0",
|
||||
"gulp-replace": "1.0.0",
|
||||
"gulp-sourcemaps": "2.6.5",
|
||||
@@ -146,17 +146,17 @@
|
||||
"hard-source-webpack-plugin": "0.13.1",
|
||||
"html-minifier": "4.0.0",
|
||||
"http-signature": "1.2.0",
|
||||
"https-proxy-agent": "2.2.2",
|
||||
"https-proxy-agent": "3.0.0",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"is-root": "2.1.0",
|
||||
"is-svg": "4.2.0",
|
||||
"js-yaml": "3.13.1",
|
||||
"jsdom": "15.1.1",
|
||||
"json5": "2.1.0",
|
||||
"json5": "2.1.1",
|
||||
"json5-loader": "3.0.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"katex": "0.11.1",
|
||||
"koa": "2.8.1",
|
||||
"koa": "2.10.0",
|
||||
"koa-bodyparser": "4.2.1",
|
||||
"koa-compress": "3.0.0",
|
||||
"koa-favicon": "2.0.1",
|
||||
@@ -170,13 +170,13 @@
|
||||
"loader-utils": "1.2.3",
|
||||
"lolex": "4.2.0",
|
||||
"lookup-dns-cache": "2.1.0",
|
||||
"mocha": "6.2.0",
|
||||
"mocha": "6.2.1",
|
||||
"moji": "0.5.1",
|
||||
"ms": "2.1.2",
|
||||
"multer": "1.4.2",
|
||||
"nested-property": "1.0.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"nodemailer": "6.3.0",
|
||||
"nodemailer": "6.3.1",
|
||||
"nprogress": "0.2.0",
|
||||
"object-assign-deep": "0.4.0",
|
||||
"os-utils": "0.0.14",
|
||||
@@ -220,22 +220,22 @@
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.3.1",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "4.14.8",
|
||||
"systeminformation": "4.14.11",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "2.1.0",
|
||||
"terser-webpack-plugin": "2.1.3",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.108.0",
|
||||
"three": "0.109.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.1.0",
|
||||
"ts-loader": "6.1.2",
|
||||
"ts-loader": "6.2.0",
|
||||
"ts-node": "8.4.1",
|
||||
"tslint": "5.20.0",
|
||||
"tslint-sonarts": "1.9.0",
|
||||
"typeorm": "0.2.19",
|
||||
"typescript": "3.6.3",
|
||||
"typescript": "3.6.4",
|
||||
"uglify-es": "3.3.9",
|
||||
"ulid": "2.3.0",
|
||||
"url-loader": "2.1.0",
|
||||
"url-loader": "2.2.0",
|
||||
"uuid": "3.3.3",
|
||||
"v-animate-css": "0.0.3",
|
||||
"v-debounce": "0.1.2",
|
||||
@@ -252,14 +252,14 @@
|
||||
"vue-router": "3.1.3",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.3.0",
|
||||
"vue-svg-inline-loader": "1.3.3",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.23.1",
|
||||
"vuedraggable": "2.23.2",
|
||||
"vuewordcloud": "18.7.11",
|
||||
"vuex": "3.1.1",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"web-push": "3.3.5",
|
||||
"webpack": "4.41.0",
|
||||
"web-push": "3.4.0",
|
||||
"webpack": "4.41.1",
|
||||
"webpack-cli": "3.3.9",
|
||||
"websocket": "1.0.30",
|
||||
"ws": "7.1.2",
|
||||
|
@@ -8,6 +8,9 @@
|
||||
<span>{{ $t('add-emoji.name') }}</span>
|
||||
<template #desc>{{ $t('add-emoji.name-desc') }}</template>
|
||||
</ui-input>
|
||||
<ui-input v-model="category" :datalist="categoryList">
|
||||
<span>{{ $t('add-emoji.category') }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="aliases">
|
||||
<span>{{ $t('add-emoji.aliases') }}</span>
|
||||
<template #desc>{{ $t('add-emoji.aliases-desc') }}</template>
|
||||
@@ -24,7 +27,7 @@
|
||||
|
||||
<ui-card>
|
||||
<template #title><fa :icon="faGrin"/> {{ $t('emojis.title') }}</template>
|
||||
<section v-for="emoji in emojis" class="oryfrbft">
|
||||
<section v-for="emoji in emojis" :key="emoji.name" class="oryfrbft">
|
||||
<div>
|
||||
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
|
||||
</div>
|
||||
@@ -33,6 +36,9 @@
|
||||
<ui-input v-model="emoji.name">
|
||||
<span>{{ $t('add-emoji.name') }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="emoji.category" :datalist="categoryList">
|
||||
<span>{{ $t('add-emoji.category') }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="emoji.aliases">
|
||||
<span>{{ $t('add-emoji.aliases') }}</span>
|
||||
</ui-input>
|
||||
@@ -55,12 +61,14 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { faGrin } from '@fortawesome/free-regular-svg-icons';
|
||||
import { unique } from '../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('admin/views/emoji.vue'),
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
category: '',
|
||||
url: '',
|
||||
aliases: '',
|
||||
emojis: [],
|
||||
@@ -72,10 +80,17 @@ export default Vue.extend({
|
||||
this.fetchEmojis();
|
||||
},
|
||||
|
||||
computed: {
|
||||
categoryList() {
|
||||
return unique(this.emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.$root.api('admin/emoji/add', {
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
url: this.url,
|
||||
aliases: this.aliases.split(' ').filter(x => x.length > 0)
|
||||
}).then(() => {
|
||||
@@ -94,7 +109,6 @@ export default Vue.extend({
|
||||
|
||||
fetchEmojis() {
|
||||
this.$root.api('admin/emoji/list').then(emojis => {
|
||||
emojis.reverse();
|
||||
for (const e of emojis) {
|
||||
e.aliases = (e.aliases || []).join(' ');
|
||||
}
|
||||
@@ -106,6 +120,7 @@ export default Vue.extend({
|
||||
this.$root.api('admin/emoji/update', {
|
||||
id: emoji.id,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
url: emoji.url,
|
||||
aliases: emoji.aliases.split(' ').filter(x => x.length > 0)
|
||||
}).then(() => {
|
||||
|
@@ -43,7 +43,7 @@ export default (opts: Opts = {}) => ({
|
||||
'ctrl+q': this.renoteDirectly,
|
||||
'up|k|shift+tab': this.focusBefore,
|
||||
'down|j|tab': this.focusAfter,
|
||||
'esc': this.blur,
|
||||
//'esc': this.blur,
|
||||
'm|o': () => this.menu(true),
|
||||
's': this.toggleShowContent,
|
||||
'1': () => this.reactDirectly('like'),
|
||||
@@ -143,12 +143,15 @@ export default (opts: Opts = {}) => ({
|
||||
react(viaKeyboard = false) {
|
||||
pleaseLogin(this.$root);
|
||||
this.blur();
|
||||
this.$root.new(MkReactionPicker, {
|
||||
const w = this.$root.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.appearNote,
|
||||
showFocus: viaKeyboard,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.close();
|
||||
});
|
||||
},
|
||||
|
||||
reactDirectly(reaction) {
|
||||
@@ -195,7 +198,7 @@ export default (opts: Opts = {}) => ({
|
||||
menu(viaKeyboard = false) {
|
||||
if (this.openingMenu) return;
|
||||
this.openingMenu = true;
|
||||
this.$root.new(MkNoteMenu, {
|
||||
const w = this.$root.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.appearNote,
|
||||
animation: !viaKeyboard
|
||||
@@ -203,6 +206,9 @@ export default (opts: Opts = {}) => ({
|
||||
this.openingMenu = false;
|
||||
this.focus();
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
toggleShowContent() {
|
||||
|
@@ -328,6 +328,9 @@ export default (opts) => ({
|
||||
w.$once('chosen', v => {
|
||||
this.applyVisibility(v);
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.close();
|
||||
});
|
||||
},
|
||||
|
||||
applyVisibility(v: string) {
|
||||
@@ -457,6 +460,9 @@ export default (opts) => ({
|
||||
vm.$once('chosen', emoji => {
|
||||
insertTextAtCursor(this.$refs.text, emoji);
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
vm.close();
|
||||
});
|
||||
},
|
||||
|
||||
saveDraft() {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-connect-failed">
|
||||
<img src="https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg" alt=""/>
|
||||
<img src="/assets/error.jpg" onerror="this.src='https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg';" alt=""/>
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
<p class="text">
|
||||
<span>{{ this.$t('description').substr(0, this.$t('description').indexOf('{')) }}</span>
|
||||
|
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div class="felqjxyj" :class="{ splash }">
|
||||
<div class="bg" ref="bg" @click="onBgClick"></div>
|
||||
<ui-modal
|
||||
ref="modal"
|
||||
class="modal"
|
||||
:class="{ splash }"
|
||||
:close-anime-duration="300"
|
||||
:close-on-bg-click="false"
|
||||
@bg-click="onBgClick"
|
||||
@before-close="onBeforeClose">
|
||||
<div class="main" ref="main" :class="{ round: $store.state.device.roundedCorners }">
|
||||
<template v-if="type == 'signin'">
|
||||
<mk-signin/>
|
||||
@@ -38,7 +44,7 @@
|
||||
</ui-horizon-group>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</ui-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -120,14 +126,6 @@ export default Vue.extend({
|
||||
if (this.user) this.canOk = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
|
||||
anime({
|
||||
targets: this.$refs.main,
|
||||
opacity: 1,
|
||||
@@ -170,33 +168,27 @@ export default Vue.extend({
|
||||
this.close();
|
||||
},
|
||||
|
||||
onBgClick() {
|
||||
if (this.cancelableByBgClick) this.cancel();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$refs.modal.close();
|
||||
},
|
||||
|
||||
onBeforeClose() {
|
||||
this.$el.style.pointerEvents = 'none';
|
||||
(this.$refs.bg as any).style.pointerEvents = 'none';
|
||||
(this.$refs.main as any).style.pointerEvents = 'none';
|
||||
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 0,
|
||||
duration: 300,
|
||||
easing: 'linear'
|
||||
});
|
||||
anime({
|
||||
targets: this.$refs.main,
|
||||
opacity: 0,
|
||||
scale: 0.8,
|
||||
duration: 300,
|
||||
easing: 'cubicBezier(0, 0.5, 0.5, 1)',
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
},
|
||||
|
||||
onBgClick() {
|
||||
if (this.cancelableByBgClick) {
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeydown(e) {
|
||||
if (e.which == 13) { // Enter
|
||||
e.preventDefault();
|
||||
@@ -209,80 +201,63 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.felqjxyj
|
||||
.modal
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
position fixed
|
||||
z-index 30000
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
&.splash
|
||||
> .main
|
||||
min-width 0
|
||||
width initial
|
||||
|
||||
> .bg
|
||||
display block
|
||||
position fixed
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(#000, 0.7)
|
||||
opacity 0
|
||||
pointer-events none
|
||||
.main
|
||||
display block
|
||||
position fixed
|
||||
margin auto
|
||||
padding 32px
|
||||
min-width 320px
|
||||
max-width 480px
|
||||
width calc(100% - 32px)
|
||||
text-align center
|
||||
background var(--face)
|
||||
color var(--faceText)
|
||||
opacity 0
|
||||
|
||||
> .main
|
||||
display block
|
||||
position fixed
|
||||
margin auto
|
||||
padding 32px
|
||||
min-width 320px
|
||||
max-width 480px
|
||||
width calc(100% - 32px)
|
||||
text-align center
|
||||
background var(--face)
|
||||
color var(--faceText)
|
||||
opacity 0
|
||||
&.round
|
||||
border-radius 8px
|
||||
|
||||
&.round
|
||||
border-radius 8px
|
||||
> .icon
|
||||
font-size 32px
|
||||
|
||||
> .icon
|
||||
font-size 32px
|
||||
&.success
|
||||
color #85da5a
|
||||
|
||||
&.success
|
||||
color #85da5a
|
||||
&.error
|
||||
color #ec4137
|
||||
|
||||
&.error
|
||||
color #ec4137
|
||||
&.warning
|
||||
color #ecb637
|
||||
|
||||
&.warning
|
||||
color #ecb637
|
||||
> *
|
||||
display block
|
||||
margin 0 auto
|
||||
|
||||
> *
|
||||
display block
|
||||
margin 0 auto
|
||||
|
||||
& + header
|
||||
margin-top 16px
|
||||
|
||||
> header
|
||||
margin 0 0 8px 0
|
||||
font-weight bold
|
||||
font-size 20px
|
||||
|
||||
& + .body
|
||||
margin-top 8px
|
||||
|
||||
> .body
|
||||
margin 16px 0 0 0
|
||||
|
||||
> .buttons
|
||||
& + header
|
||||
margin-top 16px
|
||||
|
||||
> header
|
||||
margin 0 0 8px 0
|
||||
font-weight bold
|
||||
font-size 20px
|
||||
|
||||
& + .body
|
||||
margin-top 8px
|
||||
|
||||
> .body
|
||||
margin 16px 0 0 0
|
||||
|
||||
> .buttons
|
||||
margin-top 16px
|
||||
|
||||
</style>
|
||||
|
@@ -11,25 +11,46 @@
|
||||
</button>
|
||||
</header>
|
||||
<div class="emojis">
|
||||
<header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
|
||||
<div v-if="categories.find(x => x.isActive).name">
|
||||
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
|
||||
:title="emoji.name"
|
||||
@click="chosen(emoji.char)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<mk-emoji :emoji="emoji.char"/>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button v-for="emoji in customEmojis"
|
||||
:title="emoji.name"
|
||||
@click="chosen(`:${emoji.name}:`)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<img :src="emoji.url" :alt="emoji.name"/>
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="categories[0].isActive">
|
||||
<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
|
||||
<div class="list">
|
||||
<button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
|
||||
:title="emoji.name"
|
||||
@click="chosen(emoji)"
|
||||
:key="i"
|
||||
>
|
||||
<mk-emoji v-if="emoji.char != null" :emoji="emoji.char"/>
|
||||
<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<header class="category"><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
|
||||
<template v-if="categories.find(x => x.isActive).name">
|
||||
<div class="list">
|
||||
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
|
||||
:title="emoji.name"
|
||||
@click="chosen(emoji)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<mk-emoji :emoji="emoji.char"/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="(key, i) in Object.keys(customEmojis)" :key="i">
|
||||
<header class="sub">{{ key || $t('no-category') }}</header>
|
||||
<div class="list">
|
||||
<button v-for="emoji in customEmojis[key]"
|
||||
:title="emoji.name"
|
||||
@click="chosen(emoji)"
|
||||
:key="emoji.name"
|
||||
>
|
||||
<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,8 +59,10 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { emojilist } from '../../../../../misc/emojilist';
|
||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons';
|
||||
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
|
||||
import { groupByX } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/emoji-picker.vue'),
|
||||
@@ -47,7 +70,9 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
emojilist,
|
||||
customEmojis: [],
|
||||
getStaticImageUrl,
|
||||
customEmojis: {},
|
||||
faGlobe, faHistory,
|
||||
categories: [{
|
||||
text: this.$t('custom-emoji'),
|
||||
icon: faAsterisk,
|
||||
@@ -97,18 +122,43 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
local = groupByX(local, (x: any) => x.category || '');
|
||||
this.customEmojis = local;
|
||||
|
||||
if (this.$store.state.device.activeEmojiCategoryName) {
|
||||
this.goCategory(this.$store.state.device.activeEmojiCategoryName);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
go(category) {
|
||||
go(category: any) {
|
||||
this.goCategory(category.name);
|
||||
},
|
||||
|
||||
goCategory(name: string) {
|
||||
let matched = false;
|
||||
for (const c of this.categories) {
|
||||
c.isActive = c.name === category.name;
|
||||
c.isActive = c.name === name;
|
||||
if (c.isActive) {
|
||||
matched = true;
|
||||
this.$store.commit('device/set', { key: 'activeEmojiCategoryName', value: c.name });
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
this.categories[0].isActive = true;
|
||||
}
|
||||
},
|
||||
|
||||
chosen(emoji) {
|
||||
this.$emit('chosen', emoji);
|
||||
chosen(emoji: any) {
|
||||
const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`;
|
||||
|
||||
let recents = this.$store.state.device.recentEmojis || [];
|
||||
recents = recents.filter((e: any) => getKey(e) !== getKey(emoji));
|
||||
recents.unshift(emoji)
|
||||
this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) });
|
||||
|
||||
this.$emit('chosen', getKey(emoji));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -142,7 +192,7 @@ export default Vue.extend({
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
|
||||
> header
|
||||
> header.category
|
||||
position sticky
|
||||
top 0
|
||||
left 0
|
||||
@@ -152,7 +202,12 @@ export default Vue.extend({
|
||||
color var(--text)
|
||||
font-size 12px
|
||||
|
||||
> div
|
||||
>>> header.sub
|
||||
padding 4px 8px
|
||||
color var(--text)
|
||||
font-size 12px
|
||||
|
||||
>>> div.list
|
||||
display grid
|
||||
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
|
||||
gap 4px
|
||||
@@ -180,6 +235,7 @@ export default Vue.extend({
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
object-fit contain
|
||||
font-size 28px
|
||||
transition transform 0.2s ease
|
||||
pointer-events none
|
||||
|
@@ -1,70 +1,41 @@
|
||||
<template>
|
||||
<div class="dkjvrdxtkvqrwmhfickhndpmnncsgacq">
|
||||
<div class="bg" @click="close"></div>
|
||||
<img :src="image.url" :alt="image.name" :title="image.name" @click="close"/>
|
||||
</div>
|
||||
<ui-modal ref="modal" v-hotkey.global="keymap">
|
||||
<img :src="image.url" :alt="image.name" :title="image.name" @click="close" />
|
||||
</ui-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import anime from 'animejs';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['image'],
|
||||
mounted() {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'esc': this.close,
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
opacity: 0,
|
||||
duration: 100,
|
||||
easing: 'linear',
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
(this.$refs.modal as any).close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.dkjvrdxtkvqrwmhfickhndpmnncsgacq
|
||||
display block
|
||||
img
|
||||
position fixed
|
||||
z-index 2048
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
opacity 0
|
||||
|
||||
> .bg
|
||||
display block
|
||||
position fixed
|
||||
z-index 1
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(#000, 0.7)
|
||||
|
||||
> img
|
||||
position fixed
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
max-width 100%
|
||||
max-height 100%
|
||||
margin auto
|
||||
cursor zoom-out
|
||||
image-orientation from-image
|
||||
max-width 100%
|
||||
max-height 100%
|
||||
margin auto
|
||||
cursor zoom-out
|
||||
image-orientation from-image
|
||||
|
||||
</style>
|
||||
|
@@ -47,6 +47,7 @@ import uiInfo from './ui/info.vue';
|
||||
import uiMargin from './ui/margin.vue';
|
||||
import uiHr from './ui/hr.vue';
|
||||
import uiPagination from './ui/pagination.vue';
|
||||
import uiModal from './ui/modal.vue';
|
||||
import formButton from './ui/form/button.vue';
|
||||
import formRadio from './ui/form/radio.vue';
|
||||
|
||||
@@ -97,5 +98,6 @@ Vue.component('ui-info', uiInfo);
|
||||
Vue.component('ui-margin', uiMargin);
|
||||
Vue.component('ui-hr', uiHr);
|
||||
Vue.component('ui-pagination', uiPagination);
|
||||
Vue.component('ui-modal', uiModal);
|
||||
Vue.component('form-button', formButton);
|
||||
Vue.component('form-radio', formRadio);
|
||||
|
@@ -59,9 +59,12 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$root.new(ImageViewer, {
|
||||
const viewer = this.$root.new(ImageViewer, {
|
||||
image: this.image
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
viewer.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -138,7 +138,7 @@ export default Vue.extend({
|
||||
}
|
||||
this.$root.api('users/search', {
|
||||
query: this.q,
|
||||
localOnly: true,
|
||||
localOnly: false,
|
||||
limit: 10,
|
||||
detail: false
|
||||
}).then(users => {
|
||||
|
@@ -19,6 +19,7 @@ export default Vue.extend({
|
||||
|
||||
&.nowrap
|
||||
white-space pre
|
||||
word-wrap normal // https://codeday.me/jp/qa/20190424/690106.html
|
||||
|
||||
>>> .title
|
||||
display block
|
||||
|
@@ -4,16 +4,7 @@
|
||||
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
|
||||
<p v-if="!$root.isMobile">{{ title }}</p>
|
||||
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
|
||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
|
||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
|
||||
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" :title="$t('@.reactions.hmm')" v-particle><mk-reaction-icon reaction="hmm"/></button>
|
||||
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" :title="$t('@.reactions.surprise')" v-particle><mk-reaction-icon reaction="surprise"/></button>
|
||||
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" :title="$t('@.reactions.congrats')" v-particle><mk-reaction-icon reaction="congrats"/></button>
|
||||
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" :title="$t('@.reactions.angry')" v-particle><mk-reaction-icon reaction="angry"/></button>
|
||||
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" :title="$t('@.reactions.confused')" v-particle><mk-reaction-icon reaction="confused"/></button>
|
||||
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
|
||||
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
|
||||
<button v-for="(reaction, i) in $store.state.settings.reactions" :key="reaction" @click="react(reaction)" @mouseover="onMouseover" @mouseout="onMouseout" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction" v-particle><mk-reaction-icon :reaction="reaction"/></button>
|
||||
</div>
|
||||
<div v-if="enableEmojiReaction" class="text">
|
||||
<input v-model="text" :placeholder="$t('input-reaction-placeholder')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||
|
@@ -108,6 +108,13 @@
|
||||
<ui-switch v-if="$root.isMobile" v-model="disableViaMobile">{{ $t('@._settings.disable-via-mobile') }}</ui-switch>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('@._settings.reactions') }}</header>
|
||||
<ui-textarea v-model="reactions">
|
||||
{{ $t('@._settings.reactions') }}<template #desc>{{ $t('@._settings.reactions-description') }}</template>
|
||||
</ui-textarea>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('@._settings.timeline') }}</header>
|
||||
<ui-switch v-model="showMyRenotes">{{ $t('@._settings.show-my-renotes') }}</ui-switch>
|
||||
@@ -407,6 +414,11 @@ export default Vue.extend({
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
|
||||
},
|
||||
|
||||
reactions: {
|
||||
get() { return this.$store.state.settings.reactions.join('\n'); },
|
||||
set(value: string) { this.$store.dispatch('settings/set', { key: 'reactions', value: value.trim().split('\n') }); }
|
||||
},
|
||||
|
||||
useShadow: {
|
||||
get() { return this.$store.state.device.useShadow; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'useShadow', value }); }
|
||||
|
@@ -6,7 +6,10 @@
|
||||
<div class="value" ref="passwordMetar"></div>
|
||||
</div>
|
||||
<span class="label" ref="label"><slot></slot></span>
|
||||
<span class="title" ref="title"><slot name="title"></slot></span>
|
||||
<span class="title" ref="title">
|
||||
<slot name="title"></slot>
|
||||
<span class="warning" v-if="invalid"><fa :icon="['fa', 'exclamation-circle']"/>{{ $refs.input.validationMessage }}</span>
|
||||
</span>
|
||||
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||
<template v-if="type != 'file'">
|
||||
<input v-if="debounce" ref="input"
|
||||
@@ -158,6 +161,7 @@ export default Vue.extend({
|
||||
return {
|
||||
v: this.value,
|
||||
focused: false,
|
||||
invalid: false,
|
||||
passwordStrength: '',
|
||||
id: Math.random().toString()
|
||||
};
|
||||
@@ -200,6 +204,8 @@ export default Vue.extend({
|
||||
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
||||
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
|
||||
}
|
||||
|
||||
this.invalid = this.$refs.input.validity.badInput;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -365,6 +371,17 @@ root(fill)
|
||||
//will-change transform
|
||||
transform-origin top left
|
||||
transform scale(.75)
|
||||
white-space nowrap
|
||||
width 133%
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
|
||||
> .warning
|
||||
margin-left 0.5em
|
||||
color var(--infoWarnFg)
|
||||
|
||||
> svg
|
||||
margin-right 0.1em
|
||||
|
||||
> input
|
||||
display block
|
||||
|
80
src/client/app/common/views/components/ui/modal.vue
Normal file
80
src/client/app/common/views/components/ui/modal.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="modal">
|
||||
<div class="bg" ref="bg" @click="onBgClick" />
|
||||
<slot class="main" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import anime from 'animejs';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
closeOnBgClick: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
openAnimeDuration: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
closeAnimeDuration: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 1,
|
||||
duration: this.openAnimeDuration,
|
||||
easing: 'linear'
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onBgClick() {
|
||||
this.$emit('bg-click');
|
||||
if (this.closeOnBgClick) this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('before-close');
|
||||
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 0,
|
||||
duration: this.closeAnimeDuration,
|
||||
easing: 'linear',
|
||||
complete: () => (this as any).destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.modal
|
||||
position fixed
|
||||
z-index 2048
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
.bg
|
||||
display block
|
||||
position fixed
|
||||
z-index 1
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(#000, 0.7)
|
||||
opacity 0
|
||||
|
||||
.main
|
||||
z-index 1
|
||||
</style>
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ui-container :body-togglable="true">
|
||||
<ui-container :body-togglable="true" :expanded="expanded">
|
||||
<template #header><slot></slot></template>
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
@@ -50,7 +50,11 @@ export default Vue.extend({
|
||||
iconOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@@ -27,7 +27,7 @@
|
||||
<div class="description">
|
||||
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/>
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<div class="fields" v-if="user.fields" :key="user.id">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">
|
||||
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
|
||||
@@ -112,10 +112,13 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
menu() {
|
||||
this.$root.new(XUserMenu, {
|
||||
const w = this.$root.new(XUserMenu, {
|
||||
source: this.$refs.menu,
|
||||
user: this.user
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,15 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<ui-container :show-header="false" v-if="meta && stats">
|
||||
<div class="kpdsmpnk" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
|
||||
<div>
|
||||
<router-link to="/explore" class="title">{{ $t('explore', { host: meta.name || 'Misskey' }) }}</router-link>
|
||||
<span>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ui-container>
|
||||
<div class="localfedi7" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
|
||||
<header>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</header>
|
||||
<div>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</div>
|
||||
</div>
|
||||
|
||||
<ui-container :body-togglable="true" :expanded="tag == null" ref="tags">
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :pagination="pinnedUsers" :expanded="false">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="popularUsers" :expanded="false">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyUpdatedUsers" :expanded="false">
|
||||
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyRegisteredUsers" :expanded="false">
|
||||
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
|
||||
</mk-user-list>
|
||||
</template>
|
||||
|
||||
<div class="localfedi7" v-if="tag == null" :style="{ backgroundImage: `url(/assets/fedi.jpg)` }">
|
||||
<header>{{ $t('explore-fediverse') }}</header>
|
||||
</div>
|
||||
|
||||
<ui-container :body-togglable="true" :expanded="false" ref="tags">
|
||||
<template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template>
|
||||
|
||||
<div class="vxjfqztj">
|
||||
@@ -18,25 +33,18 @@
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}-local`">
|
||||
<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}`">
|
||||
<fa :icon="faHashtag" fixed-width/>{{ tag }}
|
||||
</mk-user-list>
|
||||
<mk-user-list v-if="tag != null" :pagination="tagRemoteUsers" :key="`${tag}-remote`">
|
||||
<fa :icon="faHashtag" fixed-width/>{{ tag }} ({{ $t('federated') }})
|
||||
</mk-user-list>
|
||||
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :pagination="pinnedUsers">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="popularUsers">
|
||||
<mk-user-list :pagination="popularUsersF" :expanded="false">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyUpdatedUsers">
|
||||
<mk-user-list :pagination="recentlyUpdatedUsersF" :expanded="false">
|
||||
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :pagination="recentlyRegisteredUsers">
|
||||
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
|
||||
<mk-user-list :pagination="recentlyRegisteredUsersF" :expanded="false">
|
||||
<fa :icon="faRocket" fixed-width/>{{ $t('recently-discovered-users') }}
|
||||
</mk-user-list>
|
||||
</template>
|
||||
</div>
|
||||
@@ -45,7 +53,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { faChartLine, faPlus, faHashtag } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faChartLine, faPlus, faHashtag, faRocket } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -58,6 +66,12 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
inject: {
|
||||
inNakedDeckColumn: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
pinnedUsers: { endpoint: 'pinned-users' },
|
||||
@@ -75,12 +89,25 @@ export default Vue.extend({
|
||||
state: 'alive',
|
||||
sort: '+createdAt',
|
||||
} },
|
||||
popularUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
state: 'alive',
|
||||
origin: 'remote',
|
||||
sort: '+follower',
|
||||
} },
|
||||
recentlyUpdatedUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'combined',
|
||||
sort: '+updatedAt',
|
||||
} },
|
||||
recentlyRegisteredUsersF: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'combined',
|
||||
sort: '+createdAt',
|
||||
} },
|
||||
tagsLocal: [],
|
||||
tagsRemote: [],
|
||||
stats: null,
|
||||
meta: null,
|
||||
num: Vue.filter('number'),
|
||||
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag
|
||||
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag, faRocket
|
||||
};
|
||||
},
|
||||
|
||||
@@ -91,21 +118,7 @@ export default Vue.extend({
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
tagRemoteUsers(): any {
|
||||
return {
|
||||
endpoint: 'hashtags/users',
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'remote',
|
||||
origin: 'combined',
|
||||
sort: '+follower',
|
||||
}
|
||||
};
|
||||
@@ -152,6 +165,28 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.localfedi7
|
||||
overflow hidden
|
||||
background var(--face)
|
||||
color #fff
|
||||
text-shadow 0 0 8px #000
|
||||
border-radius 6px
|
||||
padding 16px
|
||||
margin-top 16px
|
||||
margin-bottom 16px
|
||||
height 80px
|
||||
background-position 50%
|
||||
background-size cover
|
||||
> header
|
||||
font-size 20px
|
||||
font-weight bold
|
||||
> div
|
||||
font-size 14px
|
||||
opacity 0.8
|
||||
|
||||
.localfedi7:first-child
|
||||
margin-top 0
|
||||
|
||||
.vxjfqztj
|
||||
padding 16px
|
||||
|
||||
@@ -160,35 +195,4 @@ export default Vue.extend({
|
||||
|
||||
&.local
|
||||
font-weight bold
|
||||
|
||||
.kpdsmpnk
|
||||
min-height 100px
|
||||
padding 16px
|
||||
background-position center
|
||||
background-size cover
|
||||
|
||||
&:before
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(0, 0, 0, 0.3)
|
||||
|
||||
> div
|
||||
color #fff
|
||||
text-shadow 0 0 8px #00
|
||||
|
||||
> .title
|
||||
display block
|
||||
font-size 20px
|
||||
font-weight bold
|
||||
color inherit
|
||||
|
||||
> span
|
||||
font-size 14px
|
||||
opacity 0.8
|
||||
|
||||
</style>
|
||||
|
@@ -37,7 +37,6 @@ import i18n from '../../../i18n';
|
||||
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
||||
import updateAvatar from '../../api/update-avatar';
|
||||
import updateBanner from '../../api/update-banner';
|
||||
import { appendQuery } from '../../../../../prelude/url';
|
||||
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -87,7 +86,8 @@ export default Vue.extend({
|
||||
action: this.copyUrl
|
||||
}, {
|
||||
type: 'link',
|
||||
href: appendQuery(this.file.url, 'download'),
|
||||
href: this.file.url,
|
||||
target: '_blank',
|
||||
text: this.$t('contextmenu.download'),
|
||||
icon: 'download',
|
||||
download: this.file.name
|
||||
|
@@ -1,36 +1,28 @@
|
||||
<template>
|
||||
<div class="mk-media-video-dialog">
|
||||
<div class="bg" @click="close"></div>
|
||||
<video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange"/>
|
||||
</div>
|
||||
<ui-modal v-hotkey.global="keymap">
|
||||
<video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange" />
|
||||
</ui-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import anime from 'animejs';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['video', 'start'],
|
||||
mounted() {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
const videoTag = this.$refs.video as HTMLVideoElement;
|
||||
if (this.start) videoTag.currentTime = this.start
|
||||
videoTag.volume = this.$store.state.device.mediaVolume;
|
||||
},
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'esc': this.close,
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
opacity: 0,
|
||||
duration: 100,
|
||||
easing: 'linear',
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
},
|
||||
volumechange() {
|
||||
const videoTag = this.$refs.video as HTMLVideoElement;
|
||||
@@ -41,35 +33,15 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-media-video-dialog
|
||||
display block
|
||||
video
|
||||
position fixed
|
||||
z-index 2048
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
opacity 0
|
||||
|
||||
> .bg
|
||||
display block
|
||||
position fixed
|
||||
z-index 1
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(#000, 0.7)
|
||||
|
||||
> video
|
||||
position fixed
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
max-width 80vw
|
||||
max-height 80vh
|
||||
margin auto
|
||||
max-width 80vw
|
||||
max-height 80vh
|
||||
margin auto
|
||||
|
||||
</style>
|
||||
|
@@ -53,10 +53,13 @@ export default Vue.extend({
|
||||
start = videoTag.currentTime
|
||||
videoTag.pause()
|
||||
}
|
||||
this.$root.new(MkMediaVideoDialog, {
|
||||
const viewer = this.$root.new(MkMediaVideoDialog, {
|
||||
video: this.video,
|
||||
start,
|
||||
})
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
viewer.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="kedshtep" :class="{ naked, inNakedDeckColumn, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||
<header v-if="showHeader">
|
||||
<header v-if="showHeader" :class="{ bodyTogglable }" @click="toggleContent(!showBody)">
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<slot name="func"></slot>
|
||||
<button v-if="bodyTogglable" @click="toggleContent(!showBody)">
|
||||
<button v-if="bodyTogglable">
|
||||
<template v-if="showBody"><fa icon="angle-up"/></template>
|
||||
<template v-else><fa icon="angle-down"/></template>
|
||||
</button>
|
||||
@@ -47,6 +47,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggleContent(show: boolean) {
|
||||
if (!this.bodyTogglable) return;
|
||||
this.showBody = show;
|
||||
this.$emit('toggle', show);
|
||||
}
|
||||
@@ -77,6 +78,9 @@ export default Vue.extend({
|
||||
> header
|
||||
background var(--faceHeader)
|
||||
|
||||
&.bodyTogglable
|
||||
cursor pointer
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
@@ -120,6 +124,9 @@ export default Vue.extend({
|
||||
color var(--text)
|
||||
background var(--deckColumnBg)
|
||||
|
||||
&.bodyTogglable
|
||||
cursor pointer
|
||||
|
||||
> button
|
||||
position absolute
|
||||
top 0
|
||||
|
@@ -25,7 +25,7 @@
|
||||
<p v-else class="empty">{{ $t('no-description') }}</p>
|
||||
<x-integrations :user="user" style="margin-top:16px;"/>
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<div class="fields" v-if="user.fields" :key="user.id">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">
|
||||
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
|
||||
@@ -106,10 +106,13 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
menu() {
|
||||
this.$root.new(XUserMenu, {
|
||||
const w = this.$root.new(XUserMenu, {
|
||||
source: this.$refs.menu,
|
||||
user: this.user
|
||||
});
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
w.destroyDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -57,7 +57,6 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { gcd } from '../../../../../prelude/math';
|
||||
import { appendQuery } from '../../../../../prelude/url';
|
||||
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -91,7 +90,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
dlUrl(): string {
|
||||
return appendQuery(this.file.url, 'download');
|
||||
return this.file.url;
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||
<div class="bg" ref="bg"></div>
|
||||
<ui-modal
|
||||
ref="modal"
|
||||
:close-on-bg-click="false"
|
||||
:close-anime-duration="300"
|
||||
@before-close="onBeforeClose">
|
||||
<div class="main" ref="main">
|
||||
<x-post-form ref="form"
|
||||
:reply="reply"
|
||||
@@ -12,7 +15,7 @@
|
||||
@posted="onPosted"
|
||||
@cancel="onCanceled"/>
|
||||
</div>
|
||||
</div>
|
||||
</ui-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -55,14 +58,6 @@ export default Vue.extend({
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
|
||||
anime({
|
||||
targets: this.$refs.main,
|
||||
opacity: 1,
|
||||
@@ -78,26 +73,22 @@ export default Vue.extend({
|
||||
this.$refs.form.focus();
|
||||
},
|
||||
|
||||
close() {
|
||||
(this.$refs.bg as any).style.pointerEvents = 'none';
|
||||
anime({
|
||||
targets: this.$refs.bg,
|
||||
opacity: 0,
|
||||
duration: 300,
|
||||
easing: 'linear'
|
||||
});
|
||||
|
||||
onBeforeClose() {
|
||||
(this.$refs.main as any).style.pointerEvents = 'none';
|
||||
|
||||
anime({
|
||||
targets: this.$refs.main,
|
||||
opacity: 0,
|
||||
translateY: 16,
|
||||
duration: 300,
|
||||
easing: 'easeOutQuad',
|
||||
complete: () => this.destroyDom()
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
(this.$refs.modal as any).close();
|
||||
},
|
||||
|
||||
onPosted() {
|
||||
this.$emit('posted');
|
||||
this.close();
|
||||
@@ -112,30 +103,18 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.ulveipglmagnxfgvitaxyszerjwiqmwl
|
||||
> .bg
|
||||
display block
|
||||
position fixed
|
||||
z-index 10000
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(#000, 0.7)
|
||||
opacity 0
|
||||
pointer-events none
|
||||
|
||||
> .main
|
||||
display block
|
||||
position fixed
|
||||
z-index 10000
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height 100%
|
||||
overflow auto
|
||||
margin 0 auto 0 auto
|
||||
opacity 0
|
||||
transform translateY(-16px)
|
||||
.main
|
||||
display block
|
||||
position fixed
|
||||
z-index 10000
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height 100%
|
||||
overflow auto
|
||||
margin 0 auto 0 auto
|
||||
opacity 0
|
||||
transform translateY(-16px)
|
||||
|
||||
</style>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="ukygtjoj" :class="{ naked, inNakedDeckColumn, hideHeader: !showHeader, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||
<header v-if="showHeader">
|
||||
<header v-if="showHeader" @click="() => showBody = !showBody">
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<slot name="func"></slot>
|
||||
<button v-if="bodyTogglable" @click="() => showBody = !showBody">
|
||||
<button v-if="bodyTogglable">
|
||||
<template v-if="showBody"><fa icon="angle-up"/></template>
|
||||
<template v-else><fa icon="angle-down"/></template>
|
||||
</button>
|
||||
@@ -47,6 +47,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggleContent(show: boolean) {
|
||||
if (!this.bodyTogglable) return;
|
||||
this.showBody = show;
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@
|
||||
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/>
|
||||
<x-integrations :user="user" style="margin:20px 0;"/>
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<div class="fields" v-if="user.fields" :key="user.id">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">
|
||||
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
|
||||
|
@@ -23,6 +23,7 @@ button
|
||||
border none
|
||||
cursor pointer
|
||||
color inherit
|
||||
touch-action manipulation
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
|
@@ -41,6 +41,7 @@ const defaultSettings = {
|
||||
uploadFolder: null,
|
||||
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
|
||||
pasteDialog: false,
|
||||
reactions: ['like', 'love', 'laugh', 'hmm', 'surprise', 'congrats', 'angry', 'confused', 'rip', 'pudding']
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
@@ -78,6 +79,8 @@ const defaultDeviceSettings = {
|
||||
enableMobileQuickNotificationView: false,
|
||||
roomGraphicsQuality: 'medium',
|
||||
roomUseOrthographicCamera: true,
|
||||
activeEmojiCategoryName: undefined,
|
||||
recentEmojis: [],
|
||||
};
|
||||
|
||||
export default (os: MiOS) => new Vuex.Store({
|
||||
|
BIN
src/client/assets/fedi.jpg
Normal file
BIN
src/client/assets/fedi.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
@@ -29,6 +29,7 @@ export type Source = {
|
||||
port: number;
|
||||
pass: string;
|
||||
index?: string;
|
||||
ssl?: boolean;
|
||||
};
|
||||
|
||||
autoAdmin?: boolean;
|
||||
|
@@ -32,7 +32,7 @@ const index = {
|
||||
|
||||
// Init ElasticSearch connection
|
||||
const client = config.elasticsearch ? new elasticsearch.Client({
|
||||
node: `http://${config.elasticsearch.host}:${config.elasticsearch.port}`,
|
||||
node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`,
|
||||
pingTimeout: 30000
|
||||
}) : null;
|
||||
|
||||
|
@@ -27,7 +27,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
|
||||
* `pos`として位置情報が含まれている
|
||||
|
||||
## 位置の計算法
|
||||
8x8のマップを考える場合、各マスの位置(Posと呼びます)は次のようになっています:
|
||||
8x8のマップを考える場合、各マスの位置(インデックスと呼びます)は次のようになっています:
|
||||
```
|
||||
+--+--+--+--+--+--+--+--+
|
||||
| 0| 1| 2| 3| 4| 5| 6| 7|
|
||||
@@ -38,7 +38,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
|
||||
...
|
||||
```
|
||||
|
||||
### X,Y座標 から Pos に変換する
|
||||
### X,Y座標 から インデックス に変換する
|
||||
```
|
||||
pos = x + (y * mapWidth)
|
||||
```
|
||||
@@ -47,7 +47,7 @@ pos = x + (y * mapWidth)
|
||||
mapWidth = map[0].length
|
||||
```
|
||||
|
||||
### Pos から X,Y座標 に変換する
|
||||
### インデックス から X,Y座標 に変換する
|
||||
```
|
||||
x = pos % mapWidth
|
||||
y = Math.floor(pos / mapWidth)
|
||||
|
@@ -135,8 +135,8 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
|
||||
break;
|
||||
default:
|
||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`;
|
||||
a.className = 'mention';
|
||||
a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${config.url}/${acct}`;
|
||||
a.className = 'u-url mention';
|
||||
break;
|
||||
}
|
||||
a.textContent = acct;
|
||||
|
8
src/misc/hard-limits.ts
Normal file
8
src/misc/hard-limits.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
// If you change DB_* values, you must also change the DB schema.
|
||||
|
||||
/**
|
||||
* Maximum note text length that can be stored in DB.
|
||||
* Surrogate pairs count as one
|
||||
*/
|
||||
export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
|
@@ -24,6 +24,11 @@ export class Emoji {
|
||||
})
|
||||
public host: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public category: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
})
|
||||
|
@@ -228,6 +228,7 @@ export class Note {
|
||||
|
||||
export type IMentionedRemoteUsers = {
|
||||
uri: string;
|
||||
url?: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}[];
|
||||
|
@@ -84,6 +84,19 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
|
||||
return groupBy((a, b) => f(a) === f(b), xs);
|
||||
}
|
||||
|
||||
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
|
||||
return collections.reduce((obj: Record<string, T[]>, item: T) => {
|
||||
const key = keySelector(item);
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
obj[key] = [];
|
||||
}
|
||||
|
||||
obj[key].push(item);
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two arrays by lexicographical order
|
||||
*/
|
||||
|
0
src/remote/activitypub/kernel/move/index.ts
Normal file
0
src/remote/activitypub/kernel/move/index.ts
Normal file
@@ -23,6 +23,7 @@ import { genId } from '../../../misc/gen-id';
|
||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
import { getApLock } from '../../../misc/app-lock';
|
||||
import { createMessage } from '../../../services/messages/create';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@@ -223,6 +224,13 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
if (actor.uri) updatePerson(actor.uri);
|
||||
}
|
||||
|
||||
if (note._misskey_talk && visibility === 'specified') {
|
||||
for (const recipient of visibleUsers) {
|
||||
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await post(actor, {
|
||||
createdAt: note.published ? new Date(note.published) : null,
|
||||
files,
|
||||
|
@@ -3,7 +3,7 @@ import * as promiseLimit from 'promise-limit';
|
||||
import config from '../../../config';
|
||||
import Resolver from '../resolver';
|
||||
import { resolveImage } from './image';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import { resolveNote, extractEmojis } from './note';
|
||||
@@ -150,13 +150,13 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||
createdAt: new Date(),
|
||||
lastFetchedAt: new Date(),
|
||||
name: person.name,
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
isLocked: !!person.manuallyApprovesFollowers,
|
||||
username: person.preferredUsername,
|
||||
usernameLower: person.preferredUsername.toLowerCase(),
|
||||
usernameLower: person.preferredUsername!.toLowerCase(),
|
||||
host,
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
featured: person.featured,
|
||||
featured: person.featured ? getApId(person.featured) : undefined,
|
||||
uri: person.id,
|
||||
tags,
|
||||
isBot,
|
||||
@@ -319,7 +319,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||
tags,
|
||||
isBot: object.type == 'Service',
|
||||
isCat: (person as any).isCat === true,
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
isLocked: !!person.manuallyApprovesFollowers,
|
||||
} as Partial<User>;
|
||||
|
||||
if (avatar) {
|
||||
|
@@ -12,10 +12,12 @@ import { Emoji } from '../../../models/entities/emoji';
|
||||
import { Poll } from '../../../models/entities/poll';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
|
||||
export default async function renderNote(note: Note, dive = true): Promise<any> {
|
||||
const promisedFiles: Promise<DriveFile[]> = note.fileIds.length > 0
|
||||
? DriveFiles.find({ id: In(note.fileIds) })
|
||||
: Promise.resolve([]);
|
||||
export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<any> {
|
||||
const getPromisedFiles = async (ids: string[]) => {
|
||||
if (!ids || ids.length === 0) return [];
|
||||
const items = await DriveFiles.find({ id: In(ids) });
|
||||
return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[];
|
||||
};
|
||||
|
||||
let inReplyTo;
|
||||
let inReplyToNote: Note | undefined;
|
||||
@@ -81,7 +83,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
|
||||
const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
|
||||
const mentionTags = mentionedUsers.map(u => renderMention(u));
|
||||
|
||||
const files = await promisedFiles;
|
||||
const files = await getPromisedFiles(note.fileIds);
|
||||
|
||||
let text = note.text;
|
||||
let poll: Poll | undefined;
|
||||
@@ -145,6 +147,10 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
|
||||
}))
|
||||
} : {};
|
||||
|
||||
const asTalk = isTalk ? {
|
||||
_misskey_talk: true
|
||||
} : {};
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note.id}`,
|
||||
type: 'Note',
|
||||
@@ -160,7 +166,8 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
|
||||
attachment: files.map(renderDocument),
|
||||
sensitive: note.cw != null || files.some(file => file.isSensitive),
|
||||
tag,
|
||||
...asPoll
|
||||
...asPoll,
|
||||
...asTalk
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -75,6 +75,7 @@ export interface INote extends IObject {
|
||||
type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video';
|
||||
_misskey_content?: string;
|
||||
_misskey_quote?: string;
|
||||
_misskey_talk: boolean;
|
||||
}
|
||||
|
||||
export interface IQuestion extends IObject {
|
||||
@@ -100,17 +101,22 @@ export const validActor = ['Person', 'Service'];
|
||||
|
||||
export interface IPerson extends IObject {
|
||||
type: 'Person';
|
||||
name: string;
|
||||
preferredUsername: string;
|
||||
manuallyApprovesFollowers: boolean;
|
||||
inbox: string;
|
||||
sharedInbox?: string;
|
||||
publicKey: any;
|
||||
followers: any;
|
||||
following: any;
|
||||
featured?: any;
|
||||
outbox: any;
|
||||
endpoints: any;
|
||||
name?: string;
|
||||
preferredUsername?: string;
|
||||
manuallyApprovesFollowers?: boolean;
|
||||
inbox?: string;
|
||||
sharedInbox?: string; // 後方互換性のため
|
||||
publicKey: {
|
||||
id: string;
|
||||
publicKeyPem: string;
|
||||
};
|
||||
followers?: string | ICollection | IOrderedCollection;
|
||||
following?: string | ICollection | IOrderedCollection;
|
||||
featured?: string | IOrderedCollection;
|
||||
outbox?: string | IOrderedCollection;
|
||||
endpoints?: {
|
||||
sharedInbox?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const isCollection = (object: IObject): object is ICollection =>
|
||||
|
@@ -40,18 +40,21 @@ function inbox(ctx: Router.RouterContext) {
|
||||
ctx.status = 202;
|
||||
}
|
||||
|
||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
|
||||
function isActivityPubReq(ctx: Router.RouterContext) {
|
||||
ctx.response.vary('Accept');
|
||||
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json');
|
||||
return ['application/activity+json', 'application/ld+json'].includes(accepted as string);
|
||||
const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
|
||||
return typeof accepted === 'string' && !accepted.match(/html/);
|
||||
}
|
||||
|
||||
export function setResponseType(ctx: Router.RouterContext) {
|
||||
const accpet = ctx.accepts('application/activity+json', 'application/ld+json');
|
||||
if (accpet === 'application/ld+json') {
|
||||
ctx.response.type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
|
||||
if (accept === LD_JSON) {
|
||||
ctx.response.type = LD_JSON;
|
||||
} else {
|
||||
ctx.response.type = 'application/activity+json; charset=utf-8';
|
||||
ctx.response.type = ACTIVITY_JSON;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,11 +2,15 @@ import { User } from '../../../models/entities/user';
|
||||
import { Mutings } from '../../../models';
|
||||
import { SelectQueryBuilder, Brackets } from 'typeorm';
|
||||
|
||||
export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User) {
|
||||
export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User, exclude?: User) {
|
||||
const mutingQuery = Mutings.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
||||
|
||||
if (exclude) {
|
||||
mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id });
|
||||
}
|
||||
|
||||
// 投稿の作者をミュートしていない かつ
|
||||
// 投稿の返信先の作者をミュートしていない かつ
|
||||
// 投稿の引用元の作者をミュートしていない
|
||||
|
@@ -5,6 +5,7 @@ import { Emojis } from '../../../../../models';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { insertModerationLog } from '../../../../../services/insert-moderation-log';
|
||||
import { ApiError } from '../../../error';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -25,20 +26,40 @@ export const meta = {
|
||||
validator: $.str.min(1)
|
||||
},
|
||||
|
||||
category: {
|
||||
validator: $.optional.str
|
||||
},
|
||||
|
||||
aliases: {
|
||||
validator: $.optional.arr($.str.min(1)),
|
||||
default: [] as string[]
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
emojiAlredyExists: {
|
||||
message: 'Emoji already exists.',
|
||||
code: 'EMOJI_ALREADY_EXISTS',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const type = await detectUrlMine(ps.url);
|
||||
|
||||
const exists = await Emojis.findOne({
|
||||
name: ps.name,
|
||||
host: null
|
||||
});
|
||||
|
||||
if (exists != null) throw new ApiError(meta.errors.emojiAlredyExists);
|
||||
|
||||
const emoji = await Emojis.save({
|
||||
id: genId(),
|
||||
updatedAt: new Date(),
|
||||
name: ps.name,
|
||||
category: ps.category,
|
||||
host: null,
|
||||
aliases: ps.aliases,
|
||||
url: ps.url,
|
||||
|
@@ -23,12 +23,19 @@ export const meta = {
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const emojis = await Emojis.find({
|
||||
host: toPunyNullable(ps.host)
|
||||
where: {
|
||||
host: toPunyNullable(ps.host)
|
||||
},
|
||||
order: {
|
||||
category: 'ASC',
|
||||
name: 'ASC'
|
||||
}
|
||||
});
|
||||
|
||||
return emojis.map(e => ({
|
||||
id: e.id,
|
||||
name: e.name,
|
||||
category: e.category,
|
||||
aliases: e.aliases,
|
||||
host: e.host,
|
||||
url: e.url
|
||||
|
@@ -4,6 +4,7 @@ import { ID } from '../../../../../misc/cafy-id';
|
||||
import { Emojis } from '../../../../../models';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { insertModerationLog } from '../../../../../services/insert-moderation-log';
|
||||
import { ApiError } from '../../../error';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -19,13 +20,21 @@ export const meta = {
|
||||
id: {
|
||||
validator: $.type(ID)
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
message: 'No such emoji.',
|
||||
code: 'NO_SUCH_EMOJI',
|
||||
id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const emoji = await Emojis.findOne(ps.id);
|
||||
|
||||
if (emoji == null) throw new Error('emoji not found');
|
||||
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
|
||||
|
||||
await Emojis.delete(emoji.id);
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { detectUrlMine } from '../../../../../misc/detect-url-mine';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import { Emojis } from '../../../../../models';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { ApiError } from '../../../error';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -24,6 +25,10 @@ export const meta = {
|
||||
validator: $.str
|
||||
},
|
||||
|
||||
category: {
|
||||
validator: $.optional.str
|
||||
},
|
||||
|
||||
url: {
|
||||
validator: $.str
|
||||
},
|
||||
@@ -31,19 +36,28 @@ export const meta = {
|
||||
aliases: {
|
||||
validator: $.arr($.str)
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
message: 'No such emoji.',
|
||||
code: 'NO_SUCH_EMOJI',
|
||||
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const emoji = await Emojis.findOne(ps.id);
|
||||
|
||||
if (emoji == null) throw new Error('emoji not found');
|
||||
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
|
||||
|
||||
const type = await detectUrlMine(ps.url);
|
||||
|
||||
await Emojis.update(emoji.id, {
|
||||
updatedAt: new Date(),
|
||||
name: ps.name,
|
||||
category: ps.category,
|
||||
aliases: ps.aliases,
|
||||
url: ps.url,
|
||||
type,
|
||||
|
@@ -16,16 +16,19 @@ export const meta = {
|
||||
params: {}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const code = rndstr({ length: 5, chars: '0-9' });
|
||||
export default define(meta, async () => {
|
||||
const code = rndstr({
|
||||
length: 8,
|
||||
chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns)
|
||||
});
|
||||
|
||||
await RegistrationTickets.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
code: code
|
||||
code,
|
||||
});
|
||||
|
||||
return {
|
||||
code: code
|
||||
code,
|
||||
};
|
||||
});
|
||||
|
@@ -3,6 +3,7 @@ import define from '../../define';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { Meta } from '../../../../models/entities/meta';
|
||||
import { insertModerationLog } from '../../../../services/insert-moderation-log';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -121,7 +122,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
maxNoteTextLength: {
|
||||
validator: $.optional.num.min(0),
|
||||
validator: $.optional.num.min(0).max(DB_MAX_NOTE_TEXT_LENGTH),
|
||||
desc: {
|
||||
'ja-JP': '投稿の最大文字数'
|
||||
}
|
||||
|
@@ -1,17 +1,12 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import { publishMainStream, publishGroupMessagingStream } from '../../../../../services/stream';
|
||||
import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream';
|
||||
import pushSw from '../../../../../services/push-notification';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getUser } from '../../../common/getters';
|
||||
import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { User } from '../../../../../models/entities/user';
|
||||
import { UserGroup } from '../../../../../models/entities/user-group';
|
||||
import { Not } from 'typeorm';
|
||||
import { createMessage } from '../../../../../services/messages/create';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -147,68 +142,5 @@ export default define(meta, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
const message = await MessagingMessages.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
fileId: file ? file.id : null,
|
||||
recipientId: recipientUser ? recipientUser.id : null,
|
||||
groupId: recipientGroup ? recipientGroup.id : null,
|
||||
text: ps.text ? ps.text.trim() : null,
|
||||
userId: user.id,
|
||||
isRead: false,
|
||||
reads: [] as any[]
|
||||
} as MessagingMessage);
|
||||
|
||||
const messageObj = await MessagingMessages.pack(message);
|
||||
|
||||
if (recipientUser) {
|
||||
// 自分のストリーム
|
||||
publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
|
||||
publishMessagingIndexStream(message.userId, 'message', messageObj);
|
||||
publishMainStream(message.userId, 'messagingMessage', messageObj);
|
||||
|
||||
// 相手のストリーム
|
||||
publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
|
||||
publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
|
||||
publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
// グループのストリーム
|
||||
publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
|
||||
for (const joining of joinings) {
|
||||
publishMessagingIndexStream(joining.userId, 'message', messageObj);
|
||||
publishMainStream(joining.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
|
||||
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
const freshMessage = await MessagingMessages.findOne(message.id);
|
||||
if (freshMessage == null) return; // メッセージが削除されている場合もある
|
||||
|
||||
if (recipientUser) {
|
||||
if (freshMessage.isRead) return; // 既読
|
||||
|
||||
//#region ただしミュートされているなら発行しない
|
||||
const mute = await Mutings.find({
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
if (mute.map(m => m.muteeId).includes(user.id)) return;
|
||||
//#endregion
|
||||
|
||||
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
pushSw(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
pushSw(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return messageObj;
|
||||
return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
|
||||
});
|
||||
|
@@ -7,6 +7,7 @@ import * as pkg from '../../../../package.json';
|
||||
import { Emojis } from '../../../models';
|
||||
import { getConnection } from 'typeorm';
|
||||
import redis from '../../../db/redis';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../misc/hard-limits';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@@ -95,7 +96,19 @@ export const meta = {
|
||||
export default define(meta, async (ps, me) => {
|
||||
const instance = await fetchMeta(true);
|
||||
|
||||
const emojis = await Emojis.find({ where: { host: null }, cache: { id: 'meta_emojis', milliseconds: 3600000 } }); // 1 hour
|
||||
const emojis = await Emojis.find({
|
||||
where: {
|
||||
host: null
|
||||
},
|
||||
order: {
|
||||
category: 'ASC',
|
||||
name: 'ASC'
|
||||
},
|
||||
cache: {
|
||||
id: 'meta_emojis',
|
||||
milliseconds: 3600000 // 1 hour
|
||||
}
|
||||
});
|
||||
|
||||
const response: any = {
|
||||
maintainerName: instance.maintainerName,
|
||||
@@ -138,11 +151,12 @@ export default define(meta, async (ps, me) => {
|
||||
bannerUrl: instance.bannerUrl,
|
||||
errorImageUrl: instance.errorImageUrl,
|
||||
iconUrl: instance.iconUrl,
|
||||
maxNoteTextLength: instance.maxNoteTextLength,
|
||||
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
|
||||
emojis: emojis.map(e => ({
|
||||
id: e.id,
|
||||
aliases: e.aliases,
|
||||
name: e.name,
|
||||
category: e.category,
|
||||
url: e.url,
|
||||
})),
|
||||
enableEmail: instance.enableEmail,
|
||||
|
@@ -10,8 +10,9 @@ import { User } from '../../../../models/entities/user';
|
||||
import { Users, DriveFiles, Notes } from '../../../../models';
|
||||
import { DriveFile } from '../../../../models/entities/drive-file';
|
||||
import { Note } from '../../../../models/entities/note';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
|
||||
|
||||
let maxNoteTextLength = 1000;
|
||||
let maxNoteTextLength = 500;
|
||||
|
||||
setInterval(() => {
|
||||
fetchMeta().then(m => {
|
||||
@@ -55,7 +56,9 @@ export const meta = {
|
||||
|
||||
text: {
|
||||
validator: $.optional.nullable.str.pipe(text =>
|
||||
length(text.trim()) <= maxNoteTextLength && text.trim() != ''
|
||||
text.trim() != ''
|
||||
&& length(text.trim()) <= maxNoteTextLength
|
||||
&& Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit
|
||||
),
|
||||
default: null as any,
|
||||
desc: {
|
||||
|
@@ -134,7 +134,7 @@ export default define(meta, async (ps, me) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
if (me) generateVisibilityQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me, user);
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -46,9 +46,7 @@ export default async function(ctx: Koa.BaseContext) {
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.name, { suffix: '-web' })}`));
|
||||
ctx.body = InternalStorage.read(key);
|
||||
} else {
|
||||
if ('download' in ctx.query) {
|
||||
ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`));
|
||||
}
|
||||
ctx.set('Content-Disposition', contentDisposition('inline', `${file.name}`));
|
||||
|
||||
const readable = InternalStorage.read(file.accessKey!);
|
||||
readable.on('error', commonReadableHandlerGenerator(ctx));
|
||||
|
@@ -42,4 +42,17 @@ export default class TestChart extends Chart<TestLog> {
|
||||
foo: update
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
public async decrement() {
|
||||
const update: Obj = {};
|
||||
|
||||
update.total = -1;
|
||||
update.dec = 1;
|
||||
this.total--;
|
||||
|
||||
await this.inc({
|
||||
foo: update
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -122,7 +122,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
|
||||
for (const [k, v] of Object.entries(columns)) {
|
||||
if (v > 0) query[k] = () => `"${k}" + ${v}`;
|
||||
if (v < 0) query[k] = () => `"${k}" - ${v}`;
|
||||
if (v < 0) query[k] = () => `"${k}" - ${Math.abs(v)}`;
|
||||
}
|
||||
|
||||
return query;
|
||||
|
105
src/services/messages/create.ts
Normal file
105
src/services/messages/create.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { User } from '../../models/entities/user';
|
||||
import { UserGroup } from '../../models/entities/user-group';
|
||||
import { DriveFile } from '../../models/entities/drive-file';
|
||||
import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '../../models';
|
||||
import { genId } from '../../misc/gen-id';
|
||||
import { MessagingMessage } from '../../models/entities/messaging-message';
|
||||
import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '../stream';
|
||||
import pushNotification from '../push-notification';
|
||||
import { Not } from 'typeorm';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import renderNote from '../../remote/activitypub/renderer/note';
|
||||
import renderCreate from '../../remote/activitypub/renderer/create';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../queue';
|
||||
|
||||
export async function createMessage(user: User, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | undefined, file: DriveFile | null) {
|
||||
const message = await MessagingMessages.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
fileId: file ? file.id : null,
|
||||
recipientId: recipientUser ? recipientUser.id : null,
|
||||
groupId: recipientGroup ? recipientGroup.id : null,
|
||||
text: text ? text.trim() : null,
|
||||
userId: user.id,
|
||||
isRead: false,
|
||||
reads: [] as any[]
|
||||
} as MessagingMessage);
|
||||
|
||||
const messageObj = await MessagingMessages.pack(message);
|
||||
|
||||
if (recipientUser) {
|
||||
if (Users.isLocalUser(user)) {
|
||||
// 自分のストリーム
|
||||
publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
|
||||
publishMessagingIndexStream(message.userId, 'message', messageObj);
|
||||
publishMainStream(message.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
|
||||
if (Users.isLocalUser(recipientUser)) {
|
||||
// 相手のストリーム
|
||||
publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
|
||||
publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
|
||||
publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
|
||||
}
|
||||
} else if (recipientGroup) {
|
||||
// グループのストリーム
|
||||
publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
|
||||
for (const joining of joinings) {
|
||||
publishMessagingIndexStream(joining.userId, 'message', messageObj);
|
||||
publishMainStream(joining.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
|
||||
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
const freshMessage = await MessagingMessages.findOne(message.id);
|
||||
if (freshMessage == null) return; // メッセージが削除されている場合もある
|
||||
|
||||
if (recipientUser && Users.isLocalUser(recipientUser)) {
|
||||
if (freshMessage.isRead) return; // 既読
|
||||
|
||||
//#region ただしミュートされているなら発行しない
|
||||
const mute = await Mutings.find({
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
if (mute.map(m => m.muteeId).includes(user.id)) return;
|
||||
//#endregion
|
||||
|
||||
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
pushNotification(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
if (recipientUser && Users.isLocalUser(user) && Users.isRemoteUser(recipientUser)) {
|
||||
const note = {
|
||||
id: message.id,
|
||||
createdAt: message.createdAt,
|
||||
fileIds: message.fileId ? [ message.fileId ] : [],
|
||||
text: message.text,
|
||||
userId: message.userId,
|
||||
visibility: 'specified',
|
||||
mentions: [ recipientUser ].map(u => u.id),
|
||||
mentionedRemoteUsers: JSON.stringify([ recipientUser ].map(u => ({
|
||||
uri: u.uri,
|
||||
username: u.username,
|
||||
host: u.host
|
||||
}))),
|
||||
} as Note;
|
||||
|
||||
const activity = renderActivity(renderCreate(await renderNote(note, false, true), note));
|
||||
|
||||
deliver(user, activity, recipientUser.inbox);
|
||||
}
|
||||
return messageObj;
|
||||
}
|
@@ -16,11 +16,11 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||
import extractMentions from '../../misc/extract-mentions';
|
||||
import extractEmojis from '../../misc/extract-emojis';
|
||||
import extractHashtags from '../../misc/extract-hashtags';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
|
||||
import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models';
|
||||
import { DriveFile } from '../../models/entities/drive-file';
|
||||
import { App } from '../../models/entities/app';
|
||||
import { Not, getConnection } from 'typeorm';
|
||||
import { Not, getConnection, In } from 'typeorm';
|
||||
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
|
||||
import { genId } from '../../misc/gen-id';
|
||||
import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart';
|
||||
@@ -383,11 +383,17 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
|
||||
// Append mentions data
|
||||
if (mentionedUsers.length > 0) {
|
||||
insert.mentions = mentionedUsers.map(u => u.id);
|
||||
insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => ({
|
||||
uri: (u as IRemoteUser).uri,
|
||||
username: u.username,
|
||||
host: u.host
|
||||
})));
|
||||
const profiles = await UserProfiles.find({ userId: In(insert.mentions) });
|
||||
insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => {
|
||||
const profile = profiles.find(p => p.userId == u.id);
|
||||
const url = profile != null ? profile.url : null;
|
||||
return {
|
||||
uri: u.uri,
|
||||
url: url == null ? undefined : url,
|
||||
username: u.username,
|
||||
host: u.host
|
||||
} as IMentionedRemoteUsers[0];
|
||||
}));
|
||||
}
|
||||
|
||||
// 投稿を作成
|
||||
|
@@ -29,7 +29,9 @@ export default async function(userId: string, type: string, body?: any) {
|
||||
|
||||
push.sendNotification(pushSubscription, JSON.stringify({
|
||||
type, body
|
||||
})).catch((err: any) => {
|
||||
}), {
|
||||
proxy: config.proxy
|
||||
}).catch((err: any) => {
|
||||
//swLogger.info(err.statusCode);
|
||||
//swLogger.info(err.headers);
|
||||
//swLogger.info(err.body);
|
||||
|
@@ -105,6 +105,29 @@ describe('Chart', () => {
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('Can updates (dec)', async(async () => {
|
||||
await testChart.decrement();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3);
|
||||
const chartDays = await testChart.getChart('day', 3);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [1, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [-1, 0, 0]
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [1, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [-1, 0, 0]
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('Empty chart', async(async () => {
|
||||
const chartHours = await testChart.getChart('hour', 3);
|
||||
|
@@ -6,7 +6,7 @@ import * as fs from 'fs';
|
||||
import * as webpack from 'webpack';
|
||||
import chalk from 'chalk';
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
//const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
||||
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
@@ -20,6 +20,7 @@ class WebpackOnBuildPlugin {
|
||||
}
|
||||
|
||||
const isProduction = process.env.NODE_ENV == 'production';
|
||||
const useHardSource = process.env.MISSKEY_USE_HARD_SOURCE;
|
||||
|
||||
const constants = require('./src/const.json');
|
||||
|
||||
@@ -113,7 +114,7 @@ module.exports = {
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
//new HardSourceWebpackPlugin(),
|
||||
...(useHardSource ? [new HardSourceWebpackPlugin()] : []),
|
||||
new ProgressBarPlugin({
|
||||
format: chalk` {cyan.bold yes we can} {bold [}:bar{bold ]} {green.bold :percent} {gray (:current/:total)} :elapseds`,
|
||||
clear: false
|
||||
|
Reference in New Issue
Block a user