Compare commits
174 Commits
Author | SHA1 | Date | |
---|---|---|---|
3b974428fc | |||
580191fb17 | |||
be0cb88b6c | |||
95c4e4497e | |||
2ec445f83e | |||
51b915428e | |||
1395cf89ce | |||
2a8f984db7 | |||
decf2d396f | |||
f7964da899 | |||
c8607ff7b6 | |||
e9f8897fe2 | |||
e0b107a3a0 | |||
1d3e6a7197 | |||
1c93fcb1c4 | |||
e3389e7899 | |||
454632d785 | |||
c9bca7dc85 | |||
710ba526fa | |||
aa47b6732d | |||
20f83420ca | |||
d09a68ef11 | |||
b545be5799 | |||
4fc377584f | |||
a5f09c90dd | |||
d059d7f972 | |||
c03e2dfbc0 | |||
45c5e7b967 | |||
c81a94ff75 | |||
acc6f54557 | |||
8025b121af | |||
78ec06bda3 | |||
6ef83d9c59 | |||
fca4ceef21 | |||
00f979f0e6 | |||
556677be7a | |||
624fd093f2 | |||
2ee438dece | |||
534de24406 | |||
e88ce1746d | |||
b8aad35009 | |||
47bd485a39 | |||
ad869d7469 | |||
d15cce5337 | |||
37daff6d61 | |||
5417e40f59 | |||
0fed33bfdb | |||
5dddc75d09 | |||
081578c604 | |||
6c47bf5b76 | |||
9e85291cd3 | |||
7f77517fc8 | |||
b2f288dcac | |||
52b59e9d7b | |||
80c74b1fa7 | |||
91811ea500 | |||
57150fd910 | |||
cddbbdf5d0 | |||
423dc2349b | |||
0556a2a2da | |||
65d943e42a | |||
3bcb344ecb | |||
82d721d60b | |||
48dc56e834 | |||
2c33bd6e31 | |||
b6524616bc | |||
7e2b70f912 | |||
4f071a66b6 | |||
39f2303429 | |||
cacf072027 | |||
6ab1fdfe1a | |||
6e5c93f926 | |||
1670737075 | |||
fee235c4e4 | |||
7a39d489f2 | |||
7c634218d1 | |||
2704c5be73 | |||
489b51ba9f | |||
21807c29f1 | |||
3bc62fe3eb | |||
ba0e3c4a5f | |||
9ec1fb5e37 | |||
d708409462 | |||
07d05d4f86 | |||
bbdb2ebb40 | |||
f7908ba098 | |||
f2fda3075e | |||
1338a68979 | |||
e7da505fb3 | |||
5a9228372f | |||
c4a6ba9097 | |||
d5871b408b | |||
7b3338e373 | |||
d18ee12d2f | |||
ca9cc97940 | |||
a70070ac7d | |||
069d99b320 | |||
37d350dcad | |||
8653e09b59 | |||
7cd2d59576 | |||
a0839de38f | |||
b7c5c71c6f | |||
adab0adbdd | |||
2faa58928f | |||
ffb80efe21 | |||
6f959218ef | |||
be1125dcb9 | |||
9ab34c2301 | |||
0166d81d9e | |||
0b26efbd2f | |||
2cbaedf946 | |||
b66924fbe8 | |||
8c91148954 | |||
be0eff3dda | |||
85903ac9c6 | |||
dbdd778dc7 | |||
fc50dfd8d5 | |||
f444e132ee | |||
68f562c323 | |||
820ea69613 | |||
6f4b3853a1 | |||
a706ad0e80 | |||
820116affc | |||
52650342be | |||
85ddabdc65 | |||
0730cc4fa4 | |||
17b6ab0ef0 | |||
4e208b85bb | |||
00f8b29f6d | |||
9cf0fcadb1 | |||
c595efeead | |||
b56c6793a1 | |||
ebceffba1e | |||
3ae42d9b85 | |||
796237b3c6 | |||
cb7a97ee4c | |||
0cf758b6d1 | |||
d28fca320e | |||
8bd17703c3 | |||
a78eebc43f | |||
79fb5246df | |||
458b8c78dc | |||
64e0cbd6fc | |||
7fe937026b | |||
656cec65b9 | |||
8045bbff1c | |||
c1a7a21746 | |||
f3ee63fcbe | |||
7645c212a3 | |||
8b38e2ea58 | |||
c9eb6a8919 | |||
9a41fd4734 | |||
70d96ee076 | |||
3b6fb3959b | |||
484d705320 | |||
786031be66 | |||
bc0027ce43 | |||
3e7c6d9bdc | |||
5463e3e55e | |||
84a880086e | |||
89419b7136 | |||
9106ec74f7 | |||
ebf9a0921d | |||
c237f49016 | |||
709290d2da | |||
eb3180f3b6 | |||
681997509c | |||
79ff5888fd | |||
9ee9cf8d81 | |||
ee3c0f6f18 | |||
9dd463bff4 | |||
df297d0031 | |||
d18d1cb958 | |||
5bc0570888 |
@ -108,13 +108,5 @@ autoAdmin: true
|
||||
# port: 9200
|
||||
# pass: null
|
||||
|
||||
# ServiceWorker
|
||||
#sw:
|
||||
# # Public key of VAPID
|
||||
# public_key: example-sw-public-key
|
||||
#
|
||||
# # Private key of VAPID
|
||||
# private_key: example-sw-private-key
|
||||
|
||||
# Clustering
|
||||
#clusterLimit: 1
|
||||
|
65
CHANGELOG.md
65
CHANGELOG.md
@ -1,6 +1,71 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
10.66.1
|
||||
-------
|
||||
* ActivityPubのsharedInboxに関して修正
|
||||
* MFMでのカッコの判定を改善
|
||||
* バグ修正
|
||||
|
||||
10.66.0
|
||||
-------
|
||||
* ユーザーごとのRSSフィードを提供するように
|
||||
* リストのユーザーがすべて表示できない問題を修正
|
||||
* デザインの調整
|
||||
* パフォーマンスの改善
|
||||
|
||||
10.65.0
|
||||
-------
|
||||
* 検索で投稿やユーザーのURLを入力した際にそれをフェッチして表示するように
|
||||
* リストのリネームと削除をできるように
|
||||
* リストからユーザーを削除できるように
|
||||
* リモートの絵文字を更新するように
|
||||
* ActivityPubのための絵文字エンドポイントを実装
|
||||
* 管理者がドライブのファイルのNSFWを設定できるように
|
||||
* ServiceWorkerの設定を管理者ページで行えるように
|
||||
* メンションの判定を改善
|
||||
* リモートの投稿を引用した際にオリジナルのURLを挿入するように
|
||||
* クライアントのパフォーマンス改善
|
||||
* CWの内容がタブタイトルに表示されるのを修正
|
||||
* アカウントを作成したときにログイン状態にならない問題を修正
|
||||
* 時計の針にテーマカラーが適用されていなかったのを修正
|
||||
* 一部の日時の表示が日本語で表示されていたのを修正
|
||||
* プロフィールの写真欄に画像以外のファイルが含まれる問題を修正
|
||||
* メンションが含まれる投稿に返信する際、フォームに予めそれらのメンションがセットされた状態にならない問題を修正
|
||||
* デッキのTLにUIの動きを減らすオプションが適用されていなかったのを修正
|
||||
* ログイン画面のタイムラインに隠した投稿が表示される問題を修正
|
||||
* サジェストが複数開いてしまう問題を修正
|
||||
* APから来たタグに登録時の長さ制限が適用されていなかったのを修正
|
||||
|
||||
10.64.2
|
||||
-------
|
||||
* UIの動きを減らすオプションが一部のアニメーションに適用されなかったのを修正
|
||||
|
||||
10.64.1
|
||||
-------
|
||||
* レートリミットの調整
|
||||
* アニメーションの調整
|
||||
|
||||
10.64.0
|
||||
-------
|
||||
* いくつかのアニメーションを追加
|
||||
* OGP向けにインスタンスのバナー画像を提供するように
|
||||
* 管理者ページでドライブのファイルを表示できるように
|
||||
* ユーザビリティの強化
|
||||
* バグ修正
|
||||
|
||||
10.63.1
|
||||
-------
|
||||
* メンションの表示を改善
|
||||
* バグ修正
|
||||
|
||||
10.63.0
|
||||
-------
|
||||
* ActivityPubのユーザーフィールドをユーザーページに表示
|
||||
* 404ページの実装
|
||||
* パフォーマンスの向上
|
||||
* バグ修正
|
||||
|
||||
10.62.2
|
||||
-------
|
||||
* バグ修正
|
||||
|
@ -25,3 +25,16 @@ Misskey uses [vue-i18n](https://github.com/kazupon/vue-i18n).
|
||||
## Continuous integration
|
||||
Misskey uses CircleCI for automated test.
|
||||
Configuration files are located in `/.circleci`.
|
||||
|
||||
## Glossary
|
||||
### AP
|
||||
Stands for _**A**ctivity**P**ub_.
|
||||
|
||||
### MFM
|
||||
Stands for _**M**isskey **F**lavored **M**arkdown_.
|
||||
|
||||
### Mk
|
||||
Stands for _**M**iss**k**ey_.
|
||||
|
||||
### SW
|
||||
Stands for _**S**ervice**W**orker_.
|
||||
|
13
PULL_REQUEST_TEMPLATE.md
Normal file
13
PULL_REQUEST_TEMPLATE.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Summary
|
||||
|
||||
<!--
|
||||
-
|
||||
- * Please describe your changes here *
|
||||
-
|
||||
- If you are going to resolve some issue, please add this context.
|
||||
- Resolve #ISSUE_NUMBER
|
||||
-
|
||||
- If you are going to fix some bug issue, please add this context.
|
||||
- Fix #ISSUE_NUMBER
|
||||
-
|
||||
-->
|
16
README.md
16
README.md
@ -3,9 +3,9 @@
|
||||
[](https://misskey.xyz/)
|
||||
================================================================
|
||||
|
||||
[](https://circleci.com/gh/syuilo/misskey)
|
||||
[![][dependencies-badge]][dependencies-link]
|
||||
[](http://makeapullrequest.com)
|
||||
[](https://circleci.com/gh/syuilo/misskey)
|
||||
[](https://david-dm.org/syuilo/misskey)
|
||||
[](http://makeapullrequest.com)
|
||||
|
||||
**Sophisticated microblogging platform, evolving forever.**
|
||||
|
||||
@ -77,7 +77,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13376668/71f3cf87ec6c4393a44b1b9df5ee3d12/1?token-time=2145916800&token-hash=7pSmWqgMfMSJHVIEcNsuuQoKeU3TRluew5p0EGTzWA4%3D" alt="Arctic"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Xeltica"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=ybYtxfpte1b-rGg6Zecpys2ZdZDtwR_UNJHQjt-3eoU%3D" alt="Xeltica"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=I8lJVM8LeW6TSo5W6uIIRZ42cw83zp1wK_FsbzY0mcQ%3D" alt="mydarkstar"></td>
|
||||
@ -86,7 +86,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=13376668">Arctic</a></td>
|
||||
<td><a href="https://www.patreon.com/negao">negao</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
<td><a href="https://www.patreon.com/AxellaMC">Xeltica</a></td>
|
||||
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
@ -118,7 +118,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Thu, 06 Dec 2018 14:22:05 UTC
|
||||
**Last updated:** Sun, 16 Dec 2018 18:32:06 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
@ -130,9 +130,7 @@ Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE).
|
||||
[![][agpl-3.0-badge]][AGPL-3.0]
|
||||
|
||||
[agpl-3.0]: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
[agpl-3.0-badge]: https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=flat-square
|
||||
[dependencies-link]: https://david-dm.org/syuilo/misskey
|
||||
[dependencies-badge]: https://img.shields.io/david/syuilo/misskey.svg?style=flat-square
|
||||
[agpl-3.0-badge]: https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=for-the-badge
|
||||
|
||||
[backer-url]: #backers
|
||||
[backer-badge]: https://opencollective.com/misskey/backers/badge.svg
|
||||
|
70
docs/examples/misskey.nginx
Normal file
70
docs/examples/misskey.nginx
Normal file
@ -0,0 +1,70 @@
|
||||
# Sample nginx configuration for Misskey
|
||||
#
|
||||
# 1. Replace example.tld to your domain
|
||||
# 2. Copy to /etc/nginx/sites-available/ and then symlink from /etc/nginx/sites-ebabled/
|
||||
# or copy to /etc/nginx/conf.d/
|
||||
|
||||
# For WebSocket
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache1:16m max_size=1g inactive=720m use_temp_path=off;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name example.tld;
|
||||
|
||||
# For SSL domain validation
|
||||
root /var/www/html;
|
||||
location /.well-known/acme-challenge/ { allow all; }
|
||||
location /.well-known/pki-validation/ { allow all; }
|
||||
location / { return 301 https://$server_name$request_uri; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 http2;
|
||||
listen [::]:443 http2;
|
||||
server_name example.tld;
|
||||
ssl on;
|
||||
ssl_session_cache shared:ssl_session_cache:10m;
|
||||
|
||||
# To use Let's Encrypt certificate
|
||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||
|
||||
# To use Debian/Ubuntu's self-signed certificate (For testing or before issuing a certificate)
|
||||
#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||
#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||
|
||||
# SSL protocol settings
|
||||
ssl_protocols TLSv1 TLSv1.2;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES128-SHA;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Change to your upload limit
|
||||
client_max_body_size 80m;
|
||||
|
||||
# Proxy to Node
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_http_version 1.1;
|
||||
proxy_redirect off;
|
||||
|
||||
# For WebSocket
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
# Cache settings
|
||||
proxy_cache cache1;
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_use_stale updating;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
}
|
||||
}
|
@ -47,16 +47,6 @@ As root:
|
||||
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
|
||||
5. `npm install` Install misskey dependencies.
|
||||
|
||||
*(optional)* Generate VAPID keys
|
||||
----------------------------------------------------------------
|
||||
If you want to enable ServiceWorker, you need to generate VAPID keys:
|
||||
Unless you have set your global node_modules location elsewhere, you need to run this as root.
|
||||
|
||||
``` shell
|
||||
npm install web-push -g
|
||||
web-push generate-vapid-keys
|
||||
```
|
||||
|
||||
*5.* Configure Misskey
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
||||
|
@ -47,16 +47,6 @@ En mode root :
|
||||
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Télécharge la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
|
||||
5. `npm install` Installez les dépendances de misskey.
|
||||
|
||||
*(optionnel)* Génération des clés VAPID
|
||||
----------------------------------------------------------------
|
||||
Si vous désirez activer ServiceWorker, vous devez générer les clés VAPID :
|
||||
Unless you have set your global node_modules location elsewhere, vous devez lancer ceci en mode root.
|
||||
|
||||
``` shell
|
||||
npm install web-push -g
|
||||
web-push generate-vapid-keys
|
||||
```
|
||||
|
||||
*5.* Création du fichier de configuration
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le `default.yml`.
|
||||
|
@ -53,15 +53,6 @@ adduser --disabled-password --disabled-login misskey
|
||||
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
|
||||
5. `npm install` Misskeyの依存パッケージをインストール
|
||||
|
||||
*(オプション)* VAPIDキーペアの生成
|
||||
----------------------------------------------------------------
|
||||
ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります:
|
||||
|
||||
``` shell
|
||||
npm install web-push -g
|
||||
web-push generate-vapid-keys
|
||||
```
|
||||
|
||||
*5.* 設定ファイルを作成する
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。
|
||||
|
52
gulpfile.ts
52
gulpfile.ts
@ -11,14 +11,12 @@ import tslint from 'gulp-tslint';
|
||||
const cssnano = require('gulp-cssnano');
|
||||
const stylus = require('gulp-stylus');
|
||||
import * as uglifyComposer from 'gulp-uglify/composer';
|
||||
import pug = require('gulp-pug');
|
||||
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';
|
||||
import * as htmlmin from 'gulp-htmlmin';
|
||||
const uglifyes = require('uglify-es');
|
||||
|
||||
const locales = require('./locales');
|
||||
@ -34,8 +32,6 @@ if (isDebug) {
|
||||
console.warn(chalk.yellow.bold(' built script will not be compressed.'));
|
||||
}
|
||||
|
||||
const constants = require('./src/const.json');
|
||||
|
||||
gulp.task('build', [
|
||||
'build:ts',
|
||||
'build:copy',
|
||||
@ -109,7 +105,7 @@ gulp.task('default', ['build']);
|
||||
gulp.task('build:client', [
|
||||
'build:ts',
|
||||
'build:client:script',
|
||||
'build:client:pug',
|
||||
'build:client:styles',
|
||||
'copy:client'
|
||||
]);
|
||||
|
||||
@ -148,52 +144,6 @@ gulp.task('copy:client', [
|
||||
.pipe(gulp.dest('./built/client/assets/'))
|
||||
);
|
||||
|
||||
gulp.task('build:client:pug', [
|
||||
'copy:client',
|
||||
'build:client:script',
|
||||
'build:client:styles'
|
||||
], () =>
|
||||
gulp.src('./src/client/app/base.pug')
|
||||
.pipe(pug({
|
||||
locals: {
|
||||
themeColor: constants.themeColor
|
||||
}
|
||||
}))
|
||||
.pipe(htmlmin({
|
||||
// 真理値属性の簡略化 e.g.
|
||||
// <input value="foo" readonly="readonly"> to
|
||||
// <input value="foo" readonly>
|
||||
collapseBooleanAttributes: true,
|
||||
|
||||
// テキストの一部かもしれない空白も削除する e.g.
|
||||
// <div> <p> foo </p> </div> to
|
||||
// <div><p>foo</p></div>
|
||||
collapseWhitespace: true,
|
||||
|
||||
// タグ間の改行を保持する
|
||||
preserveLineBreaks: true,
|
||||
|
||||
// (できる場合は)属性のクォーテーション削除する e.g.
|
||||
// <p class="foo-bar" id="moo" title="blah blah">foo</p> to
|
||||
// <p class=foo-bar id=moo title="blah blah">foo</p>
|
||||
removeAttributeQuotes: true,
|
||||
|
||||
// 省略可能なタグを省略する e.g.
|
||||
// <html><p>yo</p></html> ro
|
||||
// <p>yo</p>
|
||||
removeOptionalTags: true,
|
||||
|
||||
// 属性の値がデフォルトと同じなら省略する e.g.
|
||||
// <input type="text"> to
|
||||
// <input>
|
||||
removeRedundantAttributes: true,
|
||||
|
||||
// CSSも圧縮する
|
||||
minifyCSS: true
|
||||
}))
|
||||
.pipe(gulp.dest('./built/client/app/'))
|
||||
);
|
||||
|
||||
gulp.task('locales', () =>
|
||||
gulp.src('./locales/*.yml')
|
||||
.pipe(yaml({ schema: 'DEFAULT_SAFE_SCHEMA' }))
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "Ich bevorzuge Sushi anstelle von Pudding"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "Verifizierter Benutzer"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Hashtags"
|
||||
dev: "Fehler beim Erstellen der Applikation. Bitte versuche es erneut."
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "I prefer sushi rather than pudding"
|
||||
show-reversi-board-labels: "Show row and column labels in Reversi"
|
||||
use-white-black-reversi-stones: "Use white-black stone in reversi"
|
||||
use-contrast-reversi-stones: "Make the stone color clear in reversi"
|
||||
verified-user: "Verified account"
|
||||
disable-animated-mfm: "Disable animated texts in a post"
|
||||
suggest-recent-hashtags: "Suggest recently used hashtags within the post composition area"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Hashtags"
|
||||
dev: "Failed to create the application. Please try again."
|
||||
ai-chan-kawaii: "Ai-chan kawaii!"
|
||||
you: "You"
|
||||
auth/views/form.vue:
|
||||
share-access: "Would you allow <i>{name}</i> to access your account?"
|
||||
permission-ask: "This application requires the following permissions:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "Requests"
|
||||
network-time: "Response time"
|
||||
network-usage: "Traffic"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "Sort"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "Origin"
|
||||
combined: "Local + Remote"
|
||||
local: "Local"
|
||||
remote: "Remote"
|
||||
delete: "Delete"
|
||||
deleted: "Deleted successfully"
|
||||
admin/views/users.vue:
|
||||
operation: "Operations"
|
||||
username-or-userid: "Username or user ID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "Prefiero sushi a pudín"
|
||||
show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi"
|
||||
verified-user: "Cuenta verificada"
|
||||
disable-animated-mfm: "Desactivar texto animado en una publicación"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Etiquetas"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "La aplicación requiere los siguientes permisos:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -47,11 +47,11 @@ common:
|
||||
seconds_ago: "Il y a {} seconde·s"
|
||||
minutes_ago: "Il y a {} min"
|
||||
hours_ago: "Il y a {} h"
|
||||
days_ago: "Il y a {} jours"
|
||||
weeks_ago: "Il y a {} semaines·s"
|
||||
days_ago: "Il y a {} j"
|
||||
weeks_ago: "Il y a {} semaines"
|
||||
months_ago: "Il y a {} mois"
|
||||
years_ago: "Il y a {} an·s"
|
||||
month-and-day: "{day}/{month}"
|
||||
month-and-day: "{day}-{month}"
|
||||
trash: "Corbeille"
|
||||
drive: "Drive"
|
||||
messaging: "Conversations"
|
||||
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
|
||||
show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi"
|
||||
use-white-black-reversi-stones: "Jouer avec des pions noirs et blancs sur Reversi"
|
||||
use-contrast-reversi-stones: "Icône avec contraste sur Reversi"
|
||||
verified-user: "Compte vérifié"
|
||||
disable-animated-mfm: "Désactiver les textes animés dans les publications"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Hashtags"
|
||||
dev: "Échec lors de la création de l’application. Veuillez réessayer."
|
||||
ai-chan-kawaii: "Ai-Chan est mignonne !"
|
||||
you: "Vous"
|
||||
auth/views/form.vue:
|
||||
share-access: "Désirez-vous autoriser <i>{name}</i> à avoir accès à votre compte ?"
|
||||
permission-ask: "Cette application nécessite les autorisations suivantes :"
|
||||
@ -478,7 +478,7 @@ common/views/widgets/broadcast.vue:
|
||||
next: "Suivant"
|
||||
common/views/widgets/calendar.vue:
|
||||
year: "Année {}"
|
||||
month: "Mois {}"
|
||||
month: "{},"
|
||||
day: "{}"
|
||||
today: "Aujourd’hui :"
|
||||
this-month: "Ce mois-ci :"
|
||||
@ -515,7 +515,7 @@ common/views/widgets/tips.vue:
|
||||
tips-line10: "タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます"
|
||||
tips-line11: "Vous pouvez épingler des notes sur votre page en cliquant sur « … »"
|
||||
tips-line13: "Tous les fichiers attachés à cette publication sont sauvegardés dans le Drive"
|
||||
tips-line14: "ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます"
|
||||
tips-line14: "Lorsque vous personnalisez la disposition de votre page d’accueil, vous pouvez effectuer un clique droit sur un widget pour changer son apparence."
|
||||
tips-line17: "Vous pouvez mettre un texte en surbrillance en le mettant entre ** **"
|
||||
tips-line19: "Plusieurs fenêtres peuvent être détachées en dehors du navigateur."
|
||||
tips-line20: "Pourcentage sur le widget calendrier qui indique le pourcentage de temps passé"
|
||||
@ -524,7 +524,7 @@ common/views/widgets/tips.vue:
|
||||
tips-line24: "Misskey est fonctionnel depuis 2014"
|
||||
tips-line25: "Vous pouvez recevoir les notifications de Misskey dans un navigateur web compatible"
|
||||
common/views/pages/404.vue:
|
||||
page-not-found: "ページが見つかりませんでした"
|
||||
page-not-found: "La page demandée est introuvable !"
|
||||
common/views/pages/follow.vue:
|
||||
signed-in-as: "Connecté·e en tant que {}"
|
||||
following: "Suit"
|
||||
@ -553,7 +553,7 @@ desktop/views/components/activity.vue:
|
||||
title: "Activité"
|
||||
toggle: "Afficher les vues"
|
||||
desktop/views/components/calendar.vue:
|
||||
title: "{month} / {year}"
|
||||
title: "{month} - {year}"
|
||||
prev: "Mois précédent"
|
||||
next: "Mois suivant"
|
||||
go: "Cliquez pour naviguer"
|
||||
@ -877,7 +877,7 @@ common/views/components/password-settings.vue:
|
||||
enter-new-password-again: "Entrez à nouveau le nouveau mot de passe"
|
||||
not-match: "Les nouveaux mots de passe ne sont pas identiques"
|
||||
changed: "Mot de passe modifié avec succès"
|
||||
failed: "パスワード変更に失敗しました"
|
||||
failed: "Échec lors de la modification du mot de passe"
|
||||
desktop/views/components/sub-note-content.vue:
|
||||
private: "cette publication est privée"
|
||||
deleted: "cette publication a été supprimée"
|
||||
@ -970,7 +970,7 @@ admin/views/instance.vue:
|
||||
instance-description: "Description de l’instance"
|
||||
host: "Hôte"
|
||||
banner-url: "Url de l’image de la bannière"
|
||||
error-image-url: "エラー画像URL"
|
||||
error-image-url: "URL de l’image d’erreur"
|
||||
languages: "Langue de l’instance"
|
||||
languages-desc: "Vous pouvez en définir plus d’une, séparées par des espaces."
|
||||
maintainer-config: "Informations de l’administrateur"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "Requêtes"
|
||||
network-time: "Temps de réponse"
|
||||
network-usage: "Traffic"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "Tri"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "Taille - Ascendant"
|
||||
sizeDesc: "Taille - Volumineux en premier"
|
||||
origin:
|
||||
title: "Origine"
|
||||
combined: "Locaux et distants combinés"
|
||||
local: "Local"
|
||||
remote: "Distant"
|
||||
delete: "Supprimer"
|
||||
deleted: "Supprimé"
|
||||
admin/views/users.vue:
|
||||
operation: "Actions"
|
||||
username-or-userid: "Nom d’utilisateur·rice ou ID utilisateur"
|
||||
@ -1076,7 +1090,7 @@ admin/views/users.vue:
|
||||
createdAtAsc: "Date d’inscription (Ascendant)"
|
||||
createdAtDesc: "Date d’inscription (Descendant)"
|
||||
updatedAtAsc: "Mis à jour récemment (Ascendant)"
|
||||
updatedAtDesc: "更新日時が新しい順"
|
||||
updatedAtDesc: "Mis à jour récemment (descendant)"
|
||||
origin:
|
||||
title: "Origine"
|
||||
combined: "Locaux + distants"
|
||||
@ -1189,7 +1203,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
years-old: "{age} ans"
|
||||
year: "/"
|
||||
month: "/"
|
||||
day: "/"
|
||||
day: "-"
|
||||
desktop/views/pages/user/user.timeline.vue:
|
||||
default: "Publications"
|
||||
with-replies: "Publications et réponses"
|
||||
@ -1349,7 +1363,7 @@ mobile/views/pages/welcome.vue:
|
||||
signup: "S'enregistrer"
|
||||
mobile/views/pages/widgets.vue:
|
||||
dashboard: "Tableau de bord"
|
||||
widgets-hints: "ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。"
|
||||
widgets-hints: "Vous pouvez ajouter, supprimer et réaranger les widgets. Faites glisser « 三 » pour déplacer le widget. Appuyez sur « x » pour supprimer le widget. Certains widgets peuvent changer d’apparence en cliquant dessus."
|
||||
add-widget: "Ajouter"
|
||||
customization-tips: "Conseils de personnalisation"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -119,7 +119,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -179,6 +178,7 @@ common:
|
||||
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
@ -519,6 +519,14 @@ common/views/components/profile-editor.vue:
|
||||
email-verified: "メールアドレスが確認されました"
|
||||
email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
|
||||
|
||||
common/views/components/user-list-editor.vue:
|
||||
users: "ユーザー"
|
||||
rename: "リスト名を変更"
|
||||
delete: "リストを削除"
|
||||
remove-user: "このリストから削除"
|
||||
delete-are-you-sure: "リスト「$1」を削除しますか?"
|
||||
deleted: "削除しました"
|
||||
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "確認中"
|
||||
no-broadcasts: "お知らせはありません"
|
||||
@ -1156,6 +1164,12 @@ admin/views/instance.vue:
|
||||
smtp-port: "SMTPポート"
|
||||
smtp-user: "SMTPユーザー"
|
||||
smtp-pass: "SMTPパスワード"
|
||||
serviceworker-config: "ServiceWorker"
|
||||
enable-serviceworker: "ServiceWorkerを有効にする"
|
||||
serviceworker-info: "プッシュ通知を行うには有効する必要があります。"
|
||||
vapid-publickey: "VAPID公開鍵"
|
||||
vapid-privatekey: "VAPID秘密鍵"
|
||||
vapid-info: "ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります。シェルで次のようにします:"
|
||||
|
||||
admin/views/charts.vue:
|
||||
title: "チャート"
|
||||
@ -1183,6 +1197,23 @@ admin/views/charts.vue:
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "寿司(のほうがプリンよりむしろ)ウマい、タコ焼きはあらへんけど。"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示や!"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
|
||||
verified-user: "アメちゃん付きアカウント"
|
||||
disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
dev: "アプリの作成あかんかったわ。もっぺんやってみて。"
|
||||
ai-chan-kawaii: "藍ちゃめっさべっぴんさんや"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "あんたのアカウントに<i>{name}</i>がアクセスしようとしてるで?ええか?"
|
||||
permission-ask: "このアプリは次の権限を要求してんで:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "저는 (푸딩보다 차라리) 초밥이 좋아요"
|
||||
show-reversi-board-labels: "리버시 보드의 행과 열 레이블을 표시"
|
||||
use-white-black-reversi-stones: "리버시에 흑백 돌을 사용"
|
||||
use-contrast-reversi-stones: "리버시 아이콘의 대비를 높이기"
|
||||
verified-user: "공식 계정"
|
||||
disable-animated-mfm: "글의 문자 애니메이션을 비활성화"
|
||||
suggest-recent-hashtags: "최근 해시태그를 글 작성란에 표시"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "해시태그"
|
||||
dev: "앱을 만드는 데 실패했습니다. 다시 시도하시기 바랍니다."
|
||||
ai-chan-kawaii: "아이쨩 귀여워"
|
||||
you: "당신"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>가 당신의 계정에 엑세스하도록 허용하시겠습니까?"
|
||||
permission-ask: "이 앱은 다음의 권한을 요청합니다:"
|
||||
@ -524,7 +524,7 @@ common/views/widgets/tips.vue:
|
||||
tips-line24: "Misskey는 2014년에 서비스를 시작했습니다"
|
||||
tips-line25: "대응하는 브라우저인 경우 Misskey를 열어놓지 않아도 알림을 받을 수 있습니다"
|
||||
common/views/pages/404.vue:
|
||||
page-not-found: "ページが見つかりませんでした"
|
||||
page-not-found: "페이지를 찾을 수 없습니다"
|
||||
common/views/pages/follow.vue:
|
||||
signed-in-as: "{}으로 로그인"
|
||||
following: "팔로우 중"
|
||||
@ -970,7 +970,7 @@ admin/views/instance.vue:
|
||||
instance-description: "인스턴스의 소개"
|
||||
host: "관리자"
|
||||
banner-url: "배너 이미지 URL"
|
||||
error-image-url: "エラー画像URL"
|
||||
error-image-url: "오류 이미지 URL"
|
||||
languages: "인스턴스의 대상 언어"
|
||||
languages-desc: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||
maintainer-config: "관리자 정보"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "요청"
|
||||
network-time: "응답시간"
|
||||
network-usage: "통신량"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "정렬"
|
||||
createdAtAsc: "업로드 날짜 오랜 순"
|
||||
createdAtDesc: "업로드 날짜 최신순"
|
||||
sizeAsc: "크기가 작은 순"
|
||||
sizeDesc: "크기가 큰 순"
|
||||
origin:
|
||||
title: "출처"
|
||||
combined: "로컬 + 리모트"
|
||||
local: "로컬"
|
||||
remote: "리모트"
|
||||
delete: "삭제"
|
||||
deleted: "삭제하였습니다"
|
||||
admin/views/users.vue:
|
||||
operation: "작업"
|
||||
username-or-userid: "사용자명 혹은 사용자 ID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "ハッシュタグ"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,15 +111,14 @@ common:
|
||||
i-like-sushi: "Wolę sushi od puddingu"
|
||||
show-reversi-board-labels: "Pokazuj podpisy wierszy i kolumn w Reversi"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
verified-user: "Zweryfikowane konto"
|
||||
disable-animated-mfm: "Wyłącz animowany tekst we wpisach"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-show-nsfw: "Zawszę pokazuj zawartość NSFW"
|
||||
always-mark-nsfw: "Zawsze oznaczaj posty z multimediami jako NSFW"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
show-via: "viaを表示する"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
reduce-motion: "Zredukuj ruch w UI"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
use-os-default-emojis: "Użyj domyślnych Emoji systemowych"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Hashtagi"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "Ty"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "Ta aplikacja wymaga następujących uprawnień:"
|
||||
@ -298,7 +298,7 @@ common/views/components/cw-button.vue:
|
||||
hide: "Ukryj"
|
||||
show: "Pokaż więcej"
|
||||
chars: "{count} znaków"
|
||||
files: "{count}ファイル"
|
||||
files: "{count} plików"
|
||||
common/views/components/messaging.vue:
|
||||
search-user: "Znajdź użytkownika"
|
||||
you: "Ty"
|
||||
@ -576,7 +576,7 @@ desktop/views/components/drive-window.vue:
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "Awatar"
|
||||
banner: "Baner"
|
||||
nsfw: "閲覧注意"
|
||||
nsfw: "NSFW"
|
||||
contextmenu:
|
||||
rename: "Zmień nazwę"
|
||||
mark-as-sensitive: "Oznacz jako zawartość wrażliwą"
|
||||
@ -662,7 +662,7 @@ desktop/views/components/note-detail.vue:
|
||||
add-reaction: "Dodaj reakcję"
|
||||
desktop/views/components/note.vue:
|
||||
reply: "Odpowiedz"
|
||||
renote: "Renote"
|
||||
renote: "Udostępnij"
|
||||
add-reaction: "Dodaj reakcję"
|
||||
detail: "Szczegóły"
|
||||
private: "この投稿は非公開です"
|
||||
@ -729,7 +729,7 @@ desktop/views/components/settings.vue:
|
||||
notification: "Powiadomienia"
|
||||
apps: "Aplikacje"
|
||||
tags: "Hashtagi"
|
||||
mute-and-block: "ミュート/ブロック"
|
||||
mute-and-block: "Wycisz / Zablokuj"
|
||||
blocking: "ブロック"
|
||||
security: "Bezpieczeństwo"
|
||||
signin: "Historia logowań"
|
||||
@ -753,14 +753,14 @@ desktop/views/components/settings.vue:
|
||||
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
|
||||
deck-nav: "デッキ内ナビゲーション"
|
||||
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
|
||||
deck-default: "デッキをデフォルトのUIにする"
|
||||
deck-default: "Użyj Talię jako domyślne UI"
|
||||
display: "Wygląd i wyświetlanie"
|
||||
customize: "Dostosuj stronę główną"
|
||||
wallpaper: "Tapeta"
|
||||
choose-wallpaper: "Wybierz tło"
|
||||
delete-wallpaper: "Usuń tło"
|
||||
dark-mode: "Tryb ciemny"
|
||||
use-shadow: "UIに影を使用"
|
||||
use-shadow: "Użyj cieni w UI"
|
||||
rounded-corners: "Zaokrąglaj rogi w UI"
|
||||
circle-icons: "Używaj okrągłych ikon"
|
||||
contrasted-acct: "ユーザー名にコントラストを付ける"
|
||||
@ -769,20 +769,20 @@ desktop/views/components/settings.vue:
|
||||
show-reply-target: "Pokazuj cel odpowiedzi"
|
||||
timeline: "Oś czasu"
|
||||
show-my-renotes: "Pokazuj moje udostępnienia na osi czasu"
|
||||
show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
|
||||
show-renoted-my-notes: "Pokazuj moje udostępnione wpisy na osi czasu"
|
||||
show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
|
||||
show-maps: "Automatycznie pokazuj mapę"
|
||||
remain-deleted-note: "削除された投稿を表示し続ける"
|
||||
deck-column-align: "デッキのカラムの配置"
|
||||
deck-column-align-center: "中央"
|
||||
deck-column-align-left: "左"
|
||||
deck-column-align-left: "Lewo"
|
||||
deck-column-align-flexible: "Elastyczne"
|
||||
deck-column-width: "Szerokość kolumn w talii"
|
||||
deck-column-width-narrow: "wąskie"
|
||||
deck-column-width-narrower: "やや狭"
|
||||
deck-column-width-normal: "Normalny"
|
||||
deck-column-width-wider: "やや広"
|
||||
deck-column-width-wide: "szeroki"
|
||||
deck-column-width-narrow: "Wąska"
|
||||
deck-column-width-narrower: "Trochę wąska"
|
||||
deck-column-width-normal: "Normalna"
|
||||
deck-column-width-wider: "Trochę szeroka"
|
||||
deck-column-width-wide: "Szeroka"
|
||||
sound: "Dźwięk"
|
||||
enable-sounds: "Włącz dźwięk"
|
||||
enable-sounds-desc: "Odtwarzaj dźwięk przy wstawianiu wpisów, wysyłaniu lub otrzymywaniu wiadomości. Opcja ta jest zapamiętywana przez przeglądarkę."
|
||||
@ -817,10 +817,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Narzędzia"
|
||||
task-manager: "Menedżer zadań"
|
||||
third-parties: "Autorzy trzeci"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
navbar-position: "Pozycja paska nawigacji"
|
||||
navbar-position-top: "Góra"
|
||||
navbar-position-left: "Lewo"
|
||||
navbar-position-right: "Prawo"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "Jeżeli skonfigurujesz uwierzytelnianie dwuetapowe, aby zablokować się będziesz potrzebować (oprócz hasła) kodu ze skonfigurowanego urządzenia (np. smartfonu), co zwiększy bezpieczeństwo."
|
||||
detail: "Zobacz szczegóły…"
|
||||
@ -840,7 +840,7 @@ desktop/views/components/settings.2fa.vue:
|
||||
failed: "Nie udało się skonfigurować uwierzytelniania dwuetapowego, upewnij się że wprowadziłeś prawidłowy token."
|
||||
info: "Od teraz, wprowadzaj token wyświetlany na urządzeniu przy każdym logowaniu do Misskey."
|
||||
common/views/components/api-settings.vue:
|
||||
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
|
||||
intro: "Aby uzyskać dostęp do API, ustaw ten token jako klucz 'i' parametrów żądań."
|
||||
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
|
||||
regeneration-of-token: "W przypadku wycieku tokenu, możesz wygenerować nowy."
|
||||
regenerate-token: "Wygeneruj nowy token"
|
||||
@ -863,11 +863,11 @@ common/views/components/drive-settings.vue:
|
||||
common/views/components/mute-and-block.vue:
|
||||
mute-and-block: "Wycisz / Zablokuj"
|
||||
mute: "Wycisz"
|
||||
block: "ブロック"
|
||||
block: "Zablokuj"
|
||||
no-muted-users: "Brak wyciszonych użytkowników"
|
||||
no-blocked-users: "Brak zablokowanych użytkowników"
|
||||
word-mute: "Wyciszenie słowa"
|
||||
muted-words: "ミュートされたキーワード"
|
||||
muted-words: "Wyciszone słowa kluczowe"
|
||||
muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
save: "Zapisz"
|
||||
common/views/components/password-settings.vue:
|
||||
@ -884,7 +884,7 @@ desktop/views/components/sub-note-content.vue:
|
||||
media-count: "{}zawartości multimedialnej"
|
||||
poll: "Ankieta"
|
||||
desktop/views/components/settings.tags.vue:
|
||||
title: "タグ"
|
||||
title: "Tagi"
|
||||
query: "クエリ (省略可)"
|
||||
add: "Dodaj"
|
||||
save: "Zapisz"
|
||||
@ -899,8 +899,8 @@ desktop/views/components/timeline.vue:
|
||||
messages: "Wiadomości"
|
||||
list: "Listy"
|
||||
hashtag: "Hashtag"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
add-tag-timeline: "Dodaj hashtag"
|
||||
add-list: "Dodaj listę"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "Witaj ponownie,"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "Żądania"
|
||||
network-time: "Czas reakcji"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "Źródło"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "Usuń"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
@ -1237,14 +1251,14 @@ mobile/views/components/drive-file-chooser.vue:
|
||||
mobile/views/components/drive-folder-chooser.vue:
|
||||
select-folder: "Wybierz katalog"
|
||||
mobile/views/components/drive.file.vue:
|
||||
nsfw: "閲覧注意"
|
||||
nsfw: "NSFW"
|
||||
mobile/views/components/drive.file-detail.vue:
|
||||
download: "Pobierz"
|
||||
rename: "Zmień nazwę"
|
||||
move: "Przenieś"
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
nsfw: "NSFW"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
@ -1384,7 +1398,7 @@ mobile/views/pages/settings.vue:
|
||||
timeline: "Oś czasu"
|
||||
show-reply-target: "Pokazuj cel odpowiedzi"
|
||||
show-my-renotes: "Pokazuj moje udostępnienia"
|
||||
show-renoted-my-notes: "自分の投稿のRenoteを表示する"
|
||||
show-renoted-my-notes: "Pokazuj moje udostępnione wpisy"
|
||||
show-local-renotes: "ローカルの投稿のRenoteを表示する"
|
||||
post-style: "Styl wpisów"
|
||||
post-style-standard: "Standardowy"
|
||||
@ -1465,7 +1479,7 @@ deck:
|
||||
list: "Listy"
|
||||
swap-left: "Przesuń w lewo"
|
||||
swap-right: "Przesuń w prawo"
|
||||
swap-up: "上に移動"
|
||||
swap-up: "Przenieś w górę"
|
||||
swap-down: "下に移動"
|
||||
remove: "Usuń"
|
||||
add-column: "Dodaj kolumnę"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "Eu prefiro sushi a pudim"
|
||||
show-reversi-board-labels: "Mostrar etiquetas de colunas e linhas no Reversi"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "Conta verificada"
|
||||
disable-animated-mfm: "Desativar texto animado nas publicações"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Hashtags"
|
||||
dev: "アプリの作成に失敗しました。再度お試しください。"
|
||||
ai-chan-kawaii: "藍ちゃかわいい"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
permission-ask: "Este aplicativo precisa das seguintes permissões:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||
verified-user: "公式アカウント"
|
||||
disable-animated-mfm: "Отключить анимированный текст в постах"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "Хэштеги"
|
||||
dev: "Не удалось создать приложение. Пожалуйста, попробуйте ещё раз."
|
||||
ai-chan-kawaii: "Ai-chan kawaii!"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "Вы разрешаете <i>{name}</i> получить доступ к вашему аккаунту?"
|
||||
permission-ask: "このアプリは次の権限を要求しています:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "リクエスト"
|
||||
network-time: "応答時間"
|
||||
network-usage: "通信量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "ユーザー名またはユーザーID"
|
||||
|
@ -111,7 +111,6 @@ common:
|
||||
i-like-sushi: "相比于布丁来说, 我更喜欢寿司。"
|
||||
show-reversi-board-labels: "在 Reversi 中显示行和列表签"
|
||||
use-white-black-reversi-stones: "リバーシに白黒の石を使う"
|
||||
use-contrast-reversi-stones: "Make the stone color clear in Reversi"
|
||||
verified-user: "认证用户"
|
||||
disable-animated-mfm: "在帖子中禁用动画文本"
|
||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||
@ -165,6 +164,7 @@ common:
|
||||
hashtags: "标签"
|
||||
dev: "构建应用程序失败,请再试一次。"
|
||||
ai-chan-kawaii: "Ai-chan kawaii!"
|
||||
you: "あなた"
|
||||
auth/views/form.vue:
|
||||
share-access: "您要允许<i>{name}</i>来访问您的账户吗?"
|
||||
permission-ask: "这个应用程序需要以下权限:"
|
||||
@ -1054,6 +1054,20 @@ admin/views/charts.vue:
|
||||
network-requests: "请求"
|
||||
network-time: "响应时间"
|
||||
network-usage: "网络流量"
|
||||
admin/views/drive.vue:
|
||||
sort:
|
||||
title: "ソート"
|
||||
createdAtAsc: "アップロード日時が古い順"
|
||||
createdAtDesc: "アップロード日時が新しい順"
|
||||
sizeAsc: "サイズが小さい順"
|
||||
sizeDesc: "サイズが大きい順"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
local: "ローカル"
|
||||
remote: "リモート"
|
||||
delete: "削除"
|
||||
deleted: "削除しました"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "用户名或用户ID"
|
||||
|
13
package.json
13
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.63.0",
|
||||
"clientVersion": "2.0.12685",
|
||||
"version": "10.66.1",
|
||||
"clientVersion": "2.0.12859",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -37,7 +37,6 @@
|
||||
"@types/elasticsearch": "5.0.29",
|
||||
"@types/file-type": "5.2.2",
|
||||
"@types/gulp": "3.8.36",
|
||||
"@types/gulp-htmlmin": "1.3.32",
|
||||
"@types/gulp-mocha": "0.0.32",
|
||||
"@types/gulp-rename": "0.0.33",
|
||||
"@types/gulp-replace": "0.0.31",
|
||||
@ -88,7 +87,7 @@
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
"animejs": "2.2.0",
|
||||
"apexcharts": "2.2.4",
|
||||
"apexcharts": "2.4.2",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
@ -115,15 +114,14 @@
|
||||
"eslint": "5.8.0",
|
||||
"eslint-plugin-vue": "4.7.1",
|
||||
"eventemitter3": "3.1.0",
|
||||
"feed": "2.0.2",
|
||||
"file-loader": "2.0.0",
|
||||
"file-type": "10.6.0",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-htmlmin": "5.0.1",
|
||||
"gulp-imagemin": "4.1.0",
|
||||
"gulp-mocha": "6.0.0",
|
||||
"gulp-pug": "4.0.1",
|
||||
"gulp-rename": "1.4.0",
|
||||
"gulp-replace": "1.0.0",
|
||||
"gulp-sourcemaps": "2.6.4",
|
||||
@ -158,7 +156,7 @@
|
||||
"koa-views": "6.1.4",
|
||||
"langmap": "0.0.16",
|
||||
"loader-utils": "1.1.0",
|
||||
"minio": "7.0.1",
|
||||
"minio": "7.0.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
@ -228,6 +226,7 @@
|
||||
"vue-loader": "15.4.2",
|
||||
"vue-marquee-text-component": "1.1.0",
|
||||
"vue-router": "3.0.2",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.4",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
|
@ -64,7 +64,7 @@ export default abstract class Chart<T> {
|
||||
const keys = {
|
||||
span: -1,
|
||||
date: -1
|
||||
} as any;
|
||||
} as { [key: string]: 1 | -1; };
|
||||
if (grouped) keys.group = -1;
|
||||
this.collection.createIndex(keys, { unique: true });
|
||||
}
|
||||
|
188
src/client/app/admin/views/drive.vue
Normal file
188
src/client/app/admin/views/drive.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div class="pwnqwyet">
|
||||
<ui-card>
|
||||
<div slot="title"><fa :icon="faCloud"/> {{ $t('@.drive') }}</div>
|
||||
<section class="fit-top">
|
||||
<ui-horizon-group inputs>
|
||||
<ui-select v-model="sort">
|
||||
<span slot="label">{{ $t('sort.title') }}</span>
|
||||
<option value="-createdAt">{{ $t('sort.createdAtAsc') }}</option>
|
||||
<option value="+createdAt">{{ $t('sort.createdAtDesc') }}</option>
|
||||
<option value="-size">{{ $t('sort.sizeAsc') }}</option>
|
||||
<option value="+size">{{ $t('sort.sizeDesc') }}</option>
|
||||
</ui-select>
|
||||
<ui-select v-model="origin">
|
||||
<span slot="label">{{ $t('origin.title') }}</span>
|
||||
<option value="combined">{{ $t('origin.combined') }}</option>
|
||||
<option value="local">{{ $t('origin.local') }}</option>
|
||||
<option value="remote">{{ $t('origin.remote') }}</option>
|
||||
</ui-select>
|
||||
</ui-horizon-group>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<div class="kidvdlkg" v-for="file in files">
|
||||
<div @click="file._open = !file._open">
|
||||
<div>
|
||||
<div class="thumbnail" :style="thumbnail(file)"></div>
|
||||
</div>
|
||||
<div>
|
||||
<header>
|
||||
<b>{{ file.name }}</b>
|
||||
<span class="username">@{{ file.user | acct }}</span>
|
||||
</header>
|
||||
<div>
|
||||
<div>
|
||||
<span style="margin-right:16px;">{{ file.type }}</span>
|
||||
<span>{{ file.datasize | bytes }}</span>
|
||||
</div>
|
||||
<div><mk-time :time="file.createdAt" mode="detail"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="file._open">
|
||||
<ui-horizon-group>
|
||||
<ui-button @click="toggleSensitive(file)" v-if="file.isSensitive"><fa :icon="faEye"/> {{ $t('unmark-as-sensitive') }}</ui-button>
|
||||
<ui-button @click="toggleSensitive(file)" v-else><fa :icon="faEyeSlash"/> {{ $t('mark-as-sensitive') }}</ui-button>
|
||||
<ui-button @click="del(file)"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
</div>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetch">{{ $t('@.load-more') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { faCloud } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('admin/views/drive.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
sort: '+createdAt',
|
||||
origin: 'combined',
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
files: [],
|
||||
existMore: false,
|
||||
faCloud, faTrashAlt, faEye, faEyeSlash
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
sort() {
|
||||
this.files = [];
|
||||
this.offset = 0;
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
origin() {
|
||||
this.files = [];
|
||||
this.offset = 0;
|
||||
this.fetch();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.$root.api('admin/drive/files', {
|
||||
origin: this.origin,
|
||||
sort: this.sort,
|
||||
offset: this.offset,
|
||||
limit: this.limit + 1
|
||||
}).then(files => {
|
||||
if (files.length == this.limit + 1) {
|
||||
files.pop();
|
||||
this.existMore = true;
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
for (const x of files) {
|
||||
x._open = false;
|
||||
}
|
||||
this.files = this.files.concat(files);
|
||||
this.offset += this.limit;
|
||||
});
|
||||
},
|
||||
|
||||
thumbnail(file: any): any {
|
||||
return {
|
||||
'background-color': file.properties.avgColor && file.properties.avgColor.length == 3 ? `rgb(${file.properties.avgColor.join(',')})` : 'transparent',
|
||||
'background-image': `url(${file.thumbnailUrl})`
|
||||
};
|
||||
},
|
||||
|
||||
async del(file: any) {
|
||||
const process = async () => {
|
||||
await this.$root.api('drive/files/delete', { fileId: file.id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('deleted')
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toggleSensitive(file: any) {
|
||||
this.$root.api('drive/files/update', {
|
||||
fileId: file.id,
|
||||
isSensitive: !file.isSensitive
|
||||
});
|
||||
|
||||
file.isSensitive = !file.isSensitive;
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.pwnqwyet
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
.kidvdlkg
|
||||
padding 16px 0
|
||||
border-top solid 1px var(--faceDivider)
|
||||
|
||||
> div:first-child
|
||||
display flex
|
||||
cursor pointer
|
||||
|
||||
> div:nth-child(1)
|
||||
> .thumbnail
|
||||
display block
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> div:nth-child(2)
|
||||
flex 1
|
||||
padding-left 16px
|
||||
|
||||
@media (max-width 500px)
|
||||
font-size 14px
|
||||
|
||||
> header
|
||||
word-break break-word
|
||||
|
||||
> .username
|
||||
margin-left 8px
|
||||
opacity 0.7
|
||||
|
||||
</style>
|
@ -24,24 +24,28 @@
|
||||
|
||||
<ui-card>
|
||||
<div slot="title"><fa :icon="faGrin"/> {{ $t('emojis.title') }}</div>
|
||||
<section v-for="emoji in emojis">
|
||||
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
|
||||
<ui-horizon-group inputs>
|
||||
<ui-input v-model="emoji.name">
|
||||
<span>{{ $t('add-emoji.name') }}</span>
|
||||
<section v-for="emoji in emojis" class="oryfrbft">
|
||||
<div>
|
||||
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
|
||||
</div>
|
||||
<div>
|
||||
<ui-horizon-group>
|
||||
<ui-input v-model="emoji.name">
|
||||
<span>{{ $t('add-emoji.name') }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="emoji.aliases">
|
||||
<span>{{ $t('add-emoji.aliases') }}</span>
|
||||
</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-input v-model="emoji.url">
|
||||
<i slot="icon"><fa icon="link"/></i>
|
||||
<span>{{ $t('add-emoji.url') }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="emoji.aliases">
|
||||
<span>{{ $t('add-emoji.aliases') }}</span>
|
||||
</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-input v-model="emoji.url">
|
||||
<i slot="icon"><fa icon="link"/></i>
|
||||
<span>{{ $t('add-emoji.url') }}</span>
|
||||
</ui-input>
|
||||
<ui-horizon-group class="fit-bottom">
|
||||
<ui-button @click="updateEmoji(emoji)"><fa :icon="['far', 'save']"/> {{ $t('emojis.update') }}</ui-button>
|
||||
<ui-button @click="removeEmoji(emoji)"><fa :icon="['far', 'trash-alt']"/> {{ $t('emojis.remove') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group class="fit-bottom">
|
||||
<ui-button @click="updateEmoji(emoji)"><fa :icon="['far', 'save']"/> {{ $t('emojis.update') }}</ui-button>
|
||||
<ui-button @click="removeEmoji(emoji)"><fa :icon="['far', 'trash-alt']"/> {{ $t('emojis.remove') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
</div>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
@ -150,4 +154,21 @@ export default Vue.extend({
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
.oryfrbft
|
||||
@media (min-width 500px)
|
||||
display flex
|
||||
|
||||
> div:first-child
|
||||
@media (max-width 500px)
|
||||
padding-bottom 16px
|
||||
|
||||
> img
|
||||
vertical-align bottom
|
||||
|
||||
> div:last-child
|
||||
flex 1
|
||||
|
||||
@media (min-width 500px)
|
||||
padding-left 16px
|
||||
|
||||
</style>
|
||||
|
@ -22,12 +22,11 @@
|
||||
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
|
||||
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
|
||||
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
|
||||
<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
|
||||
<!-- <li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faShareAlt" fixed-width/>{{ $t('federation') }}</li> -->
|
||||
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
|
||||
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
|
||||
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
|
||||
|
||||
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li> -->
|
||||
</ul>
|
||||
<div class="back-to-misskey">
|
||||
<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
|
||||
@ -45,7 +44,7 @@
|
||||
<div v-if="page == 'emoji'"><x-emoji/></div>
|
||||
<div v-if="page == 'announcements'"><x-announcements/></div>
|
||||
<div v-if="page == 'hashtags'"><x-hashtags/></div>
|
||||
<div v-if="page == 'drive'"></div>
|
||||
<div v-if="page == 'drive'"><x-drive/></div>
|
||||
<div v-if="page == 'update'"></div>
|
||||
</div>
|
||||
</main>
|
||||
@ -63,6 +62,7 @@ import XEmoji from "./emoji.vue";
|
||||
import XAnnouncements from "./announcements.vue";
|
||||
import XHashtags from "./hashtags.vue";
|
||||
import XUsers from "./users.vue";
|
||||
import XDrive from "./drive.vue";
|
||||
import { faHeadset, faArrowLeft, faShareAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faGrin } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
@ -79,7 +79,8 @@ export default Vue.extend({
|
||||
XEmoji,
|
||||
XAnnouncements,
|
||||
XHashtags,
|
||||
XUsers
|
||||
XUsers,
|
||||
XDrive,
|
||||
},
|
||||
provide: {
|
||||
isMobile
|
||||
|
@ -32,8 +32,10 @@
|
||||
<header><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</header>
|
||||
<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch>
|
||||
<ui-info>{{ $t('recaptcha-info') }}</ui-info>
|
||||
<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-site-key') }}</ui-input>
|
||||
<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-secret-key') }}</ui-input>
|
||||
<ui-horizon-group inputs>
|
||||
<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-site-key') }}</ui-input>
|
||||
<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-secret-key') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
</section>
|
||||
<section>
|
||||
<header><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</header>
|
||||
@ -55,6 +57,15 @@
|
||||
</ui-horizon-group>
|
||||
<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<span slot="desc">{{ $t('smtp-secure-info') }}</span></ui-switch>
|
||||
</section>
|
||||
<section>
|
||||
<header><fa :icon="faBolt"/> {{ $t('serviceworker-config') }}</header>
|
||||
<ui-switch v-model="enableServiceWorker">{{ $t('enable-serviceworker') }}<span slot="desc">{{ $t('serviceworker-info') }}</span></ui-switch>
|
||||
<ui-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></ui-info>
|
||||
<ui-horizon-group inputs class="fit-bottom">
|
||||
<ui-input v-model="swPublicKey" :disabled="!enableServiceWorker"><i slot="icon"><fa icon="key"/></i>{{ $t('vapid-publickey') }}</ui-input>
|
||||
<ui-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><i slot="icon"><fa icon="key"/></i>{{ $t('vapid-privatekey') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
</section>
|
||||
<section>
|
||||
<header>summaly Proxy</header>
|
||||
<ui-input v-model="summalyProxy">URL</ui-input>
|
||||
@ -82,9 +93,11 @@
|
||||
<div slot="title"><fa :icon="['fab', 'twitter']"/> {{ $t('twitter-integration-config') }}</div>
|
||||
<section>
|
||||
<ui-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</ui-switch>
|
||||
<ui-horizon-group>
|
||||
<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('twitter-integration-consumer-key') }}</ui-input>
|
||||
<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('twitter-integration-consumer-secret') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</ui-info>
|
||||
<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('twitter-integration-consumer-key') }}</ui-input>
|
||||
<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('twitter-integration-consumer-secret') }}</ui-input>
|
||||
<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
@ -93,9 +106,11 @@
|
||||
<div slot="title"><fa :icon="['fab', 'github']"/> {{ $t('github-integration-config') }}</div>
|
||||
<section>
|
||||
<ui-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</ui-switch>
|
||||
<ui-horizon-group>
|
||||
<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('github-integration-client-id') }}</ui-input>
|
||||
<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('github-integration-client-secret') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</ui-info>
|
||||
<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('github-integration-client-id') }}</ui-input>
|
||||
<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('github-integration-client-secret') }}</ui-input>
|
||||
<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
@ -104,9 +119,11 @@
|
||||
<div slot="title"><fa :icon="['fab', 'discord']"/> {{ $t('discord-integration-config') }}</div>
|
||||
<section>
|
||||
<ui-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</ui-switch>
|
||||
<ui-horizon-group>
|
||||
<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('discord-integration-client-id') }}</ui-input>
|
||||
<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('discord-integration-client-secret') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</ui-info>
|
||||
<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('discord-integration-client-id') }}</ui-input>
|
||||
<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><i slot="icon"><fa icon="key"/></i>{{ $t('discord-integration-client-secret') }}</ui-input>
|
||||
<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
@ -118,7 +135,7 @@ import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { url, host } from '../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { faHeadset, faShieldAlt, faGhost, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeadset, faShieldAlt, faGhost, faUserPlus, faBolt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faEnvelope as farEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -166,7 +183,10 @@ export default Vue.extend({
|
||||
smtpPort: null,
|
||||
smtpUser: null,
|
||||
smtpPass: null,
|
||||
faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope
|
||||
enableServiceWorker: false,
|
||||
swPublicKey: null,
|
||||
swPrivateKey: null,
|
||||
faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt
|
||||
};
|
||||
},
|
||||
|
||||
@ -209,6 +229,9 @@ export default Vue.extend({
|
||||
this.smtpPort = meta.smtpPort;
|
||||
this.smtpUser = meta.smtpUser;
|
||||
this.smtpPass = meta.smtpPass;
|
||||
this.enableServiceWorker = meta.enableServiceWorker;
|
||||
this.swPublicKey = meta.swPublickey;
|
||||
this.swPrivateKey = meta.swPrivateKey;
|
||||
});
|
||||
},
|
||||
|
||||
@ -262,7 +285,10 @@ export default Vue.extend({
|
||||
smtpHost: this.smtpHost,
|
||||
smtpPort: parseInt(this.smtpPort, 10),
|
||||
smtpUser: this.smtpUser,
|
||||
smtpPass: this.smtpPass
|
||||
smtpPass: this.smtpPass,
|
||||
enableServiceWorker: this.enableServiceWorker,
|
||||
swPublicKey: this.swPublicKey,
|
||||
swPrivateKey: this.swPrivateKey
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
|
@ -38,25 +38,27 @@
|
||||
<option value="remote">{{ $t('users.origin.remote') }}</option>
|
||||
</ui-select>
|
||||
</ui-horizon-group>
|
||||
<div class="kofvwchc" v-for="user in users">
|
||||
<div>
|
||||
<a :href="user | userPage(null, true)">
|
||||
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<header>
|
||||
<b><mk-user-name :user="user"/></b>
|
||||
<span class="username">@{{ user | acct }}</span>
|
||||
</header>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<div class="kofvwchc" v-for="user in users">
|
||||
<div>
|
||||
<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span>
|
||||
<a :href="user | userPage(null, true)">
|
||||
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span>
|
||||
<header>
|
||||
<b><mk-user-name :user="user"/></b>
|
||||
<span class="username">@{{ user | acct }}</span>
|
||||
</header>
|
||||
<div>
|
||||
<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
@ -274,6 +276,9 @@ export default Vue.extend({
|
||||
flex 1
|
||||
padding-left 16px
|
||||
|
||||
@media (max-width 500px)
|
||||
font-size 14px
|
||||
|
||||
> header
|
||||
> .username
|
||||
margin-left 8px
|
||||
|
@ -10,3 +10,19 @@
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
.entranceFromTop {
|
||||
animation-duration: 0.5s;
|
||||
animation-name: entranceFromTop;
|
||||
}
|
||||
|
||||
@keyframes entranceFromTop {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-64px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export default function(type, data): Notification {
|
||||
|
||||
case 'unreadMessagingMessage':
|
||||
return {
|
||||
title: '%i18n:common.notification.message-from%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.message-from%'.split("{}")[1] ,
|
||||
title: '%i18n:common.notification.message-from%'.split('{}')[0] + `${getUserName(data.user)}` + '%i18n:common.notification.message-from%'.split('{}')[1] ,
|
||||
body: data.text, // TODO: getMessagingMessageSummary(data),
|
||||
icon: data.user.avatarUrl
|
||||
};
|
||||
@ -30,7 +30,7 @@ export default function(type, data): Notification {
|
||||
case 'reversiInvited':
|
||||
return {
|
||||
title: '%i18n:common.notification.reversi-invited%',
|
||||
body: '%i18n:common.notification.reversi-invited-by%'.split("{}")[0] + `${getUserName(data.parent)}` + '%i18n:common.notification.reversi-invited-by%'.split("{}")[1],
|
||||
body: '%i18n:common.notification.reversi-invited-by%'.split('{}')[0] + `${getUserName(data.parent)}` + '%i18n:common.notification.reversi-invited-by%'.split('{}')[1],
|
||||
icon: data.parent.avatarUrl
|
||||
};
|
||||
|
||||
@ -38,21 +38,21 @@ export default function(type, data): Notification {
|
||||
switch (data.type) {
|
||||
case 'mention':
|
||||
return {
|
||||
title: '%i18n:common.notification.notified-by%'.split("{}")[0] + `${getUserName(data.user)}:` + '%i18n:common.notification.notified-by%'.split("{}")[1],
|
||||
title: '%i18n:common.notification.notified-by%'.split('{}')[0] + `${getUserName(data.user)}:` + '%i18n:common.notification.notified-by%'.split('{}')[1],
|
||||
body: getNoteSummary(data),
|
||||
icon: data.user.avatarUrl
|
||||
};
|
||||
|
||||
case 'reply':
|
||||
return {
|
||||
title: '%i18n:common.notification.reply-from%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.reply-from%'.split("{}")[1],
|
||||
title: '%i18n:common.notification.reply-from%'.split('{}')[0] + `${getUserName(data.user)}` + '%i18n:common.notification.reply-from%'.split('{}')[1],
|
||||
body: getNoteSummary(data),
|
||||
icon: data.user.avatarUrl
|
||||
};
|
||||
|
||||
case 'quote':
|
||||
return {
|
||||
title: '%i18n:common.notification.quoted-by%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.quoted-by%'.split("{}")[1],
|
||||
title: '%i18n:common.notification.quoted-by%'.split('{}')[0] + `${getUserName(data.user)}` + '%i18n:common.notification.quoted-by%'.split('{}')[1],
|
||||
body: getNoteSummary(data),
|
||||
icon: data.user.avatarUrl
|
||||
};
|
||||
|
25
src/client/app/common/scripts/format-uptime.ts
Normal file
25
src/client/app/common/scripts/format-uptime.ts
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
/**
|
||||
* Format like the uptime command
|
||||
*/
|
||||
export default function(sec) {
|
||||
if (!sec) return sec;
|
||||
|
||||
const day = Math.floor(sec / 86400);
|
||||
const tod = sec % 86400;
|
||||
|
||||
// Days part in string: 2 days, 1 day, null
|
||||
const d = day >= 2 ? `${day} days` : day >= 1 ? `${day} day` : null;
|
||||
|
||||
// Time part in string: 1 sec, 1 min, 1:01
|
||||
const t
|
||||
= tod < 60 ? `${Math.floor(tod)} sec`
|
||||
: tod < 3600 ? `${Math.floor(tod / 60)} min`
|
||||
: `${Math.floor(tod / 60 / 60)}:${Math.floor((tod / 60) % 60).toString().padStart(2, '0')}`;
|
||||
|
||||
let str = '';
|
||||
if (d) str += `${d}, `;
|
||||
str += t;
|
||||
|
||||
return str;
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
|
||||
export default (data: ArrayBuffer) => {
|
||||
//const buf = new Buffer(data);
|
||||
//const hash = crypto.createHash("md5");
|
||||
//const hash = crypto.createHash('md5');
|
||||
//hash.update(buf);
|
||||
//return hash.digest("hex");
|
||||
//return hash.digest('hex');
|
||||
return '';
|
||||
};
|
||||
|
@ -80,8 +80,8 @@ export default (opts: Opts = {}) => ({
|
||||
const ast = parse(this.appearNote.text);
|
||||
// TODO: 再帰的にURL要素がないか調べる
|
||||
return unique(ast
|
||||
.filter(t => ((t.name == 'url' || t.name == 'link') && t.props.url && !t.props.silent))
|
||||
.map(t => t.props.url));
|
||||
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
|
||||
.map(t => t.node.props.url));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { themeColor } from '../../../config';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -75,7 +75,7 @@ export default Vue.extend({
|
||||
return this.dark ? '#fff' : '#777';
|
||||
},
|
||||
hHandColor(): string {
|
||||
return themeColor;
|
||||
return tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--primary')).toHexString();
|
||||
},
|
||||
|
||||
ms(): number {
|
||||
|
@ -48,7 +48,7 @@ export default Vue.extend({
|
||||
iconAndText(): any[] {
|
||||
return (
|
||||
(this.hasPendingFollowRequestFromYou && this.user.isLocked) ? ['hourglass-half', this.$t('request-pending')] :
|
||||
(this.hasPendingFollowRequestFromYou && !this.user.isLocked) ? ['hourglass-start', this.$t('follow-processing')] :
|
||||
(this.hasPendingFollowRequestFromYou && !this.user.isLocked) ? ['spinner', this.$t('follow-processing')] :
|
||||
(this.isFollowing) ? ['minus', this.$t('following')] :
|
||||
(!this.isFollowing && this.user.isLocked) ? ['plus', this.$t('follow-request')] :
|
||||
(!this.isFollowing && !this.user.isLocked) ? ['plus', this.$t('follow')] :
|
||||
|
@ -31,8 +31,8 @@
|
||||
@click="set(i)"
|
||||
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
|
||||
<template v-if="!$store.state.settings.games.reversi.useWhiteBlackStones">
|
||||
<img v-if="stone === true" :src="blackUser.avatarUrl" alt="black" :class="{ contrast: $store.state.settings.games.reversi.useContrastStones }">
|
||||
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white" :class="{ contrast: $store.state.settings.games.reversi.useContrastStones }">
|
||||
<img v-if="stone === true" :src="blackUser.avatarUrl" alt="black">
|
||||
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white">
|
||||
</template>
|
||||
<template v-if="$store.state.settings.games.reversi.useWhiteBlackStones">
|
||||
<fa v-if="stone === true" :icon="fasCircle"/>
|
||||
@ -430,13 +430,6 @@ export default Vue.extend({
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
&.contrast
|
||||
&[alt="black"]
|
||||
filter brightness(.5)
|
||||
|
||||
&[alt="white"]
|
||||
filter brightness(2)
|
||||
|
||||
> .graph
|
||||
display grid
|
||||
grid-template-columns repeat(61, 1fr)
|
||||
|
@ -60,7 +60,7 @@
|
||||
|
||||
<div>
|
||||
<template v-for="item in form">
|
||||
<ui-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</ui-switch>
|
||||
<ui-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</ui-switch>
|
||||
|
||||
<div class="card" v-if="item.type == 'radio'" :key="item.id">
|
||||
<header>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<ui-card>
|
||||
<ui-card v-if="enableTwitterIntegration || enableDiscordIntegration || enableGithubIntegration">
|
||||
<div slot="title"><fa icon="share-alt"/> {{ $t('title') }}</div>
|
||||
|
||||
<section>
|
||||
<section v-if="enableTwitterIntegration">
|
||||
<header><fa :icon="['fab', 'twitter']"/> Twitter</header>
|
||||
<p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
||||
<ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button>
|
||||
<ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section v-if="enableDiscordIntegration">
|
||||
<header><fa :icon="['fab', 'discord']"/> Discord</header>
|
||||
<p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p>
|
||||
<ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button>
|
||||
<ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section v-if="enableGithubIntegration">
|
||||
<header><fa :icon="['fab', 'github']"/> GitHub</header>
|
||||
<p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p>
|
||||
<ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button>
|
||||
@ -39,9 +39,20 @@ export default Vue.extend({
|
||||
twitterForm: null,
|
||||
discordForm: null,
|
||||
githubForm: null,
|
||||
enableTwitterIntegration: false,
|
||||
enableDiscordIntegration: false,
|
||||
enableGithubIntegration: false,
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.cookie = `i=${this.$store.state.i.token}`;
|
||||
this.$watch('$store.state.i', () => {
|
||||
|
70
src/client/app/common/views/components/mention.vue
Normal file
70
src/client/app/common/views/components/mention.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<router-link class="ldlomzub" :to="`/@${ canonical }`" v-user-preview="canonical">
|
||||
<span class="me" v-if="isMe">{{ $t('@.you') }}</span>
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { host as localHost } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
props: {
|
||||
username: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
host: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localHost
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canonical(): string {
|
||||
return `@${this.username}@${toUnicode(this.host)}`;
|
||||
},
|
||||
isMe(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.canonical.toLowerCase() === `@${this.$store.state.i.username}@${toUnicode(localHost)}`.toLowerCase();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toUnicode
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.ldlomzub
|
||||
color var(--mfmMention)
|
||||
|
||||
> .me
|
||||
pointer-events none
|
||||
user-select none
|
||||
padding 0 4px
|
||||
background var(--primary)
|
||||
border solid 1px var(--primary)
|
||||
border-radius 4px 0 0 4px
|
||||
color var(--primaryForeground)
|
||||
|
||||
& + .main
|
||||
padding 0 4px
|
||||
border solid 1px var(--primary)
|
||||
border-radius 0 4px 4px 0
|
||||
|
||||
> .main
|
||||
> .host.fade
|
||||
opacity 0.5
|
||||
|
||||
</style>
|
@ -9,7 +9,7 @@
|
||||
@keypress="onKeypress"
|
||||
@paste="onPaste"
|
||||
:placeholder="$t('input-message-here')"
|
||||
v-autocomplete="'text'"
|
||||
v-autocomplete="{ model: 'text' }"
|
||||
></textarea>
|
||||
<div class="file" @click="file = null" v-if="file">{{ file.name }}</div>
|
||||
<mk-uploader ref="uploader" @uploaded="onUploaded"/>
|
||||
|
@ -52,8 +52,8 @@ export default Vue.extend({
|
||||
if (this.message.text) {
|
||||
const ast = parse(this.message.text);
|
||||
return unique(ast
|
||||
.filter(t => ((t.name == 'url' || t.name == 'link') && t.props.url && !t.silent))
|
||||
.map(t => t.props.url));
|
||||
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
|
||||
.map(t => t.node.props.url));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
import Vue, { VNode } from 'vue';
|
||||
import { length } from 'stringz';
|
||||
import { Node } from '../../../../../mfm/parser';
|
||||
import { MfmForest } from '../../../../../mfm/parser';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import MkUrl from './url.vue';
|
||||
import MkMention from './mention.vue';
|
||||
import { concat, sum } from '../../../../../prelude/array';
|
||||
import MkFormula from './formula.vue';
|
||||
import MkGoogle from './google.vue';
|
||||
import { toUnicode } from 'punycode';
|
||||
import syntaxHighlight from '../../../../../mfm/syntax-highlight';
|
||||
import { host } from '../../../config';
|
||||
import { preorderF, countNodesF } from '../../../../../prelude/tree';
|
||||
|
||||
function getTextCount(tokens: Node[]): number {
|
||||
const rootCount = sum(tokens.filter(x => x.name === 'text').map(x => length(x.props.text)));
|
||||
const childrenCount = sum(tokens.filter(x => x.children).map(x => getTextCount(x.children)));
|
||||
return rootCount + childrenCount;
|
||||
}
|
||||
|
||||
function getChildrenCount(tokens: Node[]): number {
|
||||
const countTree = tokens.filter(x => x.children).map(x => getChildrenCount(x.children));
|
||||
return countTree.length + sum(countTree);
|
||||
function sumTextsLength(ts: MfmForest): number {
|
||||
const textNodes = preorderF(ts).filter(n => n.type === 'text');
|
||||
return sum(textNodes.map(x => length(x.props.text)));
|
||||
}
|
||||
|
||||
export default Vue.component('misskey-flavored-markdown', {
|
||||
@ -26,10 +22,6 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
ast: {
|
||||
type: [],
|
||||
required: false
|
||||
},
|
||||
shouldBreak: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@ -54,17 +46,15 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
render(createElement) {
|
||||
if (this.text == null || this.text == '') return;
|
||||
|
||||
const ast = this.ast == null ?
|
||||
parse(this.text, this.plainText) : // Parse text to ast
|
||||
this.ast as Node[];
|
||||
const ast = parse(this.text, this.plainText);
|
||||
|
||||
let bigCount = 0;
|
||||
let motionCount = 0;
|
||||
|
||||
const genEl = (ast: Node[]) => concat(ast.map((token): VNode[] => {
|
||||
switch (token.name) {
|
||||
const genEl = (ast: MfmForest) => concat(ast.map((token): VNode[] => {
|
||||
switch (token.node.type) {
|
||||
case 'text': {
|
||||
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||
const text = token.node.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||
|
||||
if (this.shouldBreak) {
|
||||
const x = text.split('\n')
|
||||
@ -94,7 +84,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
|
||||
case 'big': {
|
||||
bigCount++;
|
||||
const isLong = getTextCount(token.children) > 10 || getChildrenCount(token.children) > 5;
|
||||
const isLong = sumTextsLength(token.children) > 10 || countNodesF(token.children) > 5;
|
||||
const isMany = bigCount > 3;
|
||||
return (createElement as any)('strong', {
|
||||
attrs: {
|
||||
@ -121,7 +111,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
|
||||
case 'motion': {
|
||||
motionCount++;
|
||||
const isLong = getTextCount(token.children) > 10 || getChildrenCount(token.children) > 5;
|
||||
const isLong = sumTextsLength(token.children) > 10 || countNodesF(token.children) > 5;
|
||||
const isMany = motionCount > 3;
|
||||
return (createElement as any)('span', {
|
||||
attrs: {
|
||||
@ -138,7 +128,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
return [createElement(MkUrl, {
|
||||
key: Math.random(),
|
||||
props: {
|
||||
url: token.props.url,
|
||||
url: token.node.props.url,
|
||||
target: '_blank',
|
||||
style: 'color:var(--mfmLink);'
|
||||
}
|
||||
@ -149,40 +139,32 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
return [createElement('a', {
|
||||
attrs: {
|
||||
class: 'link',
|
||||
href: token.props.url,
|
||||
href: token.node.props.url,
|
||||
target: '_blank',
|
||||
title: token.props.url,
|
||||
title: token.node.props.url,
|
||||
style: 'color:var(--mfmLink);'
|
||||
}
|
||||
}, genEl(token.children))];
|
||||
}
|
||||
|
||||
case 'mention': {
|
||||
const host = token.props.host == null && this.author && this.author.host != null ? this.author.host : token.props.host;
|
||||
const canonical = host != null ? `@${token.props.username}@${toUnicode(host)}` : `@${token.props.username}`;
|
||||
return (createElement as any)('router-link', {
|
||||
return [createElement(MkMention, {
|
||||
key: Math.random(),
|
||||
attrs: {
|
||||
to: `/${canonical}`,
|
||||
// TODO
|
||||
//dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
||||
style: 'color:var(--mfmMention);'
|
||||
},
|
||||
directives: [{
|
||||
name: 'user-preview',
|
||||
value: canonical
|
||||
}]
|
||||
}, canonical);
|
||||
props: {
|
||||
host: (token.node.props.host == null && this.author && this.author.host != null ? this.author.host : token.node.props.host) || host,
|
||||
username: token.node.props.username
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
case 'hashtag': {
|
||||
return [createElement('router-link', {
|
||||
key: Math.random(),
|
||||
attrs: {
|
||||
to: `/tags/${encodeURIComponent(token.props.hashtag)}`,
|
||||
to: `/tags/${encodeURIComponent(token.node.props.hashtag)}`,
|
||||
style: 'color:var(--mfmHashtag);'
|
||||
}
|
||||
}, `#${token.props.hashtag}`)];
|
||||
}, `#${token.node.props.hashtag}`)];
|
||||
}
|
||||
|
||||
case 'blockCode': {
|
||||
@ -191,7 +173,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}, [
|
||||
createElement('code', {
|
||||
domProps: {
|
||||
innerHTML: syntaxHighlight(token.props.code)
|
||||
innerHTML: syntaxHighlight(token.node.props.code)
|
||||
}
|
||||
})
|
||||
])];
|
||||
@ -200,7 +182,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
case 'inlineCode': {
|
||||
return [createElement('code', {
|
||||
domProps: {
|
||||
innerHTML: syntaxHighlight(token.props.code)
|
||||
innerHTML: syntaxHighlight(token.node.props.code)
|
||||
}
|
||||
})];
|
||||
}
|
||||
@ -234,8 +216,8 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
return [createElement('mk-emoji', {
|
||||
key: Math.random(),
|
||||
attrs: {
|
||||
emoji: token.props.emoji,
|
||||
name: token.props.name
|
||||
emoji: token.node.props.emoji,
|
||||
name: token.node.props.name
|
||||
},
|
||||
props: {
|
||||
customEmojis: this.customEmojis || customEmojis,
|
||||
@ -249,7 +231,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
return [createElement(MkFormula, {
|
||||
key: Math.random(),
|
||||
props: {
|
||||
formula: token.props.formula
|
||||
formula: token.node.props.formula
|
||||
}
|
||||
})];
|
||||
}
|
||||
@ -259,13 +241,13 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
return [createElement(MkGoogle, {
|
||||
key: Math.random(),
|
||||
props: {
|
||||
q: token.props.query
|
||||
q: token.node.props.query
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
default: {
|
||||
console.log('unknown ast type:', token.name);
|
||||
console.log('unknown ast type:', token.node.type);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
53
src/client/app/common/views/components/particle.vue
Normal file
53
src/client/app/common/views/components/particle.vue
Normal file
File diff suppressed because one or more lines are too long
@ -24,7 +24,7 @@
|
||||
</ui-input>
|
||||
|
||||
<ui-input v-model="birthday" type="date">
|
||||
<span>{{ $t('birthday') }}</span>
|
||||
<span slot="title">{{ $t('birthday') }}</span>
|
||||
<span slot="prefix"><fa icon="birthday-cake"/></span>
|
||||
</ui-input>
|
||||
|
||||
|
@ -4,16 +4,16 @@
|
||||
<div class="popover" :class="{ compact, big }" ref="popover">
|
||||
<p v-if="!compact">{{ title }}</p>
|
||||
<div ref="buttons" :class="{ showFocus }">
|
||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')"><mk-reaction-icon reaction='like'/></button>
|
||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')"><mk-reaction-icon reaction='love'/></button>
|
||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')"><mk-reaction-icon reaction='laugh'/></button>
|
||||
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" :title="$t('@.reactions.hmm')"><mk-reaction-icon reaction='hmm'/></button>
|
||||
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" :title="$t('@.reactions.surprise')"><mk-reaction-icon reaction='surprise'/></button>
|
||||
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" :title="$t('@.reactions.congrats')"><mk-reaction-icon reaction='congrats'/></button>
|
||||
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" :title="$t('@.reactions.angry')"><mk-reaction-icon reaction='angry'/></button>
|
||||
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" :title="$t('@.reactions.confused')"><mk-reaction-icon reaction='confused'/></button>
|
||||
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')"><mk-reaction-icon reaction='rip'/></button>
|
||||
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')"><mk-reaction-icon reaction='pudding'/></button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,36 +1,107 @@
|
||||
<template>
|
||||
<div class="mk-reactions-viewer">
|
||||
<template v-if="reactions">
|
||||
<span :class="{ reacted: note.myReaction == 'like' }" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'love' }" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'laugh' }" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'hmm' }" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'surprise' }" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'congrats' }" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'angry' }" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'confused' }" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'rip' }" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'pudding' }" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'like' }" @click="react('like')" v-if="reactions.like" v-particle><mk-reaction-icon reaction="like" ref="like"/><span>{{ reactions.like }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'love' }" @click="react('love')" v-if="reactions.love" v-particle><mk-reaction-icon reaction="love" ref="love"/><span>{{ reactions.love }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'laugh' }" @click="react('laugh')" v-if="reactions.laugh" v-particle><mk-reaction-icon reaction="laugh" ref="laugh"/><span>{{ reactions.laugh }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'hmm' }" @click="react('hmm')" v-if="reactions.hmm" v-particle><mk-reaction-icon reaction="hmm" ref="hmm"/><span>{{ reactions.hmm }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'surprise' }" @click="react('surprise')" v-if="reactions.surprise" v-particle><mk-reaction-icon reaction="surprise" ref="surprise"/><span>{{ reactions.surprise }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'congrats' }" @click="react('congrats')" v-if="reactions.congrats" v-particle><mk-reaction-icon reaction="congrats" ref="congrats"/><span>{{ reactions.congrats }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'angry' }" @click="react('angry')" v-if="reactions.angry" v-particle><mk-reaction-icon reaction="angry" ref="angry"/><span>{{ reactions.angry }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'confused' }" @click="react('confused')" v-if="reactions.confused" v-particle><mk-reaction-icon reaction="confused" ref="confused"/><span>{{ reactions.confused }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'rip' }" @click="react('rip')" v-if="reactions.rip" v-particle><mk-reaction-icon reaction="rip" ref="rip"/><span>{{ reactions.rip }}</span></span>
|
||||
<span :class="{ reacted: note.myReaction == 'pudding' }" @click="react('pudding')" v-if="reactions.pudding" v-particle><mk-reaction-icon reaction="pudding" ref="pudding"/><span>{{ reactions.pudding }}</span></span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Icon from './reaction-icon.vue';
|
||||
import * as anime from 'animejs';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['note'],
|
||||
computed: {
|
||||
reactions(): number {
|
||||
reactions(): any {
|
||||
return this.note.reactionCounts;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'reactions.like'() {
|
||||
this.anime('like');
|
||||
},
|
||||
'reactions.love'() {
|
||||
this.anime('love');
|
||||
},
|
||||
'reactions.laugh'() {
|
||||
this.anime('laugh');
|
||||
},
|
||||
'reactions.hmm'() {
|
||||
this.anime('hmm');
|
||||
},
|
||||
'reactions.surprise'() {
|
||||
this.anime('surprise');
|
||||
},
|
||||
'reactions.congrats'() {
|
||||
this.anime('congrats');
|
||||
},
|
||||
'reactions.angry'() {
|
||||
this.anime('angry');
|
||||
},
|
||||
'reactions.confused'() {
|
||||
this.anime('confused');
|
||||
},
|
||||
'reactions.rip'() {
|
||||
this.anime('rip');
|
||||
},
|
||||
'reactions.pudding'() {
|
||||
this.anime('pudding');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
react(reaction: string) {
|
||||
this.$root.api('notes/reactions/create', {
|
||||
noteId: this.note.id,
|
||||
reaction: reaction
|
||||
});
|
||||
},
|
||||
anime(reaction: string) {
|
||||
if (this.$store.state.device.reduceMotion) return;
|
||||
if (document.hidden) return;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const rect = this.$refs[reaction].$el.getBoundingClientRect();
|
||||
|
||||
const x = rect.left;
|
||||
const y = rect.top;
|
||||
|
||||
const icon = new Icon({
|
||||
parent: this,
|
||||
propsData: {
|
||||
reaction: reaction
|
||||
}
|
||||
}).$mount();
|
||||
|
||||
icon.$el.style.position = 'absolute';
|
||||
icon.$el.style.zIndex = 100;
|
||||
icon.$el.style.top = (y + window.scrollY) + 'px';
|
||||
icon.$el.style.left = (x + window.scrollX) + 'px';
|
||||
icon.$el.style.fontSize = window.getComputedStyle(this.$refs[reaction].$el).fontSize;
|
||||
|
||||
document.body.appendChild(icon.$el);
|
||||
|
||||
anime({
|
||||
targets: icon.$el,
|
||||
opacity: [1, 0],
|
||||
translateY: [0, -64],
|
||||
duration: 1000,
|
||||
easing: 'linear',
|
||||
complete: () => {
|
||||
icon.destroyDom();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -26,6 +26,7 @@ import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/signin.vue'),
|
||||
|
||||
props: {
|
||||
withAvatar: {
|
||||
type: Boolean,
|
||||
@ -33,6 +34,7 @@ export default Vue.extend({
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
signing: false,
|
||||
@ -45,11 +47,13 @@ export default Vue.extend({
|
||||
meta: null
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUsernameChange() {
|
||||
this.$root.api('users/show', {
|
||||
@ -60,6 +64,7 @@ export default Vue.extend({
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
this.signing = true;
|
||||
|
||||
@ -80,8 +85,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
.mk-signin
|
||||
color #555
|
||||
|
||||
|
@ -50,6 +50,7 @@ import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/signup.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
host: toUnicode(host),
|
||||
@ -64,6 +65,7 @@ export default Vue.extend({
|
||||
meta: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
shouldShowProfileUrl(): boolean {
|
||||
return (this.username != '' &&
|
||||
@ -72,17 +74,20 @@ export default Vue.extend({
|
||||
this.usernameState != 'max-range');
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
|
||||
head.appendChild(script);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangeUsername() {
|
||||
if (this.username == '') {
|
||||
@ -111,6 +116,7 @@ export default Vue.extend({
|
||||
this.usernameState = 'error';
|
||||
});
|
||||
},
|
||||
|
||||
onChangePassword() {
|
||||
if (this.password == '') {
|
||||
this.passwordStrength = '';
|
||||
@ -120,6 +126,7 @@ export default Vue.extend({
|
||||
const strength = getPasswordStrength(this.password);
|
||||
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
||||
},
|
||||
|
||||
onChangePasswordRetype() {
|
||||
if (this.retypedPassword == '') {
|
||||
this.passwordRetypeState = null;
|
||||
@ -128,6 +135,7 @@ export default Vue.extend({
|
||||
|
||||
this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match';
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
this.$root.api('signup', {
|
||||
username: this.username,
|
||||
@ -138,8 +146,9 @@ export default Vue.extend({
|
||||
this.$root.api('signin', {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}, true).then(() => {
|
||||
location.href = '/';
|
||||
}, true).then(res => {
|
||||
localStorage.setItem('i', res.i);
|
||||
location.reload();
|
||||
});
|
||||
}).catch(() => {
|
||||
alert(this.$t('some-error'));
|
||||
@ -154,8 +163,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
.mk-signup
|
||||
min-width 302px
|
||||
</style>
|
||||
|
@ -33,14 +33,7 @@ export default Vue.extend({
|
||||
return typeof this.time == 'string' ? new Date(this.time) : this.time;
|
||||
},
|
||||
absolute(): string {
|
||||
const time = this._time;
|
||||
return (
|
||||
time.getFullYear() + '年' +
|
||||
(time.getMonth() + 1) + '月' +
|
||||
time.getDate() + '日' +
|
||||
' ' +
|
||||
time.getHours() + '時' +
|
||||
time.getMinutes() + '分');
|
||||
return this._time.toLocaleString();
|
||||
},
|
||||
relative(): string {
|
||||
const time = this._time;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<component class="dmtdnykelhudezerjlfpbhgovrgnqqgr"
|
||||
:is="link ? 'a' : 'button'"
|
||||
:class="[styl, { inline, primary }]"
|
||||
:class="{ inline, primary, wait }"
|
||||
:type="type"
|
||||
@click="$emit('click')"
|
||||
@mousedown="onMousedown"
|
||||
@ -48,11 +48,11 @@ export default Vue.extend({
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
styl: 'fill'
|
||||
};
|
||||
wait: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.autofocus) {
|
||||
@ -121,6 +121,24 @@ export default Vue.extend({
|
||||
box-shadow none
|
||||
text-decoration none
|
||||
user-select none
|
||||
color var(--text)
|
||||
background var(--buttonBg)
|
||||
|
||||
&:not(:disabled):hover
|
||||
background var(--buttonHoverBg)
|
||||
|
||||
&:not(:disabled):active
|
||||
background var(--buttonActiveBg)
|
||||
|
||||
&.primary
|
||||
color var(--primaryForeground)
|
||||
background var(--primary)
|
||||
|
||||
&:not(:disabled):hover
|
||||
background var(--primaryLighten5)
|
||||
|
||||
&:not(:disabled):active
|
||||
background var(--primaryDarken5)
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
@ -152,35 +170,25 @@ export default Vue.extend({
|
||||
&.primary
|
||||
font-weight bold
|
||||
|
||||
&.fill
|
||||
color var(--text)
|
||||
background var(--buttonBg)
|
||||
&.wait
|
||||
background linear-gradient(
|
||||
45deg,
|
||||
var(--primaryDarken10) 25%,
|
||||
var(--primary) 25%,
|
||||
var(--primary) 50%,
|
||||
var(--primaryDarken10) 50%,
|
||||
var(--primaryDarken10) 75%,
|
||||
var(--primary) 75%,
|
||||
var(--primary)
|
||||
)
|
||||
background-size 32px 32px
|
||||
animation stripe-bg 1.5s linear infinite
|
||||
opacity 0.7
|
||||
cursor wait
|
||||
|
||||
&:not(:disabled):hover
|
||||
background var(--buttonHoverBg)
|
||||
|
||||
&:not(:disabled):active
|
||||
background var(--buttonActiveBg)
|
||||
|
||||
&.primary
|
||||
color var(--primaryForeground)
|
||||
background var(--primary)
|
||||
|
||||
&:not(:disabled):hover
|
||||
background var(--primaryLighten5)
|
||||
|
||||
&:not(:disabled):active
|
||||
background var(--primaryDarken5)
|
||||
|
||||
&:not(.fill)
|
||||
color var(--primary)
|
||||
background none
|
||||
|
||||
&:not(:disabled):hover
|
||||
color var(--primaryDarken5)
|
||||
|
||||
&:not(:disabled):active
|
||||
background var(--primaryAlpha03)
|
||||
@keyframes stripe-bg
|
||||
from {background-position: 0 0;}
|
||||
to {background-position: -64px 32px;}
|
||||
|
||||
> .ripples
|
||||
position absolute
|
||||
|
@ -6,6 +6,7 @@
|
||||
<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>
|
||||
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||
<template v-if="type != 'file'">
|
||||
<input ref="input"
|
||||
@ -281,6 +282,20 @@ root(fill)
|
||||
transform-origin top left
|
||||
transform scale(1)
|
||||
|
||||
> .title
|
||||
position absolute
|
||||
z-index 1
|
||||
top fill ? -24px : -17px
|
||||
left 0 !important
|
||||
pointer-events none
|
||||
font-size 16px
|
||||
line-height 32px
|
||||
color var(--inputLabel)
|
||||
pointer-events none
|
||||
//will-change transform
|
||||
transform-origin top left
|
||||
transform scale(.75)
|
||||
|
||||
> input
|
||||
display block
|
||||
width 100%
|
||||
|
@ -170,6 +170,9 @@ export default Vue.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.hostname === 'music.youtube.com')
|
||||
url.hostname = 'youtube.com';
|
||||
|
||||
fetch(`/url?url=${encodeURIComponent(this.url)}`).then(res => {
|
||||
res.json().then(info => {
|
||||
if (info.url == null) return;
|
||||
|
150
src/client/app/common/views/components/user-list-editor.vue
Normal file
150
src/client/app/common/views/components/user-list-editor.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="cudqjmnl">
|
||||
<ui-card>
|
||||
<div slot="title"><fa :icon="faList"/> {{ list.title }}</div>
|
||||
|
||||
<section>
|
||||
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
|
||||
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title"><fa :icon="faUsers"/> {{ $t('users') }}</div>
|
||||
|
||||
<section>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<div class="phcqulfl" v-for="user in users">
|
||||
<div>
|
||||
<a :href="user | userPage">
|
||||
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<header>
|
||||
<b><mk-user-name :user="user"/></b>
|
||||
<span class="username">@{{ user | acct }}</span>
|
||||
</header>
|
||||
<div>
|
||||
<a @click="remove(user)">{{ $t('remove-user') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { faList, faICursor, faUsers } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/user-list-editor.vue'),
|
||||
|
||||
props: {
|
||||
list: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
faList, faICursor, faTrashAlt, faUsers
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchUsers();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchUsers() {
|
||||
this.$root.api('users/show', {
|
||||
userIds: this.list.userIds
|
||||
}).then(users => {
|
||||
this.users = users;
|
||||
});
|
||||
},
|
||||
|
||||
rename() {
|
||||
this.$root.dialog({
|
||||
title: this.$t('rename'),
|
||||
input: {
|
||||
default: this.list.title
|
||||
}
|
||||
}).then(({ canceled, result: title }) => {
|
||||
if (canceled) return;
|
||||
this.$root.api('users/lists/update', {
|
||||
listId: this.list.id,
|
||||
title: title
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
del() {
|
||||
this.$root.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('delete-are-you-sure').replace('$1', this.list.title),
|
||||
showCancelButton: true
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
this.$root.api('users/lists/delete', {
|
||||
listId: this.list.id
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('deleted')
|
||||
});
|
||||
}).catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
remove(user: any) {
|
||||
this.$root.api('users/lists/pull', {
|
||||
listId: this.list.id,
|
||||
userId: user.id
|
||||
}).then(() => {
|
||||
this.fetchUsers();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.cudqjmnl
|
||||
.phcqulfl
|
||||
display flex
|
||||
padding 16px 0
|
||||
border-top solid 1px var(--faceDivider)
|
||||
|
||||
> div:first-child
|
||||
> a
|
||||
> .avatar
|
||||
width 64px
|
||||
height 64px
|
||||
|
||||
> div:last-child
|
||||
flex 1
|
||||
padding-left 16px
|
||||
|
||||
@media (max-width 500px)
|
||||
font-size 14px
|
||||
|
||||
> header
|
||||
> .username
|
||||
margin-left 8px
|
||||
opacity 0.7
|
||||
|
||||
</style>
|
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</header>
|
||||
<div class="text">
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :author="note.user" :custom-emojis="note.emojis"/>
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.cw != null ? note.cw : note.text" :author="note.user" :custom-emojis="note.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,21 +21,24 @@ class Autocomplete {
|
||||
private suggestion: any;
|
||||
private textarea: any;
|
||||
private vm: any;
|
||||
private model: any;
|
||||
private currentType: string;
|
||||
private opts: {
|
||||
model: string;
|
||||
};
|
||||
private opening: boolean;
|
||||
|
||||
private get text(): string {
|
||||
return this.vm[this.model];
|
||||
return this.vm[this.opts.model];
|
||||
}
|
||||
|
||||
private set text(text: string) {
|
||||
this.vm[this.model] = text;
|
||||
this.vm[this.opts.model] = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 対象のテキストエリアを与えてインスタンスを初期化します。
|
||||
*/
|
||||
constructor(textarea, vm, model) {
|
||||
constructor(textarea, vm, opts) {
|
||||
//#region BIND
|
||||
this.onInput = this.onInput.bind(this);
|
||||
this.complete = this.complete.bind(this);
|
||||
@ -45,7 +48,8 @@ class Autocomplete {
|
||||
this.suggestion = null;
|
||||
this.textarea = textarea;
|
||||
this.vm = vm;
|
||||
this.model = model;
|
||||
this.opts = opts;
|
||||
this.opening = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,6 +130,8 @@ class Autocomplete {
|
||||
if (type != this.currentType) {
|
||||
this.close();
|
||||
}
|
||||
if (this.opening) return;
|
||||
this.opening = true;
|
||||
this.currentType = type;
|
||||
|
||||
//#region サジェストを表示すべき位置を計算
|
||||
@ -141,6 +147,8 @@ class Autocomplete {
|
||||
this.suggestion.x = x;
|
||||
this.suggestion.y = y;
|
||||
this.suggestion.q = q;
|
||||
|
||||
this.opening = false;
|
||||
} else {
|
||||
const MkAutocomplete = await import('../components/autocomplete.vue').then(m => m.default);
|
||||
|
||||
@ -160,6 +168,8 @@ class Autocomplete {
|
||||
|
||||
// 要素追加
|
||||
document.body.appendChild(this.suggestion.$el);
|
||||
|
||||
this.opening = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import autocomplete from './autocomplete';
|
||||
import particle from './particle';
|
||||
|
||||
Vue.directive('autocomplete', autocomplete);
|
||||
Vue.directive('particle', particle);
|
||||
|
24
src/client/app/common/views/directives/particle.ts
Normal file
24
src/client/app/common/views/directives/particle.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import Particle from '../components/particle.vue';
|
||||
|
||||
export default {
|
||||
bind(el, binding, vn) {
|
||||
if (vn.context.$store.state.device.reduceMotion) return;
|
||||
|
||||
el.addEventListener('click', () => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
const x = rect.left + (el.clientWidth / 2);
|
||||
const y = rect.top + (el.clientHeight / 2);
|
||||
|
||||
const particle = new Particle({
|
||||
parent: vn.context,
|
||||
propsData: {
|
||||
x,
|
||||
y
|
||||
}
|
||||
}).$mount();
|
||||
|
||||
document.body.appendChild(particle.$el);
|
||||
});
|
||||
}
|
||||
};
|
@ -22,7 +22,7 @@
|
||||
:disabled="followWait">
|
||||
<template v-if="!followWait">
|
||||
<template v-if="user.hasPendingFollowRequestFromYou && user.isLocked"><fa icon="hourglass-half"/> {{ $t('request-pending') }}</template>
|
||||
<template v-else-if="user.hasPendingFollowRequestFromYou && !user.isLocked"><fa icon="hourglass-start"/> {{ $t('follow-processing') }}</template>
|
||||
<template v-else-if="user.hasPendingFollowRequestFromYou && !user.isLocked"><fa icon="spinner"/> {{ $t('follow-processing') }}</template>
|
||||
<template v-else-if="user.isFollowing"><fa icon="minus"/> {{ $t('following') }}</template>
|
||||
<template v-else-if="!user.isFollowing && user.isLocked"><fa icon="plus"/> {{ $t('follow-request') }}</template>
|
||||
<template v-else-if="!user.isFollowing && !user.isLocked"><fa icon="plus"/> {{ $t('follow') }}</template>
|
||||
|
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="uptimes">
|
||||
<p>Uptimes</p>
|
||||
<p>Process: {{ process ? process.toFixed(0) : '---' }}s</p>
|
||||
<p>OS: {{ os ? os.toFixed(0) : '---' }}s</p>
|
||||
<p>Process: {{ process }}</p>
|
||||
<p>OS: {{ os }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import formatUptime from '../../scripts/format-uptime';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['connection'],
|
||||
@ -25,8 +26,8 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
this.process = stats.process_uptime;
|
||||
this.os = stats.os_uptime;
|
||||
this.process = formatUptime(stats.process_uptime);
|
||||
this.os = formatUptime(stats.os_uptime);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -16,7 +16,6 @@ export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://
|
||||
export const lang = window.lang;
|
||||
export const langs = _LANGS_;
|
||||
export const locale = JSON.parse(localStorage.getItem('locale'));
|
||||
export const themeColor = _THEME_COLOR_;
|
||||
export const copyright = _COPYRIGHT_;
|
||||
export const version = _VERSION_;
|
||||
export const clientVersion = _CLIENT_VERSION_;
|
||||
|
@ -16,13 +16,13 @@
|
||||
<b>{{ $t('recent-tags') }}:</b>
|
||||
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
|
||||
</div>
|
||||
<div class="local-only" v-if="this.localOnly == true">{{ $t('local-only-message') }}</div>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="'cw'">
|
||||
<div class="local-only" v-if="localOnly == true">{{ $t('local-only-message') }}</div>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }">
|
||||
<div class="textarea">
|
||||
<textarea :class="{ with: (files.length != 0 || poll) }"
|
||||
ref="text" v-model="text" :disabled="posting"
|
||||
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
||||
v-autocomplete="'text'"
|
||||
v-autocomplete="{ model: 'text' }"
|
||||
></textarea>
|
||||
<button class="emoji" @click="emoji" ref="emoji">
|
||||
<fa :icon="['far', 'laugh']"/>
|
||||
@ -54,9 +54,9 @@
|
||||
<span v-if="visibility === 'private'"><fa icon="lock"/></span>
|
||||
</button>
|
||||
<p class="text-count" :class="{ over: trimmedLength(text) > maxNoteTextLength }">{{ maxNoteTextLength - trimmedLength(text) }}</p>
|
||||
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
|
||||
<ui-button primary :wait="posting" class="submit" :disabled="!canPost" @click="post">
|
||||
{{ posting ? $t('posting') : submitText }}<mk-ellipsis v-if="posting"/>
|
||||
</button>
|
||||
</ui-button>
|
||||
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
|
||||
<div class="dropzone" v-if="draghover"></div>
|
||||
</div>
|
||||
@ -74,6 +74,7 @@ import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import { toASCII } from 'punycode';
|
||||
import extractMentions from '../../../../../misc/extract-mentions';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/post-form.vue'),
|
||||
@ -184,8 +185,7 @@ export default Vue.extend({
|
||||
if (this.reply && this.reply.text != null) {
|
||||
const ast = parse(this.reply.text);
|
||||
|
||||
// TODO: 新しいMFMパーサに対応
|
||||
for (const x of ast.filter(t => t.type == 'mention')) {
|
||||
for (const x of extractMentions(ast)) {
|
||||
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||
|
||||
// 自分は除外
|
||||
@ -684,62 +684,8 @@ export default Vue.extend({
|
||||
position absolute
|
||||
bottom 16px
|
||||
right 16px
|
||||
cursor pointer
|
||||
padding 0
|
||||
margin 0
|
||||
width 110px
|
||||
height 40px
|
||||
font-size 1em
|
||||
color var(--primaryForeground)
|
||||
background var(--primary)
|
||||
outline none
|
||||
border none
|
||||
border-radius 4px
|
||||
|
||||
&:not(:disabled)
|
||||
font-weight bold
|
||||
|
||||
&:hover:not(:disabled)
|
||||
background var(--primaryLighten5)
|
||||
|
||||
&:active:not(:disabled)
|
||||
background var(--primaryDarken5)
|
||||
|
||||
&:focus
|
||||
&:after
|
||||
content ""
|
||||
pointer-events none
|
||||
position absolute
|
||||
top -5px
|
||||
right -5px
|
||||
bottom -5px
|
||||
left -5px
|
||||
border 2px solid var(--primaryAlpha03)
|
||||
border-radius 8px
|
||||
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
cursor default
|
||||
|
||||
&.wait
|
||||
background linear-gradient(
|
||||
45deg,
|
||||
var(--primaryDarken10) 25%,
|
||||
var(--primary) 25%,
|
||||
var(--primary) 50%,
|
||||
var(--primaryDarken10) 50%,
|
||||
var(--primaryDarken10) 75%,
|
||||
var(--primary) 75%,
|
||||
var(--primary)
|
||||
)
|
||||
background-size 32px 32px
|
||||
animation stripe-bg 1.5s linear infinite
|
||||
opacity 0.7
|
||||
cursor wait
|
||||
|
||||
@keyframes stripe-bg
|
||||
from {background-position: 0 0;}
|
||||
to {background-position: -64px 32px;}
|
||||
|
||||
> .text-count
|
||||
pointer-events none
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="2fa">
|
||||
<p style="margin-top:0;">{{ $t('intro') }}<a :href="$t('href')" target="_blank">{{ $t('detail') }}</a></p>
|
||||
<p style="margin-top:0;">{{ $t('intro') }}<a :href="$t('url')" target="_blank">{{ $t('detail') }}</a></p>
|
||||
<ui-info warn>{{ $t('caution') }}</ui-info>
|
||||
<p v-if="!data && !$store.state.i.twoFactorEnabled"><ui-button @click="register">{{ $t('register') }}</ui-button></p>
|
||||
<template v-if="$store.state.i.twoFactorEnabled">
|
||||
@ -9,7 +9,7 @@
|
||||
</template>
|
||||
<div v-if="data">
|
||||
<ol>
|
||||
<li>{{ $t('authenticator% <a href="https://support.google.com/accounts/answer/1066447" target="_blank">%i18n:@howtoinstall') }}</a></li>
|
||||
<li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" target="_blank">{{ $t('howtoinstall') }}</a></li>
|
||||
<li>{{ $t('scan') }}<br><img :src="data.qr"></li>
|
||||
<li>{{ $t('done') }}<br>
|
||||
<input type="number" v-model="token" class="ui">
|
||||
|
@ -137,7 +137,6 @@
|
||||
<section>
|
||||
<ui-switch v-model="games_reversi_showBoardLabels">{{ $t('@.show-reversi-board-labels') }}</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useWhiteBlackStones">{{ $t('@.use-white-black-reversi-stones') }}</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useContrastStones">{{ $t('@.use-contrast-reversi-stones') }}</ui-switch>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
@ -511,11 +510,6 @@ export default Vue.extend({
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useWhiteBlackStones', value }); }
|
||||
},
|
||||
|
||||
games_reversi_useContrastStones: {
|
||||
get() { return this.$store.state.settings.games.reversi.useContrastStones; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
get() { return this.$store.state.settings.disableAnimatedMfm; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
|
||||
|
@ -92,6 +92,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import MkUserListsWindow from './user-lists-window.vue';
|
||||
import MkUserListWindow from './user-list-window.vue';
|
||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
||||
import MkSettingsWindow from './settings-window.vue';
|
||||
import MkDriveWindow from './drive-window.vue';
|
||||
@ -143,7 +144,9 @@ export default Vue.extend({
|
||||
this.close();
|
||||
const w = this.$root.new(MkUserListsWindow);
|
||||
w.$once('choosen', list => {
|
||||
this.$router.push(`i/lists/${ list.id }`);
|
||||
this.$root.new(MkUserListWindow, {
|
||||
list
|
||||
});
|
||||
});
|
||||
},
|
||||
followRequests() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<form class="search" @submit.prevent="onSubmit">
|
||||
<form class="wlvfdpkp" @submit.prevent="onSubmit">
|
||||
<i><fa icon="search"/></i>
|
||||
<input v-model="q" type="search" :placeholder="$t('placeholder')"/>
|
||||
<input v-model="q" type="search" :placeholder="$t('placeholder')" v-autocomplete="{ model: 'q' }"/>
|
||||
<div class="result"></div>
|
||||
</form>
|
||||
</template>
|
||||
@ -14,15 +14,36 @@ export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/ui.header.search.vue'),
|
||||
data() {
|
||||
return {
|
||||
q: ''
|
||||
q: '',
|
||||
wait: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
if (this.q.startsWith('#')) {
|
||||
this.$router.push(`/tags/${encodeURIComponent(this.q.substr(1))}`);
|
||||
async onSubmit() {
|
||||
if (this.wait) return;
|
||||
|
||||
const q = this.q.trim();
|
||||
if (q.startsWith('@')) {
|
||||
this.$router.push(`/${q}`);
|
||||
} else if (q.startsWith('#')) {
|
||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
||||
} else if (q.startsWith('https://')) {
|
||||
this.wait = true;
|
||||
try {
|
||||
const res = await this.$root.api('ap/show', {
|
||||
uri: q
|
||||
});
|
||||
if (res.type == 'User') {
|
||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
||||
} else if (res.type == 'Note') {
|
||||
this.$router.push(`/notes/${res.object.id}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO
|
||||
}
|
||||
this.wait = false;
|
||||
} else {
|
||||
this.$router.push(`/search?q=${encodeURIComponent(this.q)}`);
|
||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +51,7 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.search
|
||||
.wlvfdpkp
|
||||
@media (max-width 800px)
|
||||
display none !important
|
||||
|
||||
|
24
src/client/app/desktop/views/components/user-list-window.vue
Normal file
24
src/client/app/desktop/views/components/user-list-window.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
||||
<span slot="header"><fa icon="list"/> {{ list.title }}</span>
|
||||
|
||||
<x-editor :list="list"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XEditor from '../../../common/views/components/user-list-editor.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XEditor
|
||||
},
|
||||
|
||||
props: {
|
||||
list: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
|
||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
||||
<span slot="header"><fa icon="list"/> {{ $t('title') }}</span>
|
||||
|
||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
|
||||
|
@ -627,6 +627,7 @@ export default Vue.extend({
|
||||
|
||||
> .content
|
||||
height 100%
|
||||
overflow auto
|
||||
|
||||
&:not([flexible])
|
||||
> .main > .body > .content
|
||||
|
@ -11,7 +11,7 @@
|
||||
<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<transition-group name="mk-notes" class="transition notes" ref="notes">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition notes" ref="notes" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<x-note
|
||||
:note="note"
|
||||
@ -24,7 +24,7 @@
|
||||
<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</transition-group>
|
||||
</component>
|
||||
|
||||
<footer v-if="more">
|
||||
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<x-notification class="notification" :notification="notification" :key="notification.id"/>
|
||||
<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
|
||||
|
@ -28,7 +28,9 @@
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">{{ field.name }}</dt>
|
||||
<dt class="name">
|
||||
<misskey-flavored-markdown :text="field.name" :shouldBreak="false" :plainText="true" :custom-emojis="user.emojis"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<misskey-flavored-markdown :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
</dd>
|
||||
@ -431,6 +433,7 @@ export default Vue.extend({
|
||||
display flex
|
||||
padding 0
|
||||
margin 0
|
||||
align-items center
|
||||
|
||||
> .name
|
||||
padding 4px
|
||||
|
@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<main v-if="!fetching">
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<div class="more" v-if="existMore">
|
||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
@ -75,7 +77,7 @@ main
|
||||
padding 16px
|
||||
max-width 700px
|
||||
|
||||
> .post
|
||||
> * > .post
|
||||
margin-bottom 16px
|
||||
|
||||
> .more
|
||||
|
@ -3,7 +3,7 @@
|
||||
<header :class="$style.header">
|
||||
<h1>#{{ $route.params.tag }}</h1>
|
||||
</header>
|
||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
@ -20,7 +20,9 @@
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">{{ field.name }}</dt>
|
||||
<dt class="name">
|
||||
<misskey-flavored-markdown :text="field.name" :shouldBreak="false" :plainText="true" :custom-emojis="user.emojis"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<misskey-flavored-markdown :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
</dd>
|
||||
@ -189,6 +191,7 @@ export default Vue.extend({
|
||||
display flex
|
||||
padding 0
|
||||
margin 0
|
||||
align-items center
|
||||
|
||||
> .name
|
||||
border-right solid 1px var(--faceDivider)
|
||||
|
@ -24,9 +24,14 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withFiles: true,
|
||||
fileType: image,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
|
@ -15,7 +15,7 @@
|
||||
@paste="onPaste"
|
||||
:placeholder="placeholder"
|
||||
ref="text"
|
||||
v-autocomplete="'text'"
|
||||
v-autocomplete="{ model: 'text' }"
|
||||
></textarea>
|
||||
<button class="emoji" @click="emoji" ref="emoji">
|
||||
<fa :icon="['far', 'laugh']"/>
|
||||
|
@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
|
||||
import VAnimateCss from 'v-animate-css';
|
||||
import VModal from 'vue-js-modal';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import SequentialEntrance from 'vue-sequential-entrance';
|
||||
|
||||
import VueHotkey from './common/hotkey';
|
||||
import App from './app.vue';
|
||||
@ -122,6 +123,7 @@ import {
|
||||
faArrowLeft,
|
||||
faMapMarker,
|
||||
faRobot,
|
||||
faHourglassHalf,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import {
|
||||
@ -252,6 +254,7 @@ library.add(
|
||||
faArrowLeft,
|
||||
faMapMarker,
|
||||
faRobot,
|
||||
faHourglassHalf,
|
||||
|
||||
farBell,
|
||||
farEnvelope,
|
||||
@ -287,6 +290,7 @@ Vue.use(VAnimateCss);
|
||||
Vue.use(VModal);
|
||||
Vue.use(VueHotkey);
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(SequentialEntrance);
|
||||
|
||||
Vue.component('fa', FontAwesomeIcon);
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<mk-notification :notification="notification" :key="notification.id"/>
|
||||
<p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date">
|
||||
|
@ -19,8 +19,8 @@
|
||||
</span>
|
||||
<a @click="addVisibleUser">+{{ $t('add-visible-user') }}</a>
|
||||
</div>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="'cw'">
|
||||
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="'text'"></textarea>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }">
|
||||
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea>
|
||||
<div class="attaches" v-show="files.length != 0">
|
||||
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
|
||||
<div class="file" v-for="file in files" :key="file.id">
|
||||
@ -66,6 +66,7 @@ import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import { toASCII } from 'punycode';
|
||||
import extractMentions from '../../../../../misc/extract-mentions';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/post-form.vue'),
|
||||
@ -174,7 +175,7 @@ export default Vue.extend({
|
||||
if (this.reply && this.reply.text != null) {
|
||||
const ast = parse(this.reply.text);
|
||||
|
||||
for (const x of ast.filter(t => t.type == 'mention')) {
|
||||
for (const x of extractMentions(ast)) {
|
||||
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||
|
||||
// 自分は除外
|
||||
|
@ -45,9 +45,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$store.commit('setUiHeaderHeight', this.$refs.root.offsetHeight);
|
||||
});
|
||||
this.$store.commit('setUiHeaderHeight', 48);
|
||||
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = this.$root.stream.useSharedConnection('main');
|
||||
|
@ -60,7 +60,8 @@ export default Vue.extend({
|
||||
hasGameInvitation: false,
|
||||
connection: null,
|
||||
aboutUrl: `/docs/${lang}/about`,
|
||||
announcements: []
|
||||
announcements: [],
|
||||
searching: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -95,12 +96,37 @@ export default Vue.extend({
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
if (this.searching) return;
|
||||
|
||||
this.$root.dialog({
|
||||
title: this.$t('search'),
|
||||
input: true
|
||||
}).then(({ canceled, result: query }) => {
|
||||
}).then(async ({ canceled, result: query }) => {
|
||||
if (canceled) return;
|
||||
this.$router.push(`/search?q=${encodeURIComponent(query)}`);
|
||||
|
||||
const q = this.q.trim();
|
||||
if (q.startsWith('@')) {
|
||||
this.$router.push(`/${q}`);
|
||||
} else if (q.startsWith('#')) {
|
||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
||||
} else if (q.startsWith('https://')) {
|
||||
this.searching = true;
|
||||
try {
|
||||
const res = await this.$root.api('ap/show', {
|
||||
uri: q
|
||||
});
|
||||
if (res.type == 'User') {
|
||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
||||
} else if (res.type == 'Note') {
|
||||
this.$router.push(`/notes/${res.object.id}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO
|
||||
}
|
||||
this.searching = false;
|
||||
} else {
|
||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -3,9 +3,11 @@
|
||||
<span slot="header"><span style="margin-right:4px;"><fa icon="star"/></span>{{ $t('title') }}</span>
|
||||
|
||||
<main>
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</main>
|
||||
</mk-ui>
|
||||
@ -79,13 +81,13 @@ main
|
||||
margin 0 auto
|
||||
padding 8px
|
||||
|
||||
> .post
|
||||
> * > .post
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
> .post
|
||||
> * > .post
|
||||
margin-bottom 16px
|
||||
|
||||
@media (min-width 600px)
|
||||
|
@ -33,7 +33,7 @@
|
||||
|
||||
<section>
|
||||
<ui-switch v-model="games_reversi_showBoardLabels">{{ $t('@.show-reversi-board-labels') }}</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useContrastStones">{{ $t('@.use-contrast-reversi-stones') }}</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useWhiteBlackStones">{{ $t('@.use-white-black-reversi-stones') }}</ui-switch>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@ -287,9 +287,9 @@ export default Vue.extend({
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.showBoardLabels', value }); }
|
||||
},
|
||||
|
||||
games_reversi_useContrastStones: {
|
||||
get() { return this.$store.state.settings.games.reversi.useContrastStones; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
|
||||
games_reversi_useWhiteBlackStones: {
|
||||
get() { return this.$store.state.settings.games.reversi.useWhiteBlackStones; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useWhiteBlackStones', value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<span slot="header"><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</span>
|
||||
|
||||
<main>
|
||||
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
||||
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
|
@ -3,11 +3,7 @@
|
||||
<span slot="header" v-if="!fetching"><fa icon="list"/>{{ list.title }}</span>
|
||||
|
||||
<main v-if="!fetching">
|
||||
<ul>
|
||||
<li v-for="user in users" :key="user.id"><router-link :to="user | userPage">
|
||||
<mk-user-name :user="user"/>
|
||||
</router-link></li>
|
||||
</ul>
|
||||
<x-editor :list="list"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
@ -15,13 +11,16 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import XEditor from '../../../common/views/components/user-list-editor.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
list: null,
|
||||
users: null
|
||||
list: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@ -42,12 +41,6 @@ export default Vue.extend({
|
||||
this.fetching = false;
|
||||
|
||||
Progress.done();
|
||||
|
||||
this.$root.api('users/show', {
|
||||
userIds: this.list.userIds
|
||||
}).then(users => {
|
||||
this.users = users;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -55,8 +48,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
main
|
||||
width 100%
|
||||
max-width 680px
|
||||
|
@ -26,7 +26,9 @@
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">{{ field.name }}</dt>
|
||||
<dt class="name">
|
||||
<misskey-flavored-markdown :text="field.name" :shouldBreak="false" :plainText="true" :custom-emojis="user.emojis"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<misskey-flavored-markdown :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
</dd>
|
||||
@ -316,6 +318,7 @@ main
|
||||
display flex
|
||||
padding 0
|
||||
margin 0
|
||||
align-items center
|
||||
|
||||
> .name
|
||||
padding 4px
|
||||
|
@ -26,10 +26,15 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withFiles: true,
|
||||
limit: 6,
|
||||
fileType: image,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
for (const note of notes) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
// Detect an old browser
|
||||
if (!('fetch' in window)) {
|
||||
alert(
|
||||
'お使いのブラウザ(またはOS)が古いためMisskeyを動作させることができません。' +
|
||||
'お使いのブラウザ(またはOS)のバージョンが旧式のため、Misskeyを動作させることができません。' +
|
||||
'バージョンを最新のものに更新するか、別のブラウザをお試しください。' +
|
||||
'\n\n' +
|
||||
'Your browser (or your OS) seems outdated. ' +
|
||||
|
@ -40,8 +40,7 @@ const defaultSettings = {
|
||||
games: {
|
||||
reversi: {
|
||||
showBoardLabels: false,
|
||||
useWhileBlackStones: false,
|
||||
useContrastStones: false
|
||||
useWhiteBlackStones: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
2
src/client/app/v.d.ts
vendored
2
src/client/app/v.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue';
|
||||
export default Vue;
|
||||
}
|
||||
|
@ -39,21 +39,7 @@ export type Source = {
|
||||
|
||||
accesslog?: string;
|
||||
|
||||
/**
|
||||
* Service Worker
|
||||
*/
|
||||
sw?: {
|
||||
public_key: string;
|
||||
private_key: string;
|
||||
};
|
||||
|
||||
clusterLimit?: number;
|
||||
|
||||
user_recommendation?: {
|
||||
external: boolean;
|
||||
engine: string;
|
||||
timeout: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,3 @@
|
||||
{
|
||||
"copyright": "Copyright (c) 2014-2018 syuilo",
|
||||
"themeColor": "#fb4e4e",
|
||||
"themeColorForeground": "#fff"
|
||||
"copyright": "Copyright (c) 2014-2018 syuilo"
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ type: `switch`
|
||||
スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。
|
||||
|
||||
##### プロパティ
|
||||
`desc` ... スイッチの詳細な説明。
|
||||
`label` ... スイッチに表記するテキスト。
|
||||
|
||||
#### ラジオボタン
|
||||
type: `radio`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { count, concat } from "../../prelude/array";
|
||||
import { count, concat } from '../../prelude/array';
|
||||
|
||||
// MISSKEY REVERSI ENGINE
|
||||
|
||||
@ -76,27 +76,14 @@ export default class Reversi {
|
||||
this.mapHeight = map.length;
|
||||
const mapData = map.join('');
|
||||
|
||||
this.board = mapData.split('').map(d => {
|
||||
if (d == '-') return null;
|
||||
if (d == 'b') return BLACK;
|
||||
if (d == 'w') return WHITE;
|
||||
return undefined;
|
||||
});
|
||||
this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
|
||||
|
||||
this.map = mapData.split('').map(d => {
|
||||
if (d == '-' || d == 'b' || d == 'w') return 'empty';
|
||||
return 'null';
|
||||
});
|
||||
this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
|
||||
//#endregion
|
||||
|
||||
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
|
||||
if (!this.canPutSomewhere(BLACK)) {
|
||||
if (!this.canPutSomewhere(WHITE)) {
|
||||
this.turn = null;
|
||||
} else {
|
||||
this.turn = WHITE;
|
||||
}
|
||||
}
|
||||
if (!this.canPutSomewhere(BLACK))
|
||||
this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,16 +104,14 @@ export default class Reversi {
|
||||
* 黒石の比率
|
||||
*/
|
||||
public get blackP() {
|
||||
if (this.blackCount == 0 && this.whiteCount == 0) return 0;
|
||||
return this.blackCount / (this.blackCount + this.whiteCount);
|
||||
return this.blackCount == 0 && this.whiteCount == 0 ? 0 : this.blackCount / (this.blackCount + this.whiteCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 白石の比率
|
||||
*/
|
||||
public get whiteP() {
|
||||
if (this.blackCount == 0 && this.whiteCount == 0) return 0;
|
||||
return this.whiteCount / (this.blackCount + this.whiteCount);
|
||||
return this.blackCount == 0 && this.whiteCount == 0 ? 0 : this.whiteCount / (this.blackCount + this.whiteCount);
|
||||
}
|
||||
|
||||
public transformPosToXy(pos: number): number[] {
|
||||
@ -172,13 +157,10 @@ export default class Reversi {
|
||||
|
||||
private calcTurn() {
|
||||
// ターン計算
|
||||
if (this.canPutSomewhere(!this.prevColor)) {
|
||||
this.turn = !this.prevColor;
|
||||
} else if (this.canPutSomewhere(this.prevColor)) {
|
||||
this.turn = this.prevColor;
|
||||
} else {
|
||||
this.turn = null;
|
||||
}
|
||||
this.turn =
|
||||
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
|
||||
this.canPutSomewhere(this.prevColor) ? this.prevColor :
|
||||
null;
|
||||
}
|
||||
|
||||
public undo() {
|
||||
@ -199,8 +181,7 @@ export default class Reversi {
|
||||
*/
|
||||
public mapDataGet(pos: number): MapPixel {
|
||||
const [x, y] = this.transformPosToXy(pos);
|
||||
if (x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight) return 'null';
|
||||
return this.map[pos];
|
||||
return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,16 +204,10 @@ export default class Reversi {
|
||||
* @param pos 位置
|
||||
*/
|
||||
public canPut(color: Color, pos: number): boolean {
|
||||
// 既に石が置いてある場所には打てない
|
||||
if (this.board[pos] !== null) return false;
|
||||
|
||||
if (this.opts.canPutEverywhere) {
|
||||
// 挟んでなくても置けるモード
|
||||
return this.mapDataGet(pos) == 'empty';
|
||||
} else {
|
||||
// 相手の石を1つでも反転させられるか
|
||||
return this.effects(color, pos).length !== 0;
|
||||
}
|
||||
return (
|
||||
this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
|
||||
this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
|
||||
this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,19 +238,13 @@ export default class Reversi {
|
||||
[x, y] = nextPos(x, y);
|
||||
|
||||
// 座標が指し示す位置がボード外に出たとき
|
||||
if (this.opts.loopedBoard) {
|
||||
x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth;
|
||||
y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight;
|
||||
|
||||
if (this.transformXyToPos(x, y) == initPos) {
|
||||
if (this.opts.loopedBoard && this.transformXyToPos(
|
||||
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
|
||||
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) == initPos)
|
||||
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
|
||||
return found;
|
||||
}
|
||||
} else {
|
||||
if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) {
|
||||
return []; // 挟めないことが確定 (盤面外に到達)
|
||||
}
|
||||
}
|
||||
return found;
|
||||
else if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight)
|
||||
return []; // 挟めないことが確定 (盤面外に到達)
|
||||
|
||||
const pos = this.transformXyToPos(x, y);
|
||||
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
|
||||
@ -300,14 +269,9 @@ export default class Reversi {
|
||||
* ゲームの勝者 (null = 引き分け)
|
||||
*/
|
||||
public get winner(): Color {
|
||||
if (!this.isEnded) return undefined;
|
||||
|
||||
if (this.blackCount == this.whiteCount) return null;
|
||||
|
||||
if (this.opts.isLlotheo) {
|
||||
return this.blackCount > this.whiteCount ? WHITE : BLACK;
|
||||
} else {
|
||||
return this.blackCount > this.whiteCount ? BLACK : WHITE;
|
||||
}
|
||||
return this.isEnded ?
|
||||
this.blackCount == this.whiteCount ? null :
|
||||
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
|
||||
undefined;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export default function(html: string): string {
|
||||
if ((rel && rel.value.match('tag') !== null) || !href || href.value == txt) {
|
||||
text += txt;
|
||||
// メンション
|
||||
} else if (txt.startsWith('@') && !rel || !rel.value.match(/^me /)) {
|
||||
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
|
||||
const part = txt.split('@');
|
||||
|
||||
if (part.length == 2) {
|
||||
|
@ -2,10 +2,10 @@ const jsdom = require('jsdom');
|
||||
const { JSDOM } = jsdom;
|
||||
import config from '../config';
|
||||
import { INote } from '../models/note';
|
||||
import { Node } from './parser';
|
||||
import { intersperse } from '../prelude/array';
|
||||
import { MfmForest, MfmTree } from './parser';
|
||||
|
||||
export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => {
|
||||
export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => {
|
||||
if (tokens == null) {
|
||||
return null;
|
||||
}
|
||||
@ -14,11 +14,11 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
|
||||
|
||||
const doc = window.document;
|
||||
|
||||
function appendChildren(children: Node[], targetElement: any): void {
|
||||
for (const child of children.map(n => handlers[n.name](n))) targetElement.appendChild(child);
|
||||
function appendChildren(children: MfmForest, targetElement: any): void {
|
||||
for (const child of children.map(t => handlers[t.node.type](t))) targetElement.appendChild(child);
|
||||
}
|
||||
|
||||
const handlers: { [key: string]: (token: Node) => any } = {
|
||||
const handlers: { [key: string]: (token: MfmTree) => any } = {
|
||||
bold(token) {
|
||||
const el = doc.createElement('b');
|
||||
appendChildren(token.children, el);
|
||||
@ -58,7 +58,7 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
|
||||
blockCode(token) {
|
||||
const pre = doc.createElement('pre');
|
||||
const inner = doc.createElement('code');
|
||||
inner.innerHTML = token.props.code;
|
||||
inner.innerHTML = token.node.props.code;
|
||||
pre.appendChild(inner);
|
||||
return pre;
|
||||
},
|
||||
@ -70,41 +70,51 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
|
||||
},
|
||||
|
||||
emoji(token) {
|
||||
return doc.createTextNode(token.props.emoji ? token.props.emoji : `:${token.props.name}:`);
|
||||
return doc.createTextNode(token.node.props.emoji ? token.node.props.emoji : `:${token.node.props.name}:`);
|
||||
},
|
||||
|
||||
hashtag(token) {
|
||||
const a = doc.createElement('a');
|
||||
a.href = `${config.url}/tags/${token.props.hashtag}`;
|
||||
a.textContent = `#${token.props.hashtag}`;
|
||||
a.href = `${config.url}/tags/${token.node.props.hashtag}`;
|
||||
a.textContent = `#${token.node.props.hashtag}`;
|
||||
a.setAttribute('rel', 'tag');
|
||||
return a;
|
||||
},
|
||||
|
||||
inlineCode(token) {
|
||||
const el = doc.createElement('code');
|
||||
el.textContent = token.props.code;
|
||||
el.textContent = token.node.props.code;
|
||||
return el;
|
||||
},
|
||||
|
||||
math(token) {
|
||||
const el = doc.createElement('code');
|
||||
el.textContent = token.props.formula;
|
||||
el.textContent = token.node.props.formula;
|
||||
return el;
|
||||
},
|
||||
|
||||
link(token) {
|
||||
const a = doc.createElement('a');
|
||||
a.href = token.props.url;
|
||||
a.href = token.node.props.url;
|
||||
appendChildren(token.children, a);
|
||||
return a;
|
||||
},
|
||||
|
||||
mention(token) {
|
||||
const a = doc.createElement('a');
|
||||
const { username, host, acct } = token.props;
|
||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`;
|
||||
const { username, host, acct } = token.node.props;
|
||||
switch (host) {
|
||||
case 'github.com':
|
||||
a.href = `https://github.com/${username}`;
|
||||
break;
|
||||
case 'twitter.com':
|
||||
a.href = `https://twitter.com/${username}`;
|
||||
break;
|
||||
default:
|
||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`;
|
||||
break;
|
||||
}
|
||||
a.textContent = acct;
|
||||
return a;
|
||||
},
|
||||
@ -123,7 +133,7 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
|
||||
|
||||
text(token) {
|
||||
const el = doc.createElement('span');
|
||||
const nodes = (token.props.text as string).split('\n').map(x => doc.createTextNode(x));
|
||||
const nodes = (token.node.props.text as string).split('\n').map(x => doc.createTextNode(x));
|
||||
|
||||
for (const x of intersperse('br', nodes)) {
|
||||
el.appendChild(x === 'br' ? doc.createElement('br') : x);
|
||||
@ -134,15 +144,15 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
|
||||
|
||||
url(token) {
|
||||
const a = doc.createElement('a');
|
||||
a.href = token.props.url;
|
||||
a.textContent = token.props.url;
|
||||
a.href = token.node.props.url;
|
||||
a.textContent = token.node.props.url;
|
||||
return a;
|
||||
},
|
||||
|
||||
search(token) {
|
||||
const a = doc.createElement('a');
|
||||
a.href = `https://www.google.com/?#q=${token.props.query}`;
|
||||
a.textContent = token.props.content;
|
||||
a.href = `https://www.google.com/?#q=${token.node.props.query}`;
|
||||
a.textContent = token.node.props.content;
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user