Compare commits

..

141 Commits

Author SHA1 Message Date
796252357e Merge branch 'develop' 2019-05-10 17:33:21 +09:00
b0344d52e9 11.12.0 2019-05-10 17:32:57 +09:00
8e6da3a0d9 インスタンス運営者がピン留めユーザーを設定できるように
Related #4892
2019-05-10 17:30:28 +09:00
756e4eaeec テキストのリスト内で変数埋め込みできるように 2019-05-10 16:08:01 +09:00
3126d0730a MisskeyPagesで変数を並べ替えられるように 2019-05-10 16:04:32 +09:00
748e9f15df Add notes/unrenote API 2019-05-10 15:53:53 +09:00
7c714f5788 Improve MisskeyPages 2019-05-10 14:18:18 +09:00
d3c3ad839b Update ObjectStorage example (#4890) 2019-05-10 01:46:11 +09:00
168de3c316 Resolve #4870 2019-05-09 23:27:34 +09:00
9e20fc5c88 Validate Note on createNote (#4881) 2019-05-09 15:43:31 +09:00
1ff5151786 Fix: みつけるで人気のタグが表示されない (#4883) 2019-05-09 15:42:56 +09:00
a56738a331 Update README.md [AUTOGEN] (#4877) 2019-05-07 20:55:37 +09:00
2d04a3d6d2 Merge branch 'develop' 2019-05-07 18:56:33 +09:00
e0a55b9100 11.11.2 2019-05-07 18:56:19 +09:00
0a57eecb3a New Crowdin translations (#4812)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (English)
2019-05-07 18:49:51 +09:00
51f98020f6 Fix #4874 2019-05-07 18:48:38 +09:00
70123805e1 Fix #4866 2019-05-07 18:23:12 +09:00
1448b11d00 Clean up 2019-05-07 18:20:18 +09:00
490b81ed05 Fix #4873 2019-05-07 18:14:37 +09:00
96f675abed Fix: IPv4 onlyホストからDualstackホストにAP deliverできない (#4872) 2019-05-07 17:49:25 +09:00
4cd451c613 2段階認証コードの入力フォームタイプの訂正 (#4869)
Fix #4849, Resolve #4848
2019-05-07 08:45:29 +09:00
187792dfc4 Fix bug 2019-05-06 20:09:13 +09:00
3dde561fe5 Resolve #4864 (#4865) 2019-05-06 17:28:55 +09:00
a75ec45172 Merge branch 'develop' 2019-05-05 20:36:25 +09:00
3732ddf75f 11.11.1 2019-05-05 20:36:11 +09:00
b8d8097734 Fix bug 2019-05-05 20:36:09 +09:00
1092532292 Merge branch 'develop' 2019-05-05 20:33:44 +09:00
8a79ba0e2b 11.11.0 2019-05-05 20:33:29 +09:00
ca2949fbb4 MisskeyPagesにリストから選択関数を追加 2019-05-05 20:31:15 +09:00
17b373ac07 ✌️ 2019-05-05 20:16:05 +09:00
7aa66f438f Resolve #4853 2019-05-05 20:12:35 +09:00
73641fd78d Fix #4852 2019-05-05 15:17:29 +09:00
64aac9d6ad Fix #4862 2019-05-05 15:12:25 +09:00
2be13736c8 Update user-profile.ts
#4809
2019-05-05 09:42:38 +09:00
ff4f5fec1d meidg (#4835) 2019-05-05 09:29:15 +09:00
7d64f8abe4 外部サービス連携後のPackedUserがその情報を持つように (#4850) 2019-05-05 09:28:55 +09:00
88e6929e9f 外部サービス連携ログインリンクにアイコン追加 (#4858) 2019-05-05 09:28:23 +09:00
5fb0a995dd 様々な修正 (#4859)
Typo, Redundant code, Syntax error の修正
2019-05-05 09:27:55 +09:00
58a04ce1a5 ログアウトの処理と外部サービス連携Viewがセッションクッキーを作らないように (#4856) 2019-05-05 04:04:30 +09:00
f74e0d9123 Merge branch 'develop' 2019-05-04 15:21:51 +09:00
c6249b82d4 11.10.1 2019-05-04 15:21:35 +09:00
d6131c0b09 MisskeyPagesでページブロックを削除できなくなっていた問題を修正 2019-05-04 15:14:02 +09:00
e62c810b7c Use node 12 2019-05-04 14:53:46 +09:00
2851a1a7ef Update dependencies 🚀 2019-05-04 14:47:28 +09:00
12cd2709d6 Merge branch 'develop' 2019-05-03 18:58:54 +09:00
bf54e58873 11.10.0 2019-05-03 18:58:09 +09:00
6b473e3a5c Fix #4840 2019-05-03 18:55:24 +09:00
4b68abd963 割った余りを求める関数をMisskeyPagesに追加 2019-05-03 18:48:40 +09:00
0e764a2b3e Fix external service authentication (#4846) 2019-05-03 18:38:19 +09:00
9d1ed1eb0d Some import and export fixes (#4842)
* Fix: Mastodon v2.8.0 のフォローリストがインポートできない

* Fix: エクスポートリクエストに失敗してもエラーが出ない (#4821)

* エクスポートファイルでは同一ハッシュチェックをしないように
2019-05-03 18:33:25 +09:00
a09a3465a2 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-05-03 14:49:40 +09:00
001969efaf Improve usability 2019-05-03 14:49:22 +09:00
e7c515da9a Update README.md [AUTOGEN] (#4839) 2019-05-03 14:34:41 +09:00
8367c7dd49 Update README.md [AUTOGEN] (#4837) 2019-05-03 14:32:09 +09:00
55e6cae240 Fix #4834 2019-05-03 09:16:31 +09:00
5553c3fb17 Improve usability 2019-05-03 08:27:46 +09:00
026265cb1e Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-05-03 08:22:51 +09:00
289c76a802 Disable ServiceWorker 2019-05-03 08:22:44 +09:00
7c1bc1d6bc Update README.md [AUTOGEN] (#4832) 2019-05-02 19:44:59 +09:00
90cf0d32b5 Update README.md [AUTOGEN] (#4827) 2019-05-02 18:48:48 +09:00
88d934f922 Merge branch 'develop' 2019-05-02 17:57:29 +09:00
abf11bb03c 11.9.0 2019-05-02 17:57:13 +09:00
2d1f50303d Improve MisskeyPages 2019-05-02 17:55:59 +09:00
9fb7c4091f Merge branch 'develop' 2019-05-02 17:18:34 +09:00
2fdec27ab0 11.8.1 2019-05-02 17:18:11 +09:00
aff56469ed Fix bug 2019-05-02 17:15:14 +09:00
419cb7fbad Remove unwanted ! character in user token regex (#4830) 2019-05-02 06:24:32 +09:00
7882851539 Fix TypeScript semantic error (#4828)
ビルドの時こういうエラーが出てます
src/misc/aiscript/evaluator.ts(2,29): error TS7016: Could not find a declaration file for module 'seedrandom'
2019-05-01 22:56:18 +09:00
358299cf0e Merge branch 'develop' 2019-05-01 20:59:18 +09:00
5a2af24869 11.8.0-2 2019-05-01 20:58:59 +09:00
6d35872af5 Merge branch 'develop' 2019-05-01 20:57:23 +09:00
559dfdaa80 11.8.0 2019-05-01 20:56:50 +09:00
79c49bc926 ページのソースを見れるように 2019-05-01 20:48:56 +09:00
76c538ad25 🎨 2019-05-01 19:50:56 +09:00
e76e358d98 Fix bug 2019-05-01 19:50:52 +09:00
76f37671b4 🎨 2019-05-01 19:45:05 +09:00
a9fc176c3c Fix types 2019-05-01 19:31:34 +09:00
8b13e3c327 Refactor 2019-05-01 19:20:47 +09:00
c3cd6ad2d2 Refactoring 2019-05-01 18:33:11 +09:00
3444b9c9c8 Add splitStrByLine function 2019-05-01 16:33:54 +09:00
ca6fc9cd79 Remove strConcat function 2019-05-01 16:07:05 +09:00
4747ae8b61 Improve AiScript 2019-05-01 15:17:24 +09:00
b6c50d63a0 Update ja-JP.yml 2019-05-01 15:04:26 +09:00
52ebf2055e Improve AiScript 2019-05-01 14:54:34 +09:00
d0af2c2a98 Improve MisskeyPages 2019-05-01 10:37:25 +09:00
10216af48a Improve MisskeyPages 2019-05-01 10:05:33 +09:00
d0c8d537f5 Refactor 2019-05-01 09:15:29 +09:00
1903aaf351 Improve API doc
Fix #4825
2019-05-01 04:44:46 +09:00
e6cdf1b995 Fix: mention (あなた宛て) streaming にミュートが効かない (#4823) 2019-04-30 15:53:13 +09:00
2900d22cdc Update CHANGELOG.md 2019-04-30 12:38:07 +09:00
dc072d4706 Merge branch 'develop' 2019-04-30 12:21:31 +09:00
8ae14f146b 11.7.0 2019-04-30 12:20:59 +09:00
57444c6c3f Update page-editor.el.post.vue 2019-04-30 12:17:53 +09:00
759719d124 Improve MisskeyPages 2019-04-30 12:15:41 +09:00
59782973be 🎨 2019-04-30 10:08:55 +09:00
6c647ea91c Improve usability 2019-04-30 07:55:40 +09:00
c9763dabe1 変換関数を追加 2019-04-30 07:49:46 +09:00
da82754659 🎨 2019-04-30 07:43:56 +09:00
a3cc0ad18b 🎨 2019-04-30 07:41:57 +09:00
2e8e5c2751 Improve MisskeyPages
* ifブロック を追加
* ボタンやスイッチなどのテキストに変数使えるようにした
2019-04-30 06:40:02 +09:00
a60d83b101 Fix ogp 2019-04-29 18:15:12 +09:00
6d45265763 Use bigint 2019-04-29 17:38:31 +09:00
42e84b77e1 Merge branch 'develop' 2019-04-29 15:26:43 +09:00
24121cfadb 11.6.0 2019-04-29 15:26:09 +09:00
93093dd288 🎨 2019-04-29 15:20:37 +09:00
53381c04e6 🎨 2019-04-29 15:17:31 +09:00
c6b64e57f1 いくつかの文字列関数を追加 2019-04-29 15:13:04 +09:00
9158426d0a Improve usability 2019-04-29 14:51:04 +09:00
cef26853df Improve usability 2019-04-29 14:46:35 +09:00
c0673884c5 🎨 2019-04-29 14:38:06 +09:00
c98eb64598 Fix bug 2019-04-29 14:37:58 +09:00
8836bd4f3b Merge branch 'develop' 2019-04-29 11:11:17 +09:00
a58df29208 11.5.1 2019-04-29 11:10:59 +09:00
0d64a17d86 Update page.vue 2019-04-29 11:10:50 +09:00
c886c09cdb MisskeyPagesで値が0の変数が表示されない問題を修正 2019-04-29 11:08:35 +09:00
e86d0007c6 Fix error 2019-04-29 11:03:05 +09:00
624ee76e71 Fix bug 2019-04-29 11:00:41 +09:00
9406079cb7 Merge branch 'develop' 2019-04-29 09:29:48 +09:00
cd2de7f893 11.5.0 2019-04-29 09:29:21 +09:00
342061803e Update dependencies 🚀 2019-04-29 09:28:13 +09:00
05b8111c19 Pages (#4811)
* wip

* wip

* wip

* Update page-editor.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update page-editor.variable.core.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update aiscript.ts

* wip

* Update package.json

* wip

* wip

* wip

* wip

* wip

* Update page.vue

* wip

* wip

* wip

* wip

* more info

* wip fn

* wip

* wip

* wip
2019-04-29 09:11:57 +09:00
747a0b1791 Update define.ts 2019-04-28 19:56:41 +09:00
c05586b53a Improve performance 2019-04-27 11:17:03 +09:00
0cfca4a618 Fix official instance address (#4805)
いくつかのURLが misskey.xyz になってたままだったので misskey.io に訂正
2019-04-26 18:29:56 +09:00
2b8187f7ab Fix bug 2019-04-26 00:54:11 +09:00
291e7e7943 おすすめユーザーに自分自身を含まないように (#4803)
Fix #4790
2019-04-26 00:52:58 +09:00
535d10f469 Improve API console 2019-04-25 14:40:42 +09:00
4cb58c0892 Refactor 2019-04-25 13:27:07 +09:00
6721d27e3f Improve hashtag API 2019-04-25 13:25:10 +09:00
da9dd7c423 Improve API definition 2019-04-25 12:56:52 +09:00
867eb41618 Fix #4415 2019-04-25 12:37:13 +09:00
6fdff13480 Update example.yml 2019-04-25 12:24:18 +09:00
e581ead1ed Update CHANGELOG.md 2019-04-25 12:22:03 +09:00
7495206db2 Merge branch 'develop' 2019-04-25 07:48:29 +09:00
fe87d16d46 11.4.0 2019-04-25 07:48:12 +09:00
0db54386cd Resolve #3119 2019-04-25 07:46:39 +09:00
772258b0b8 Fix #4793 2019-04-25 04:32:01 +09:00
3ef002e14d Fix bug 2019-04-25 04:27:34 +09:00
b90d473ae5 Fix layout 2019-04-25 04:26:58 +09:00
f5091d524b Refactorgin 2019-04-25 04:17:03 +09:00
ee5720df2c Fix #4704 (#4797)
*  Fix #4632

* Fix #4795
2019-04-25 04:07:39 +09:00
196 changed files with 6765 additions and 933 deletions

View File

@ -84,40 +84,54 @@ redis:
drive:
storage: 'fs'
# OR
# OR
# storage: 'minio'
# bucket:
# prefix:
# config:
# endPoint:
# port:
# useSSL:
# accessKey:
# secretKey:
#drive:
# storage: 'minio'
# bucket:
# prefix:
# config:
# endPoint:
# port:
# useSSL:
# accessKey:
# secretKey:
# S3 example
# storage: 'minio'
# bucket: bucket-name
# prefix: files
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3/GCS example
#
# * Replace <endpoint> to
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# GCS: use 'storage.googleapis.com'
#
# * Replace <region> to
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# GCS: not needed (just delete the region line)
#
#drive:
# storage: 'minio'
# bucket: bucket-name
# prefix: files
# baseUrl: https://bucket-name.<endpoint>
# config:
# endPoint: <endpoint>
# region: <region>
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3 example (with CDN, custom domain)
# storage: 'minio'
# bucket: drive.example.com
# prefix: files
# baseUrl: https://drive.example.com
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3/GCS example (with CDN, custom domain)
#
#drive:
# storage: 'minio'
# bucket: drive.example.com
# prefix: files
# baseUrl: https://drive.example.com
# config:
# endPoint: <endpoint>
# region: <region>
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
@ -132,6 +146,9 @@ drive:
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
# ┌─────────────────────┐
@ -146,3 +163,6 @@ autoAdmin: true
# Clustering
#clusterLimit: 1
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4

1
.gitignore vendored
View File

@ -20,3 +20,4 @@ api-docs.json
yarn.lock
.DS_Store
/files
ormconfig.json

View File

@ -1 +1 @@
v11.7.0
v12.1.0

View File

@ -5,8 +5,46 @@ If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`)
Migration
------------------------------
#### 1
`ormconfig.json`という名前で、Misskeyのインストール場所(package.jsonとかがあるディレクトリ)に新たなファイルを作る。中身は次のようにします:
``` json
{
"type": "postgres",
"host": "PostgreSQLのホスト",
"port": 5432,
"username": "PostgreSQLのユーザー名",
"password": "PostgreSQLのパスワード",
"database": "PostgreSQLのデータベース名",
"entities": ["src/models/entities/*.ts"],
"migrations": ["migration/*.ts"],
"cli": {
"migrationsDir": "migration"
}
}
```
上記の各種PostgreSQLの設定(ポートも)は、設定ファイルに書いてあるものをコピーしてください。
#### 2
```
npm i -g ts-node
```
#### 3
```
ts-node ./node_modules/typeorm/cli.js migration:run
```
How to migrate to v11 from v10
------------------------------
### 移行の注意点
**以下のデータは引き継がれません**
* 通知
* リモートの投稿
* リバーシの対局
### 手順
1. v11をインストールしたい場所に syuilo/misskey をクローン
2. config を設定する
* PostgreSQL(`db`)の設定とは別に、v10からMongoDBの設定をコピペしてくる(例は下にあります)
@ -35,6 +73,180 @@ mongodb:
8. master ブランチに戻す
9. enjoy
11.12.0 (2019/05/10)
--------------------
### 注意
このアップデートを適用した後、プロセスを起動(もしくは再起動)する前に[マイグレーション](#migration)の手順を実行してください
### Improvements
* インスタンス運営者がおすすめアカウントを設定できるように
* MisskeyPagesでNAME環境変数がNULLにならないように
* MisskeyPagesにNULL環境変数を追加
* MisskeyPagesで変数を並べ替えられるように
* MisskeyPagesのテキストのリスト内で変数埋め込みできるように
* 自分の指定した投稿のRenoteを全て解除するAPIを追加
### Fixes
* Noteをpull取得した時にhost名がvalidateされていない問題を修正
* みつけるで人気のタグが表示されない問題を修正
### その他
* アカウントのisVerifiedフラグを廃止
11.11.2 (2019/05/07)
--------------------
### Fixes
* IPv4 onlyホストからDualstackホストにAP deliverできない問題を修正
* ストリーミングに接続するまでラグがある問題を修正
* 2段階認証のコードが0から始まる時正しく入力できない問題を修正
* ユーザーの更新日時が新しい順で更新日時がnullのユーザーが先頭に来る問題を修正
* 値選択時の問題を修正
* リバーシでマップの変更が反映されない問題を修正
* リバーシで対局終了直後に盤面を巻き戻してもすぐ最終ターンまでリセットされる問題を修正
11.11.1 (2019/05/05)
--------------------
### Fixes
* MisskeyPagesのリストから選択関数が使えない問題を修正
11.11.0 (2019/05/05)
--------------------
### Improvements
* MisskeyPagesにリストから選択関数を追加
* MisskeyPagesに確率を指定できるテキストランダム選択関数を追加
* 外部サービス連携ログインリンクにアイコン追加
### Fixes
* MisskeyPagesでifを入れ子にできなくなっていた問題を修正
* MisskeyPagesで数値入力を作成するとテキスト入力になる問題を修正
* 外部サービス連携に関する問題を修正
11.10.1 (2019/05/04)
--------------------
### Fixes
* MisskeyPagesでページブロックを削除できなくなっていた問題を修正
### その他
* Node.js v12対応
11.10.0 (2019/05/03)
--------------------
### 注意
このアップデートを適用した後、プロセスを起動(もしくは再起動)する前に[マイグレーション](#migration)の手順を実行してください
### Improvements
* MisskeyPagesに割った余りを求める関数を追加
* Mastodon v2.8.0 のフォローリストをインポートできるように
* エクスポートリクエストに失敗したらエラーを表示するように
* エクスポートファイルでは同一ハッシュチェックをしないように
### Fixes
* 2段階認証を設定するとログインできなくなる問題を修正
* ファイルをアップロードできないことがある問題を修正
* リモートファイルをキャッシュしない設定だとサムネイル時にオリジナル画像が表示されない問題を修正
* 外部サービス連携の不具合を修正
11.9.0 (2019/05/02)
-------------------
### Improvements
* MisskeyPagesで編集時にページブロックをドラッグで並べ替えられるように
* MisskeyPagesにカウンターボタンブロックを追加
11.8.1 (2019/05/02)
-------------------
### Fixes
* リモートファイルをキャッシュしないオプション有効時にファイルが作成できない問題を修正
11.8.0-2 (2019/05/01)
-------------------
* 11.8.0 のリリース内容が 11.7.0 と同一だったのを修正
11.8.0 (2019/05/01)
-------------------
### Improvements
* MisskeyPagesで関数を作成できるように
* MisskeyPagesでソースを表示できるように
* MisskeyPagesにシードを与えるランダム関数を追加
* MisskeyPagesに複数行テキストをテキストのリストに変換する関数を追加
### Fixes
* APIドキュメントが見れなくなっていたのを修正
* mention (あなた宛て) streaming にミュートが効かない問題を修正
* デザインの調整
11.7.0 (2019/04/30)
-------------------
### Improvements
* MisskeyPagesに ifブロック を追加
* MisskeyPagesに テキストエリア を追加
* MisskeyPagesに 複数行テキスト入力 を追加
* MisskeyPagesに 投稿フォーム を追加
* MisskeyPagesに 変換系関数 を追加
* MisskeyPagesに 環境変数 URL を追加
* MisskeyPagesでボタンやスイッチなどのテキストに変数使えるように
### Fixes
* OGPのサイト名を修正
* デザインの調整
11.6.0 (2019/04/29)
-------------------
### Improvements
* AiScriptにいくつかの文字列操作関数を追加
* ページ編集画面にページへのリンクを表示するように
### Fixes
* MisskeyPagesで数値入力が文字列として扱われる問題を修正
* デザインの調整
11.5.1 (2019/04/29)
-------------------
### Fixes
* MisskeyPagesで環境変数を別の変数内で使えない問題を修正
* MisskeyPagesで値が0の変数が表示されない問題を修正
11.5.0 (2019/04/29)
-------------------
### 注意
このアップデートを適用した後、プロセスを起動(もしくは再起動)する前に[マイグレーション](migration)の手順を実行してください
### New features
#### MisskeyPages
ページ(記事)を作成できるように。
* 後から何度でも編集できる
* アイキャッチを設定できる
* フォントを設定できる
* 画像を好きな位置に挿入できる
* URLを決められる
* タイトルを設定できる
* 見出しを設定できる
* ページの要約を設定できる(URLプレビュー時などに便利)
* 変数や式(aka AiScript)を使用して動的なページも作れる
* 目次自動生成(coming soon)
ページを気に入ったら「いいね」しよう (coming soon)
### Improvements
* APIコンソールでパラメータテンプレートを表示するように
### Fixes
* おすすめユーザーに自分自身が含まれる問題を修正
* ユーザーサジェストで表示名が変わらない問題を修正
11.4.0 (2019/04/25)
-------------------
### Improvements
* 検索でローカルの投稿のみに絞れるように
* 検索で特定のインスタンスの投稿のみに絞れるように
* 検索で特定のユーザーの投稿のみに絞れるように
### Fixes
* 投稿が増殖する問題を修正
* ストリームで過去の投稿が流れてくる問題を修正
* モバイル版のユーザーページで遷移してもユーザー名が変わらない問題を修正
* お知らせを切り替えても内容が変わらない問題を修正
11.3.1 (2019/04/24)
-------------------
### Fixes

View File

@ -1,4 +1,4 @@
FROM node:11-alpine AS base
FROM node:12.1-alpine AS base
ENV NODE_ENV=production

View File

@ -1,6 +1,6 @@
<a href="https://xn--931a.moe/"><img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/></a>
[![Misskey](/assets/title.png)](https://misskey.xyz/)
[![Misskey](/assets/title.png)](https://misskey.io/)
================================================================
[![CircleCI](https://img.shields.io/circleci/project/github/syuilo/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/syuilo/misskey)
@ -10,7 +10,7 @@
**A forever evolving, sophisticated microblogging platform.**
<p align="justify">
<a href="https://misskey.xyz">Misskey</a> is a decentralized microblogging platform born on Earth.
<a href="https://misskey.io">Misskey</a> is a decentralized microblogging platform born on Earth.
Since it exists within the Fediverse (a universe where various social media platforms are organized),
it is mutually linked with other social media platforms.
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://joinmisskey.github.io/">Find an instance!</a>
@ -105,6 +105,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
@ -112,62 +113,65 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1.png?token-time=2145916800&token-hash=FMV7cPKBD1TU2WTbl1jg6AcdKSvTb2BSFcDhgc-EO8w%3D" alt="gutfuckllc" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td>
<td><img src="https://c8.patreon.com/2/200/18833336" alt="itiradi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1.jpe?token-time=2145916800&token-hash=UQRWf01TwHDV4Cls1K0YAOAjM29ssif7hLVq0ESQ0hs%3D" alt="nemu" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
<td><a href="https://www.patreon.com/user?u=18833336">itiradi</a></td>
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
<td><a href="https://www.patreon.com/Corset">CG</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Wed, 24 Apr 2019 05:56:07 UTC
**Last updated:** Tue, 07 May 2019 11:55:07 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@ -61,6 +61,7 @@ common:
month-and-day: "{day}. {month}."
trash: "Koš"
drive: "Disk"
pages: "Stránky"
messaging: "Konverzace"
home: "Domů"
deck: "Deck"
@ -71,12 +72,20 @@ common:
favorites: "Oblíbené"
permissions:
"read:account": "Zobrazit informace o účtu"
"write:account": "Narábět s účtem"
"read:blocks": "Prohlížet blokování"
"write:blocks": "Narábět s blokováním"
"read:drive": "Prohlížet Disk"
"write:drive": "Pracovat s Diskem"
"read:favorites": "Prohlížet oblíbené"
"write:favorites": "Narábět s oblíbeními"
"read:following": "Prohlížet následování"
"write:following": "Pracovat s následováním"
"read:messaging": "Prohlížet konverzaci"
"write:messaging": "Pracovat s konverzaci"
"read:mutes": "Prohlížet ztlumené"
"write:mutes": "Narábět s utíšeními"
"write:notes": "Narábět s poznámkami"
"read:notifications": "Prohlížet oznámení"
"write:notifications": "Pracovat s oznámeními"
"read:reactions": "Prohlížet reakce"
@ -377,6 +386,7 @@ common/views/components/theme.vue:
installed: "\"{}\" byl nainstalován"
create-a-theme: "Vytvořit motiv"
save-created-theme: "Uložit motiv"
text-color: "Barva textu"
base-theme: "Základní vzhled"
base-theme-light: "Světlý"
base-theme-dark: "Tmavý"
@ -454,6 +464,7 @@ common/views/components/user-menu.vue:
suspend: "Zmrazit"
common/views/components/poll.vue:
vote-count: "{} hlasů"
total-votes: "{} hlasů celkem"
vote: "Hlasovat"
show-result: "Podívat se na výsledky"
voted: "Už jste hlasovaly"
@ -464,10 +475,21 @@ common/views/components/poll.vue:
remaining-seconds: "zbývá {s} sekund"
common/views/components/poll-editor.vue:
no-only-one-choice: "Musíte vybrat alespoň dvě možnosti"
choice-n: "Volba {}"
remove: "Odstranit tuto možnost"
add: "+ Přidat možnost"
destroy: "Zahodit dotazník"
expiration: "Termín"
infinite: "Nekonečne"
at: "Výběr data a času"
deadline-date: "Termín ukončení"
interval: "Trvání"
second: "Sekunda"
minute: "Minuta"
hour: "Hodina"
day: "Ne"
common/views/components/reaction-picker.vue:
choose-reaction: "Vyberte svoji reakci"
common/views/components/emoji-picker.vue:
custom-emoji: "Emoji"
people: "Lidé"
@ -1198,3 +1220,42 @@ deck/deck.user-column.vue:
activity: "Aktivita"
dev/views/new-app.vue:
app-name-desc: "Jméno vaší aplikace"
pages:
title: "Titulek"
blocks:
post: "Formulář pro psaní"
_post:
text: "Obsah"
_textInput:
text: "Titulek"
_textareaInput:
text: "Titulek"
_numberInput:
text: "Titulek"
_switch:
text: "Titulek"
_counter:
text: "Titulek"
_button:
text: "Titulek"
_action:
_dialog:
content: "Obsah"
script:
categories:
random: "Náhodně"
list: "Seznamy"
blocks:
_join:
arg1: "Seznamy"
random: "Náhodně"
_randomPick:
arg1: "Seznamy"
_dailyRandomPick:
arg1: "Seznamy"
_seedRandomPick:
arg2: "Seznamy"
_pick:
arg1: "Seznamy"
types:
array: "Seznamy"

View File

@ -53,6 +53,7 @@ common:
month-and-day: "{day}/{month}"
trash: "Papierkorb"
drive: "Drive"
pages: "Seite"
messaging: "Unterhaltungen"
home: "Home"
deck: "Stapel"
@ -64,6 +65,7 @@ common:
permissions:
"read:account": "Accountinformationen anzeigen."
"write:account": "Accountinformationen bearbeiten."
"read:blocks": "Blöcke anzeigen"
"read:drive": "Dateien anzeigen"
"write:drive": "Dateien bearbeiten"
"read:favorites": "Favoriten anzeigen"
@ -718,25 +720,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "Hast Du einen Fehler gefunden oder Lust, diese Dokumentation zu verbessern?"
edit-this-page-on-github-link: "Seite auf GitHub bearbeiten!"
api:
entities:
properties: "Eigenschaften"
endpoints:
params: "Parameter"
no-params: "Keine Parameter."
res: "Antwort"
require-credential: "Dieser Endpunkt erfordert eine Authentifizierung."
require-permission: "Dieser Endpunkt erfordert die {permission} Berechtigung."
has-limit: "Es gibt eine Ratenbegrenzung."
duration-limit: "Es sind maximal {max} Anfragen pro {duration} Millisekunden möglich."
min-interval-limit: "Es ist nur eine Anfrage alle {interval} Millisekunden möglich."
show-src: "Quellcode anzeigen."
show-src-link: "Quellcode auf GitHub anzeigen"
generated: "Dieses Dokument wird automatisch anhand der API-Definition generiert."
props:
name: "Name"
type: "Typ"
description: "Beschreibung"
dev/views/index.vue:
manage-apps: "Anwendungen verwalten"
dev/views/apps.vue:
@ -753,3 +736,24 @@ dev/views/new-app.vue:
authority: "Berechtigungen"
authority-desc: "Nur die hier eingetragenen Berechtigungen, werden per API zur Verfügung stehen."
authority-warning: "Dies kann auch nach dem erstellen der Anwendung geändert werden, allerdings werden dann alle bisher generierten Token ungültig."
pages:
blocks:
post: "\"Neuer Beitrag\"-Formular"
script:
categories:
random: "Zufällige Auswahl"
list: "Listen"
blocks:
_join:
arg1: "Listen"
random: "Zufällige Auswahl"
_randomPick:
arg1: "Listen"
_dailyRandomPick:
arg1: "Listen"
_seedRandomPick:
arg2: "Listen"
_pick:
arg1: "Listen"
types:
array: "Listen"

View File

@ -34,6 +34,7 @@ common:
signup: "Sign up"
signout: "Logout"
reload-to-apply-the-setting: "You'll need to reload the page to reflect this setting. Do you want to reload it now?"
fetching-as-ap-object: "Inquiring to union"
got-it: "Got it!"
customization-tips:
title: "Customization tips"
@ -61,6 +62,7 @@ common:
month-and-day: "{month}/{day}"
trash: "Trash"
drive: "Drive"
pages: "Pages"
messaging: "Talk"
home: "Home"
deck: "Deck"
@ -1621,25 +1623,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "Found an error, or do you want to contribute to the documentation?"
edit-this-page-on-github-link: "Edit this page at GitHub!"
api:
entities:
properties: "Properties"
endpoints:
params: "Parameters"
no-params: "No parameter."
res: "Response"
require-credential: "This endpoint requires the authentication information."
require-permission: "This endpoint requires {permission} permission."
has-limit: "There is a rate limit."
duration-limit: "If you have sent your requests more than {max} times in {duration} milliseconds, you will be unable to send more requests."
min-interval-limit: "If {interval} milliseconds haven't passed since the last request, you can't send a request."
show-src: "You can view the source code for this endpoint."
show-src-link: "See the code on GitHub"
generated: "This document is generated by the API definition."
props:
name: "Name"
type: "Type"
description: "Description"
dev/views/index.vue:
manage-apps: "Manage apps"
dev/views/apps.vue:
@ -1648,13 +1631,252 @@ dev/views/apps.vue:
app-missing: "No apps"
dev/views/new-app.vue:
new-app: "New Application"
new-app-info: "You can also create an application with the API. (app/create)"
create-app: "Creating application"
app-name: "Application name"
app-name-placeholder: "ex) Misskey for iOS"
app-name-desc: "The name of your app"
app-overview: "Application summary"
app-overview-placeholder: " ex) Misskey iOS Client."
app-overview-desc: "A brief description, or an introduction of your app."
callback-url: "The callback URL (optional)"
callback-url-placeholder: "ex) https://your.app.example.com/callback.php"
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
authority: "Permissions"
authority-desc: "Only the functions requested here can be accessed via the API."
authority-warning: "You can change it even after creating the application, but if you give different permissions, all user keys associated at that time will be invalidated."
pages:
new-page: "Create a page"
edit-page: "Edit a page"
read-page: "Viewing the source"
page-created: "Created the page!"
page-updated: "Updated the page"
are-you-sure-delete: "Do you want to delete this page?"
page-deleted: "The page has been deleted"
edit-this-page: "Edit this page"
view-source: "View Source"
view-page: "View page"
inspector: "Inspector"
content: "Page block"
variables: "Variables"
more-details: "Description"
title: "Title"
url: "Page URL"
summary: "Summary of page"
align-center: "Center align"
font: "Font"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
set-eye-catching-image: "Set an eye-catching image"
remove-eye-catching-image: "Delete an eye-catching image"
choose-block: "Add a block"
select-type: "Select a type"
enter-variable-name: "Please choose a variable name"
the-variable-name-is-already-used: "This variable name is already used"
content-blocks: "Content"
input-blocks: "Input"
special-blocks: "Special"
post-from-post-form: "Post this content"
posted-from-post-form: "Posted!"
blocks:
text: "Text"
textarea: "Text area"
section: "Section"
image: "Images"
button: "Button"
if: "If"
_if:
variable: "Variables"
post: "Post form"
_post:
text: "Content"
textInput: "Text input"
_textInput:
name: "Variable name"
text: "Title"
default: "Default value"
textareaInput: "Multiple type text input"
_textareaInput:
name: "Variable name"
text: "Title"
default: "Default value"
numberInput: "Numeric input"
_numberInput:
name: "Variable name"
text: "Title"
default: "Default value"
switch: "Switch"
_switch:
name: "Variable name"
text: "Title"
default: "Default value"
counter: "Counter"
_counter:
name: "Variable name"
text: "Title"
inc: "Increase number"
_button:
text: "Title"
action: "Operation when the button pressed"
_action:
dialog: "Show a dialog"
_dialog:
content: "Content"
resetRandom: "Reset a random number"
script:
categories:
flow: "Control"
logical: "Logical operation"
operation: "Compute"
comparison: "Compare"
random: "Random"
value: "Value"
fn: "Function"
text: "Text operation"
convert: "Variable"
list: "Lists"
blocks:
text: "Text"
multiLineText: "Text (Multiple lines)"
textList: "List of text"
_textList:
info: "Separate each one with a newline"
strLen: "Length of text"
_strLen:
arg1: "Text"
strPick: "Extract character"
_strPick:
arg1: "Text"
arg2: "Position of character"
strReplace: "Replace string(s)"
_strReplace:
arg1: "Text"
arg2: "Before replacement"
arg3: "After replacement"
strReverse: "Flip text"
_strReverse:
arg1: "Text"
_join:
arg1: "Lists"
arg2: "Separator"
add: "+ Plus"
_add:
arg1: "A"
arg2: "B"
subtract: "- Minus"
_subtract:
arg1: "A"
arg2: "B"
multiply: "× Multiply"
_multiply:
arg1: "A"
arg2: "B"
divide: "÷ Divide"
_divide:
arg1: "A"
arg2: "B"
_remind:
arg1: "A"
arg2: "B"
eq: "A and B are equal"
_eq:
arg1: "A"
arg2: "B"
notEq: "A and B are different"
_notEq:
arg1: "A"
arg2: "B"
and: "A and B"
_and:
arg1: "A"
arg2: "B"
or: "A or B"
_or:
arg1: "A"
arg2: "B"
lt: "A is smaller than B"
_lt:
arg1: "A"
arg2: "B"
gt: "A is bigger than B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "A is smaller or same than B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: "A is bigger or same than B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Branch"
_if:
arg1: "If"
arg2: "then"
arg3: "else"
not: "denial"
_not:
arg1: "denial"
random: "Random"
_random:
arg1: "Probability"
rannum: "Random number"
_rannum:
arg1: "Minimum"
arg2: "Maximum"
randomPick: "Choose at random from the list"
_randomPick:
arg1: "Lists"
dailyRandom: "Random (Daily for each user)"
_dailyRandom:
arg1: "Probability"
dailyRannum: "Random number (Daily for each user)"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Choose at random from the list (Daily for each user)"
_dailyRandomPick:
arg1: "Lists"
_seedRandom:
arg1: "Seed"
arg2: "Probability"
_seedRannum:
arg1: "Seed"
arg2: "Minimum"
arg3: "Maximum"
_seedRandomPick:
arg1: "Seed"
arg2: "Lists"
_DRPWPM:
arg1: "List of text"
_pick:
arg1: "Lists"
arg2: "Position"
number: "Number"
stringToNumber: "Text to number"
_stringToNumber:
arg1: "Text"
numberToString: "Number to text"
_numberToString:
arg1: "Number"
splitStrByLine: "Split the text by lines"
_splitStrByLine:
arg1: "Text"
ref: "Variables"
fn: "Function"
_fn:
slots: "Slots"
arg1: "Output"
for: "Repeat"
thereIsEmptySlot: "Slot {slot} is empty!"
types:
string: "Text"
number: "Number"
boolean: "Flag"
array: "Lists"
stringArray: "List of text"
emptySlot: "Empty slot"
enviromentVariables: "Environment variable"
pageVariables: "Page element"
argVariables: "Input slot"

View File

@ -969,3 +969,24 @@ deck:
rename: "Renombrar"
deck/deck.user-column.vue:
activity: "Actividad"
pages:
blocks:
post: "Formulario"
script:
categories:
random: "Aleatorio"
list: "Listas"
blocks:
_join:
arg1: "Listas"
random: "Aleatorio"
_randomPick:
arg1: "Listas"
_dailyRandomPick:
arg1: "Listas"
_seedRandomPick:
arg2: "Listas"
_pick:
arg1: "Listas"
types:
array: "Listas"

View File

@ -60,6 +60,7 @@ common:
month-and-day: "{day}-{month}"
trash: "Corbeille"
drive: "Drive"
pages: "Pages"
messaging: "Conversations"
home: "Principal"
deck: "Deck"
@ -1469,6 +1470,7 @@ mobile/views/pages/drive.vue:
contextmenu:
upload: "Téléverser un fichier"
create-folder: "Créer un dossier"
rename-folder: "Renommer le dossier"
mobile/views/pages/user-lists.vue:
title: "Listes"
mobile/views/pages/signup.vue:
@ -1570,25 +1572,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "Vous avez trouvé une erreur ou vous voulez contribuer à la documentation ?"
edit-this-page-on-github-link: "Éditez cette page sur GitHub !"
api:
entities:
properties: "Propriétés"
endpoints:
params: "Paramètres"
no-params: "Aucun paramètre"
res: "Réponse"
require-credential: "Ce point de communication nécessite une authentification."
require-permission: "Ce point de communication nécessite la permission {permission}."
has-limit: "Il ya un taux limite."
duration-limit: "Si vous avez envoyé plus de {max} requêtes en {duration} millisecondes, vous ne serez pas en mesure d'envoyer d'autres requêtes."
min-interval-limit: "Vous ne pourrez pas effectuer une nouvelle requête si {interval} millisecondes ne se sont pas écoulées depuis la dernière demande."
show-src: "Vous pouvez voir le code source ce point de communication."
show-src-link: "Consulter le code sur GitHub"
generated: "Ce document est généré à partir de la définition de lAPI."
props:
name: "Nom"
type: "Type"
description: "Description"
dev/views/index.vue:
manage-apps: "Gestion des applications"
dev/views/apps.vue:
@ -1605,3 +1588,142 @@ dev/views/new-app.vue:
authority: "Autorisations "
authority-desc: "Sont accessibles via lAPI, uniquement les fonctionnalités demandées ici."
authority-warning: "Vous pouvez le changer même après avoir créé l'application, mais si vous attribuez une nouvelle permission, toutes les clés utilisateur associées seront dès lors invalides."
pages:
title: "Titre"
blocks:
text: "Texte"
textarea: "Zone de texte"
section: "Section"
image: "Images"
button: "Bouton"
if: "Si"
_if:
variable: "Variables"
post: "Champs de publication"
_post:
text: "Contenu"
_textInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
_textareaInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
_numberInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
switch: "Basculer"
_switch:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
counter: "Compteur"
_counter:
name: "Nom de la variable"
text: "Titre"
_button:
text: "Titre"
_action:
_dialog:
content: "Contenu"
script:
categories:
flow: "Contrôle"
logical: "Opération logique"
operation: "Calculer"
comparison: "Comparer"
random: "Aléatoire"
value: "Valeur"
fn: "Fonction"
list: "Listes"
blocks:
text: "Texte"
strLen: "Longueur du texte"
_strLen:
arg1: "Texte"
strPick: "Extraire un caractère"
_strPick:
arg1: "Texte"
_strReplace:
arg1: "Texte"
arg2: "Avant le remplacement"
arg3: "Après le remplacement"
strReverse: "Inverser le texte"
_strReverse:
arg1: "Texte"
_join:
arg1: "Listes"
arg2: "Séparateur"
add: "+ Plus"
_add:
arg1: "A"
arg2: "B"
subtract: "- Moins"
_subtract:
arg1: "A"
arg2: "B"
multiply: "× Multiplier par"
_multiply:
arg1: "A"
arg2: "B"
divide: "÷ Diviser par"
_divide:
arg1: "A"
arg2: "B"
_remind:
arg1: "A"
arg2: "B"
eq: "A et B sont équivalents"
_eq:
arg1: "A"
arg2: "B"
notEq: "A et B sont différents"
_notEq:
arg1: "A"
arg2: "B"
_and:
arg1: "A"
arg2: "B"
_or:
arg1: "A"
arg2: "B"
_lt:
arg1: "A"
arg2: "B"
_gt:
arg1: "A"
arg2: "B"
_ltEq:
arg1: "A"
arg2: "B"
_gtEq:
arg1: "A"
arg2: "B"
random: "Aléatoire"
_randomPick:
arg1: "Listes"
_dailyRandomPick:
arg1: "Listes"
_seedRandomPick:
arg2: "Listes"
_pick:
arg1: "Listes"
number: "Numérique"
_stringToNumber:
arg1: "Texte"
_numberToString:
arg1: "Numérique"
_splitStrByLine:
arg1: "Texte"
fn: "Fonction"
_fn:
arg1: "Sortie"
for: "Répéter"
types:
string: "Texte"
number: "Numérique"
array: "Listes"
stringArray: "Liste de texte"
enviromentVariables: "Variables d'environnement"

View File

@ -65,6 +65,7 @@ common:
trash: "ゴミ箱"
drive: "ドライブ"
pages: "ページ"
messaging: "トーク"
home: "ホーム"
deck: "デッキ"
@ -262,7 +263,6 @@ common:
update-available-title: "更新があります"
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
verified-user: "公式アカウント"
hide-password: "パスワードを隠す"
show-password: "パスワードを表示する"
@ -338,7 +338,7 @@ auth/views/index.vue:
sign-in: "サインインしてください"
common/views/pages/explore.vue:
verified-users: "公式アカウント"
pinned-users: "ピン留めされたユーザー"
popular-users: "人気のユーザー"
recently-updated-users: "最近投稿したユーザー"
recently-registered-users: "新規ユーザー"
@ -1263,7 +1263,7 @@ admin/views/instance.vue:
invite: "招待"
save: "保存"
saved: "保存しました"
user-recommendation-config: "おすすめユーザー"
pinned-users: "ピン留めユーザー"
email-config: "メールサーバーの設定"
email-config-info: "メールアドレス確認やパスワードリセットの際に使われます。"
enable-email: "メール配信を有効にする"
@ -1350,12 +1350,6 @@ admin/views/users.vue:
silence-confirm: "サイレンスしますか?"
unmake-silence: "サイレンスの解除"
unsilence-confirm: "サイレンスを解除しますか?"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
@ -1372,7 +1366,6 @@ admin/views/users.vue:
admin: "管理者"
moderator: "モデレーター"
adminOrModerator: "管理者+モデレーター"
verified: "公式アカウント"
silenced: "サイレンス済み"
suspended: "凍結済み"
origin:
@ -1813,26 +1806,6 @@ docs:
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
edit-this-page-on-github-link: "このページをGitHubで編集"
api:
entities:
properties: "プロパティ"
endpoints:
params: "パラメータ"
no-params: "パラメータはありません"
res: "レスポンス"
require-credential: "このエンドポイントは認証情報が必須です。"
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
has-limit: "レートリミットがあります。"
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
show-src: "このエンドポイントのソースコードも閲覧できます。"
show-src-link: "コードをGitHubで見る"
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
props:
name: "名前"
type: "型"
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
@ -1857,3 +1830,264 @@ dev/views/new-app.vue:
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
pages:
new-page: "ページの作成"
edit-page: "ページの編集"
read-page: "ソースを表示中"
page-created: "ページを作成しました"
page-updated: "ページを更新しました"
are-you-sure-delete: "このページを削除しますか?"
page-deleted: "ページを削除しました"
edit-this-page: "このページを編集"
view-source: "ソースを表示"
view-page: "ページを見る"
inspector: "インスペクター"
content: "ページブロック"
variables: "変数"
variables-info: "変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。"
variables-info2: "変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b>や<b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b>や<b>C</b>を参照することはできません。"
variables-info3: "ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。"
variables-info4: "関数を使うと、値の算出処理を再利用可能な形にまとめることができます。関数を作るには、「関数」タイプの変数を作成します。関数にはスロット(引数)を設定することができ、スロットの値は関数内で変数として利用可能です。また、AiScript標準で関数を引数に取る関数(高階関数と呼ばれます)も存在します。関数は予め定義しておくほかに、このような高階関数のスロットに即席でセットすることもできます。"
more-details: "詳しい説明"
title: "タイトル"
url: "ページURL"
summary: "ページの要約"
align-center: "中央寄せ"
font: "フォント"
fontSerif: "セリフ"
fontSansSerif: "サンセリフ"
set-eye-catching-image: "アイキャッチ画像を設定"
remove-eye-catching-image: "アイキャッチ画像を削除"
choose-block: "ブロックを追加"
select-type: "種類を選択"
enter-variable-name: "変数名を決めてください"
the-variable-name-is-already-used: "その変数名は既に使われています"
content-blocks: "コンテンツ"
input-blocks: "入力"
special-blocks: "特殊"
post-from-post-form: "この内容を投稿"
posted-from-post-form: "投稿しました"
blocks:
text: "テキスト"
textarea: "テキストエリア"
section: "セクション"
image: "画像"
button: "ボタン"
if: "もし"
_if:
variable: "変数"
post: "投稿フォーム"
_post:
text: "内容"
textInput: "テキスト入力"
_textInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
textareaInput: "複数行テキスト入力"
_textareaInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
numberInput: "数値入力"
_numberInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
switch: "スイッチ"
_switch:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
counter: "カウンター"
_counter:
name: "変数名"
text: "タイトル"
inc: "増加値"
_button:
text: "タイトル"
action: "ボタンを押したときの動作"
_action:
dialog: "ダイアログを表示する"
_dialog:
content: "内容"
resetRandom: "乱数をリセット"
script:
categories:
flow: "制御"
logical: "論理演算"
operation: "計算"
comparison: "比較"
random: "ランダム"
value: "値"
fn: "関数"
text: "テキスト操作"
convert: "変換"
list: "リスト"
blocks:
text: "テキスト"
multiLineText: "テキスト(複数行)"
textList: "テキストのリスト"
_textList:
info: "ひとつひとつを改行で区切ってください"
strLen: "テキストの長さ"
_strLen:
arg1: "テキスト"
strPick: "文字取り出し"
_strPick:
arg1: "テキスト"
arg2: "文字の位置"
strReplace: "テキスト置き換え"
_strReplace:
arg1: "テキスト"
arg2: "置き換え前"
arg3: "置き換え後"
strReverse: "テキストを反転"
_strReverse:
arg1: "テキスト"
join: "テキストを連結"
_join:
arg1: "リスト"
arg2: "区切り"
add: "+ 足す"
_add:
arg1: "A"
arg2: "B"
subtract: "- 引く"
_subtract:
arg1: "A"
arg2: "B"
multiply: "× 掛ける"
_multiply:
arg1: "A"
arg2: "B"
divide: "÷ 割る"
_divide:
arg1: "A"
arg2: "B"
remind: "÷ 割った余り"
_remind:
arg1: "A"
arg2: "B"
eq: "AとBが同じ"
_eq:
arg1: "A"
arg2: "B"
notEq: "AとBが異なる"
_notEq:
arg1: "A"
arg2: "B"
and: "AかつB"
_and:
arg1: "A"
arg2: "B"
or: "AまたはB"
_or:
arg1: "A"
arg2: "B"
lt: "< AがBより小さい"
_lt:
arg1: "A"
arg2: "B"
gt: "> AがBより大きい"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= AがBと同じか小さい"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= AがBと同じか大きい"
_gtEq:
arg1: "A"
arg2: "B"
if: "分岐"
_if:
arg1: "もし"
arg2: "なら"
arg3: "そうでなければ"
not: "否定"
_not:
arg1: "否定"
random: "ランダム"
_random:
arg1: "確率"
rannum: "乱数"
_rannum:
arg1: "最小"
arg2: "最大"
randomPick: "リストからランダムに選択"
_randomPick:
arg1: "リスト"
dailyRandom: "ランダム (ユーザーごとに日替わり)"
_dailyRandom:
arg1: "確率"
dailyRannum: "乱数 (ユーザーごとに日替わり)"
_dailyRannum:
arg1: "最小"
arg2: "最大"
dailyRandomPick: "リストからランダムに選択 (ユーザーごとに日替わり)"
_dailyRandomPick:
arg1: "リスト"
seedRandom: "ランダム (シード)"
_seedRandom:
arg1: "シード"
arg2: "確率"
seedRannum: "乱数 (シード)"
_seedRannum:
arg1: "シード"
arg2: "最小"
arg3: "最大"
seedRandomPick: "リストからランダムに選択 (シード)"
_seedRandomPick:
arg1: "シード"
arg2: "リスト"
DRPWPM: "確率付きリストからランダムに選択 (ユーザーごとに日替わり)"
_DRPWPM:
arg1: "テキストのリスト"
pick: "リストから選択"
_pick:
arg1: "リスト"
arg2: "位置"
number: "数値"
stringToNumber: "テキストを数値に"
_stringToNumber:
arg1: "テキスト"
numberToString: "数値をテキストに"
_numberToString:
arg1: "数値"
splitStrByLine: "テキストを行で分割"
_splitStrByLine:
arg1: "テキスト"
ref: "変数"
fn: "関数"
_fn:
slots: "スロット"
slots-info: "スロットひとつひとつを改行で区切ってください"
arg1: "出力"
for: "繰り返し"
_for:
arg1: "回数"
arg2: "処理"
typeError: "スロット{slot}は\"{expect}\"を受け付けますが、\"{actual}\"が入れられています!"
thereIsEmptySlot: "スロット{slot}が空です!"
types:
string: "テキスト"
number: "数値"
boolean: "フラグ"
array: "リスト"
stringArray: "テキストのリスト"
emptySlot: "空のスロット"
enviromentVariables: "環境変数"
pageVariables: "ページ要素"
argVariables: "入力スロット"

View File

@ -1246,25 +1246,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
edit-this-page-on-github-link: "このページをGitHubで編集"
api:
entities:
properties: "プロパティ"
endpoints:
params: "パラメータ"
no-params: "パラメータはありません"
res: "レスポンス"
require-credential: "このエンドポイントは認証情報が必須です。"
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
has-limit: "レートリミットがあります。"
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
show-src: "このエンドポイントのソースコードも閲覧できます。"
show-src-link: "コードをGitHubで見る"
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
props:
name: "名前"
type: "型"
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
@ -1281,3 +1262,25 @@ dev/views/new-app.vue:
authority: "権限"
authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな"
authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。"
pages:
blocks:
image: "画像"
post: "投稿フォーム"
script:
categories:
random: "いんじゃんほい"
list: "リスト"
blocks:
_join:
arg1: "リスト"
random: "いんじゃんほい"
_randomPick:
arg1: "リスト"
_dailyRandomPick:
arg1: "リスト"
_seedRandomPick:
arg2: "リスト"
_pick:
arg1: "リスト"
types:
array: "リスト"

View File

@ -34,6 +34,7 @@ common:
signup: "신규 등록"
signout: "로그아웃"
reload-to-apply-the-setting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
fetching-as-ap-object: "연합에서 조회 중"
got-it: "알겠습니다"
customization-tips:
title: "커스터마이징 도움말"
@ -61,6 +62,7 @@ common:
month-and-day: "{month}월 {day}일"
trash: "휴지통"
drive: "드라이브"
pages: "페이지"
messaging: "대화"
home: "홈"
deck: "덱"
@ -72,8 +74,23 @@ common:
permissions:
"read:account": "계정 정보 보기"
"write:account": "계정 정보 변경"
"read:blocks": "차단 보기"
"write:blocks": "차단 수정"
"read:drive": "드라이브 보기"
"write:drive": "드라이브 수정"
"read:favorites": "즐겨찾기 보기"
"write:favorites": "즐겨찾기 수정"
"read:following": "팔로우 정보 보기"
"write:following": "팔로잉, 팔로우 수정"
"read:messaging": "대화 보기"
"write:messaging": "대화 수정"
"read:mutes": "뮤트 보기"
"write:mutes": "뮤트 수정"
"write:notes": "글 작성, 삭제"
"read:notifications": "글 보기"
"write:notifications": "알림 수정"
"read:reactions": "리액션 보기"
"write:reactions": "리액션 수정"
"write:votes": "투표하기"
empty-timeline-info:
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
@ -293,12 +310,12 @@ auth/views/form.vue:
accept: "접근 권한 허용"
auth/views/index.vue:
loading: "로드 중"
denied: "플리케이션의 연계를 취소하였습니다."
denied: "플리케이션의 연계를 취소하였습니다."
denied-paragraph: "이 앱이 당신의 계정에 액세스할 수 없습니다."
already-authorized: "이 앱은 이미 연결되어 있습니다."
allowed: "플리케이션의 연동을 허용하였습니다."
callback-url: "플리케이션으로 돌아갑니다."
please-go-back: "플리케이션으로 돌아가여 시도하여 주십시오."
allowed: "플리케이션의 연동을 허용하였습니다."
callback-url: "플리케이션으로 돌아갑니다."
please-go-back: "플리케이션으로 돌아가여 시도하여 주십시오."
error: "세션이 존재하지 않습니다."
sign-in: "로그인 해주시기 바랍니다"
common/views/pages/explore.vue:
@ -474,8 +491,12 @@ common/views/components/user-menu.vue:
mention: "멘션"
mute: "뮤트"
unmute: "뮤트 해제"
mute-confirm: "이 사용자를 뮤트하시겠습니까?"
unmute-confirm: "이 사용자를 뮤트 해제하시겠습니까?"
block: "차단"
unblock: "차단 해제"
block-confirm: "이 사용자를 차단하시겠습니까?"
unblock-confirm: "이 사용자를 차단 해제하시겠습니까?"
push-to-list: "리스트에 추가"
select-list: "리스트를 선택하여 주십시오"
report-abuse: "스팸 신고"
@ -483,8 +504,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "관리자에게 보고되었습니다. 협조해주셔서 감사합니다."
silence: "침묵"
unsilence: "침묵 해제"
silence-confirm: "이 사용자를 침묵하시겠습니까?"
unsilence-confirm: "이 사용자를 침묵 해제하시겠습니까?"
suspend: "정지"
unsuspend: "정지 해제"
suspend-confirm: "이 사용자를 정지하시겠습니까?"
unsuspend-confirm: "이 사용자를 정지 해제하시겠습니까?"
common/views/components/poll.vue:
vote-to: "\"{}\"에 투표하기"
vote-count: "{}표"
@ -668,6 +693,7 @@ common/views/components/user-list-editor.vue:
delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
deleted: "삭제하였습니다"
common/views/components/user-lists.vue:
create-list: "리스트 만들기"
list-name: "리스트 이름"
common/views/widgets/broadcast.vue:
fetching: "확인중"
@ -946,7 +972,7 @@ common/views/components/api-settings.vue:
sending: "응답을 기다리는 중"
response: "결과"
desktop/views/components/settings.apps.vue:
no-apps: "연결된 플리케이션이 없습니다"
no-apps: "연결된 플리케이션이 없습니다"
common/views/components/drive-settings.vue:
max: "최대 용량"
in-use: "사용중"
@ -1194,7 +1220,9 @@ admin/views/users.vue:
unsuspend-confirm: "정지를 해제하시겠습니까?"
unsuspended: "정지를 해제하였습니다"
make-silence: "침묵"
silence-confirm: "침묵으로 설정합니까?"
unmake-silence: "침묵 해제"
unsilence-confirm: "침묵 해제하시겠습니까?"
verify: "공식 계정으로 설정"
verify-confirm: "공식 계정으로 설정하시겠습니까?"
verified: "공식 계정으로 설정하였습니다"
@ -1388,6 +1416,7 @@ desktop/views/widgets/polls.vue:
desktop/views/widgets/post-form.vue:
title: "글쓰기"
note: "글쓰기"
something-happened: "알 수 없는 문제로 글을 게시할 수 없습니다."
desktop/views/widgets/profile.vue:
update-banner: "클릭하여 배너 변경"
update-avatar: "클릭하여 아바타 변경"
@ -1410,6 +1439,7 @@ mobile/views/components/drive.vue:
here-is-root: "현재 경로는 루트 경로로 폴더가 아닙니다."
url-prompt: "업로드 하려는 파일의 URL"
uploading: "업로드를 요청하였습니다. 업로드가 완료될 때까지 시간이 소요될 수 있습니다."
folder-name-cannot-empty: "폴더 이름은 비워둘 수 없습니다."
mobile/views/components/drive-file-chooser.vue:
select-file: "파일 선택"
mobile/views/components/drive-folder-chooser.vue:
@ -1486,7 +1516,11 @@ mobile/views/components/ui.nav.vue:
mobile/views/pages/drive.vue:
contextmenu:
upload: "파일 업로드"
url-upload: "파일을 URL로부터 업로드"
create-folder: "폴더 만들기"
rename-folder: "폴더 이름 바꾸기"
move-folder: "이 폴더를 이동"
delete-folder: "이 폴더를 삭제"
mobile/views/pages/user-lists.vue:
title: "리스트"
mobile/views/pages/signup.vue:
@ -1589,25 +1623,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "틀린 점이나 개선할 점을 찾으셨나요?"
edit-this-page-on-github-link: "이 페이지를 GitHub에서 편집"
api:
entities:
properties: "프로퍼티"
endpoints:
params: "매개변수"
no-params: "매개변수가 없습니다"
res: "응답"
require-credential: "이 엔드포인트는 인증 정보가 필수적입니다."
require-permission: "이 엔드포인트는 {permission} 권한을 필요로 합니다."
has-limit: "Rate limit(요청 비율 제한)이 있습니다."
duration-limit: "최근 {duration} 밀리초 사이에 엔드포인트로의 요청 수의 합계가 {max}를 넘는 경우 요청이 불가능합니다."
min-interval-limit: "이전 요청으로부터 {interval} 밀리초가 지나지 않은 경우 요청할 수 없습니다."
show-src: "이 엔드포인트에 대한 소스코드를 확인할 수 있습니다."
show-src-link: "코드를 Github에서 보기"
generated: "이 문서는 API 정의를 기반으로 자동으로 생성됩니다."
props:
name: "이름"
type: "자료형"
description: "설명"
dev/views/index.vue:
manage-apps: "앱 관리"
dev/views/apps.vue:
@ -1615,12 +1630,64 @@ dev/views/apps.vue:
create-app: "앱 생성"
app-missing: "앱 없음"
dev/views/new-app.vue:
create-app: "플리케이션 생성"
app-name: "플리케이션 이름"
new-app: "새 애플리케이션"
new-app-info: "플리케이션은 API에서도 생성할 수 있습니다. (app/create)"
create-app: "애플리케이션 생성"
app-name: "애플리케이션 이름"
app-name-placeholder: "ex) Misskey for iOS"
app-name-desc: "앱의 이름."
app-overview: "앱 개요"
app-overview-placeholder: "ex) Misskey iOS 클라이언트."
app-overview-desc: "애플리케이션에 대한 간단한 설명이나 소개"
callback-url: "콜백 URL (옵션)"
callback-url-placeholder: "ex) https://your.app.example.com/callback.php"
callback-url-desc: "사용자가 인증 폼에서 인증한 뒤 리다이렉트할 URL을 설정합니다."
authority: "권한"
authority-desc: "이곳에서 요청한 권한에 한정하여 API로 액세스할 수 있습니다."
authority-warning: "앱을 생성한 뒤에도 변경할 수 있지만, 새로운 권한을 설정하는 경우 그 시점부터 예전에 발급받았던 유저 키는 모두 무효화됩니다."
pages:
new-page: "페이지 만들기"
edit-page: "페이지 수정"
read-page: "소스 표시중"
page-created: "페이지를 만들었습니다"
page-updated: "페이지를 수정했습니다"
are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
title: "제목"
blocks:
image: "이미지"
post: "게시 양식"
_post:
text: "내용"
_textInput:
text: "제목"
_textareaInput:
text: "제목"
_numberInput:
text: "제목"
_switch:
text: "제목"
_counter:
text: "제목"
_button:
text: "제목"
_action:
_dialog:
content: "내용"
script:
categories:
random: "랜덤"
list: "리스트"
blocks:
_join:
arg1: "리스트"
random: "랜덤"
_randomPick:
arg1: "리스트"
_dailyRandomPick:
arg1: "리스트"
_seedRandomPick:
arg2: "리스트"
_pick:
arg1: "리스트"
types:
array: "리스트"

View File

@ -619,13 +619,22 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "Heb je een fout ontdekt of wil je bijdragen aan de documentatie? "
edit-this-page-on-github-link: "Bewerk deze pagina op GitHub!"
api:
entities:
properties: "Eigenschappen"
endpoints:
params: "Parameters"
res: "Antwoord"
props:
name: "Naam"
type: "Type"
description: "Omschrijving"
pages:
blocks:
image: "Afbeeldingen"
script:
categories:
list: "Lijsten"
blocks:
_join:
arg1: "Lijsten"
_randomPick:
arg1: "Lijsten"
_dailyRandomPick:
arg1: "Lijsten"
_seedRandomPick:
arg2: "Lijsten"
_pick:
arg1: "Lijsten"
types:
array: "Lijsten"

View File

@ -489,14 +489,24 @@ deck/deck.user-column.vue:
following: "Følger"
followers: "Følgere"
images: "Bilder"
docs:
api:
entities:
properties: "Egenskaper"
endpoints:
params: "Parametere"
res: "Respons"
props:
name: "Navn"
type: "Type"
description: "Beskrivelse"
pages:
blocks:
image: "Bilder"
script:
categories:
random: "Tilfeldig"
list: "Lister"
blocks:
_join:
arg1: "Lister"
random: "Tilfeldig"
_randomPick:
arg1: "Lister"
_dailyRandomPick:
arg1: "Lister"
_seedRandomPick:
arg2: "Lister"
_pick:
arg1: "Lister"
types:
array: "Lister"

View File

@ -54,6 +54,7 @@ common:
month-and-day: "{month}-{day}"
trash: "Kosz"
drive: "Dysk"
pages: "Strony"
messaging: "Rozmowy"
home: "Strona główna"
deck: "Tablice"
@ -127,6 +128,7 @@ common:
behavior: "Zachowanie"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
note-visibility: "Widoczność wpisów"
remember-note-visibility: "Zapamiętaj widoczność wpisów"
web-search-engine: "Wyszukiwarka internetowa"
line-width: "Szerokości linii"
line-width-thin: "Cienka"
@ -1190,23 +1192,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "Znalazłeś błąd lub chcesz pomóc w tworzeniu dokumentacji?"
edit-this-page-on-github-link: "Edytuj stronę na GitHubie!"
api:
entities:
properties: "Właściwości"
endpoints:
params: "Parametry"
no-params: "Brak parametrów."
res: "Odpowiedź"
require-credential: "Punkt końcowy wymaga informacji o uwierzytelnieniu."
require-permission: "Ten punkt końcowy wymaga uprawnienia {permission}."
has-limit: "Istnieje limit częstotliwości."
min-interval-limit: "Nie możesz wykonać żądania przed upłynięciem {interval} od ostatniego żądania."
show-src: "Możesz zobaczyć kod źródłowy tego punktu końcowego."
show-src-link: "Zobacz kod na GitHubie"
props:
name: "Nazwa"
type: "Rodzaj"
description: "Opis"
dev/views/index.vue:
manage-apps: "Zarządzaj aplikacjami"
dev/views/apps.vue:
@ -1215,3 +1200,38 @@ dev/views/apps.vue:
dev/views/new-app.vue:
app-name: "Nazwa Aplikacji"
authority: "Uprawnienia"
pages:
title: "Tytuł"
blocks:
image: "Zdjęcia"
post: "Formularz tworzenia"
_textInput:
text: "Tytuł"
_textareaInput:
text: "Tytuł"
_numberInput:
text: "Tytuł"
_switch:
text: "Tytuł"
_counter:
text: "Tytuł"
_button:
text: "Tytuł"
script:
categories:
random: "Losowy"
list: "Listy"
blocks:
_join:
arg1: "Listy"
random: "Losowy"
_randomPick:
arg1: "Listy"
_dailyRandomPick:
arg1: "Listy"
_seedRandomPick:
arg2: "Listy"
_pick:
arg1: "Listy"
types:
array: "Listy"

View File

@ -274,18 +274,9 @@ deck/deck.user-column.vue:
timeline: "Linha do tempo"
docs:
edit-this-page-on-github-link: "Edite esta página no GitHub!"
api:
entities:
properties: "Propriedades"
endpoints:
params: "Parâmetros"
no-params: "Sem parâmetros"
res: "Resposta"
show-src-link: "Veja o código no GitHub"
generated: "Este documento foi gerado pelas definições da API."
props:
name: "Nome"
type: "Tipo"
description: "Descrição"
dev/views/index.vue:
manage-apps: "Gerenciar aplicativos"
pages:
blocks:
image: "Imagens"
post: "Formulário de publicação"

View File

@ -146,3 +146,9 @@ mobile/views/components/sub-note-content.vue:
poll: "Голосования"
mobile/views/pages/widgets.vue:
customization-tips: "Советы по настройке"
pages:
script:
categories:
random: "Случайно"
blocks:
random: "Случайно"

View File

@ -62,6 +62,7 @@ common:
month-and-day: "{month}月 {day}日"
trash: "垃圾箱"
drive: "网盘"
pages: "页面"
messaging: "聊天"
home: "首页"
deck: "Deck"
@ -1622,25 +1623,6 @@ deck/deck.user-column.vue:
docs:
edit-this-page-on-github: "发现错误或想要为文档做出贡献?"
edit-this-page-on-github-link: "在GitHub上编辑这个页面。"
api:
entities:
properties: "属性"
endpoints:
params: "参数"
no-params: "没有参数"
res: "回应"
require-credential: "此端点需要身份验证信息。"
require-permission: "此端点需要{permission}权限。"
has-limit: "有一个速率限制。"
duration-limit: "如果您发送请求在{duration}毫秒内多于{max}次,则无法发送更多请求。"
min-interval-limit: "如果自上次请求后未传递{interval}毫秒,则无法发送请求。"
show-src: "您可以查看此端点的源代码。"
show-src-link: "查阅GitHub上的代码"
generated: "该文档由API定义生成。"
props:
name: "名称"
type: "型号"
description: "描述"
dev/views/index.vue:
manage-apps: "管理应用"
dev/views/apps.vue:
@ -1663,3 +1645,247 @@ dev/views/new-app.vue:
authority: "权限"
authority-desc: "只能通过API访问此处请求的功能。"
authority-warning: "您可以在创建应用程序后对其进行更改,但如果您授予不同的权限,则当时关联的所有用户密钥都将失效。"
pages:
new-page: "创建页面"
edit-page: "编辑页面"
read-page: "查看源"
page-created: "页面已创建"
page-updated: "页面已更新"
are-you-sure-delete: "是否删除此页面?"
page-deleted: "该页面已被删除。"
edit-this-page: "编辑此页面"
view-source: "查看源代码"
view-page: "查看页面"
inspector: "检查器"
content: "页面内容"
variables: "变量"
variables-info: "您可以使用变量创建动态页面。在文本中通过<b>{变量名}</b>的写法来嵌入变量值。例如在文本<b>Hello { thing } world!</b>中,如果变量(thing)的值为<b>ai</b>,那么该文本会成为<b>Hello ai world!</b>。"
variables-info2: "因为变量的计算(计算变量值)是从上到下执行的,所以不能在变量中引用下面的变量。例如从上到下依次定义了<b>ABC</b>3个变量那么<b>C</b>中可以引用<b>A</b>或<b>B</b>,但是<b>A</b>无法引用<b>B</b>或<b>C</b>。"
variables-info3: "为了接收来自用户的输入,页面上设有“用户输入”块,在“变量名称”中设置要在其中保存输入值的变量名(变量会自动创建)。您可以使用该变量执行操作以响应用户输入。"
variables-info4: "通过使用函数,您可以将数值计算过程组合成可重用的形式。要创建函数,需要创建一个“函数”类型的变量。你可以将函数设定为槽函数(参数)的格式槽函数的值可作为函数中的变量使用。另外AiScript标准中还有一些函数会将函数作为参数(称为高阶函数)。\n除了已经预先定义的函数外您也可以将它们设置为这些高阶函数的槽函数。"
more-details: "详细说明"
title: "标题"
url: "页面URL"
summary: "页面摘要"
align-center: "居中"
font: "字体"
fontSerif: "衬线字体"
fontSansSerif: "无衬线字体"
choose-block: "添加块"
select-type: "类型选择"
enter-variable-name: "请确定变量名"
the-variable-name-is-already-used: "变量名已使用"
content-blocks: "内容"
input-blocks: "输入"
special-blocks: "特殊"
post-from-post-form: "发布此内容"
posted-from-post-form: "已发布"
blocks:
text: "文本"
textarea: "文本区域"
section: "章节"
image: "图片"
button: "按钮"
if: "如果"
_if:
variable: "变量"
post: "投稿形式"
_post:
text: "内容"
textInput: "文本输入"
_textInput:
name: "变量名"
text: "标题"
default: "默认值"
textareaInput: "多行文本输入"
_textareaInput:
name: "变量名"
text: "标题"
default: "默认值"
numberInput: "数值输入"
_numberInput:
name: "变量名"
text: "标题"
default: "默认值"
switch: "开关"
_switch:
name: "变量名"
text: "标题"
default: "默认值"
counter: "计数器"
_counter:
name: "变量名"
text: "标题"
inc: "增加值"
_button:
text: "标题"
action: "按下按钮时的行为"
_action:
dialog: "显示对话框"
_dialog:
content: "内容"
resetRandom: "随机值重置"
script:
categories:
flow: "控制"
logical: "逻辑运算"
operation: "计算"
comparison: "比较"
random: "随机"
value: "值"
fn: "函数"
text: "文本操作"
list: "列表"
blocks:
text: "文本"
multiLineText: "文本 (多行)"
textList: "文本列表"
_textList:
info: "情使用换行符分隔每行"
strLen: "文本长度"
_strLen:
arg1: "文本"
strPick: "字符提取"
_strPick:
arg1: "文本"
arg2: "字符位置"
strReplace: "文本替换"
_strReplace:
arg1: "文本"
arg2: "替换之前"
arg3: "替换之后"
strReverse: "文本反向"
_strReverse:
arg1: "文本"
join: "合并文本"
_join:
arg1: "列表"
arg2: "分隔符"
add: "+ 加"
_add:
arg1: "A"
arg2: "B"
subtract: "- 减"
_subtract:
arg1: "A"
arg2: "B"
multiply: "× 乘"
_multiply:
arg1: "A"
arg2: "B"
divide: "÷ 除"
_divide:
arg1: "A"
arg2: "B"
remind: "÷ 取模"
_remind:
arg1: "A"
arg2: "B"
eq: "A和B相等"
_eq:
arg1: "A"
arg2: "B"
notEq: "A和B不等"
_notEq:
arg1: "A"
arg2: "B"
and: "A和B"
_and:
arg1: "A"
arg2: "B"
or: "A或B"
_or:
arg1: "A"
arg2: "B"
lt: "< A小于B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A大于B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A小于等于B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A大于等于B"
_gtEq:
arg1: "A"
arg2: "B"
if: "分支"
_if:
arg1: "如果"
arg2: "的话"
arg3: "否则"
random: "随机"
_random:
arg1: "概率"
rannum: "随机"
_rannum:
arg1: "最小"
arg2: "最大"
randomPick: "从列表中随机选择"
_randomPick:
arg1: "列表"
dailyRandom: "随机(每个用户每日)"
_dailyRandom:
arg1: "概率"
dailyRannum: "随机数(每个用户每日)"
_dailyRannum:
arg1: "最小"
arg2: "最大"
dailyRandomPick: "从列表中随机选择(每个用户每日)"
_dailyRandomPick:
arg1: "列表"
seedRandom: "随机 (种子)"
_seedRandom:
arg1: "种子"
arg2: "概率"
seedRannum: "随机数(种子)"
_seedRannum:
arg1: "种子"
arg2: "最小"
arg3: "最大"
seedRandomPick: "从列表中随机选择 (种子)"
_seedRandomPick:
arg1: "种子"
arg2: "列表"
_DRPWPM:
arg1: "文本列表"
pick: "从列表中选择"
_pick:
arg1: "列表"
arg2: "位置"
number: "数值"
stringToNumber: "文本到数字"
_stringToNumber:
arg1: "文本"
numberToString: "数字到文本"
_numberToString:
arg1: "数值"
splitStrByLine: "将文本按行拆分"
_splitStrByLine:
arg1: "文本"
ref: "变量"
fn: "函数"
_fn:
slots: "槽函数"
slots-info: "请使用换行符分隔每个槽函数"
arg1: "输出"
for: "重复"
_for:
arg1: "次数"
arg2: "处理"
typeError: "槽函数{slot}需要传入“{expect}”,但是实际传入为“{actual}”!"
thereIsEmptySlot: "槽函数{slot}为空!"
types:
string: "文本"
number: "数值"
boolean: "布尔值"
array: "列表"
stringArray: "文本列表"
emptySlot: "空白槽函数"
enviromentVariables: "环境变量"
pageVariables: "页面元素"
argVariables: "输入槽函数"

View File

@ -0,0 +1,31 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class Pages1556348509290 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE TYPE "page_visibility_enum" AS ENUM('public', 'followers', 'specified')`);
await queryRunner.query(`CREATE TABLE "page" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "title" character varying(256) NOT NULL, "name" character varying(256) NOT NULL, "summary" character varying(256), "alignCenter" boolean NOT NULL, "font" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "eyeCatchingImageId" character varying(32), "content" jsonb NOT NULL DEFAULT '[]', "variables" jsonb NOT NULL DEFAULT '[]', "visibility" "page_visibility_enum" NOT NULL, "visibleUserIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_742f4117e065c5b6ad21b37ba1f" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_fbb4297c927a9b85e9cefa2eb1" ON "page" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_af639b066dfbca78b01a920f8a" ON "page" ("updatedAt") `);
await queryRunner.query(`CREATE INDEX "IDX_b82c19c08afb292de4600d99e4" ON "page" ("name") `);
await queryRunner.query(`CREATE INDEX "IDX_ae1d917992dd0c9d9bbdad06c4" ON "page" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_90148bbc2bf0854428786bfc15" ON "page" ("visibleUserIds") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2133ef8317e4bdb839c0dcbf13" ON "page" ("userId", "name") `);
await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_ae1d917992dd0c9d9bbdad06c4a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10"`);
await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_ae1d917992dd0c9d9bbdad06c4a"`);
await queryRunner.query(`DROP INDEX "IDX_2133ef8317e4bdb839c0dcbf13"`);
await queryRunner.query(`DROP INDEX "IDX_90148bbc2bf0854428786bfc15"`);
await queryRunner.query(`DROP INDEX "IDX_ae1d917992dd0c9d9bbdad06c4"`);
await queryRunner.query(`DROP INDEX "IDX_b82c19c08afb292de4600d99e4"`);
await queryRunner.query(`DROP INDEX "IDX_af639b066dfbca78b01a920f8a"`);
await queryRunner.query(`DROP INDEX "IDX_fbb4297c927a9b85e9cefa2eb1"`);
await queryRunner.query(`DROP TABLE "page"`);
await queryRunner.query(`DROP TYPE "page_visibility_enum"`);
}
}

View File

@ -0,0 +1,23 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class UserProfile1556746559567 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`UPDATE "user_profile" SET github = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubId"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "githubId" VARCHAR(64)`);
await queryRunner.query(`UPDATE "user_profile" SET discord = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordExpiresDate"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "discordExpiresDate" VARCHAR(64)`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`UPDATE "user_profile" SET github = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubId"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "githubId" INTEGER`);
await queryRunner.query(`UPDATE "user_profile" SET discord = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordExpiresDate"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "discordExpiresDate" INTEGER`);
}
}

View File

@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class PinnedUsers1557476068003 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedUsers"`);
}
}

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "11.3.1",
"version": "11.12.0",
"codename": "daybreak",
"repository": {
"type": "git",
@ -23,6 +23,7 @@
"format": "gulp format"
},
"dependencies": {
"@elastic/elasticsearch": "7.0.0-rc.2",
"@fortawesome/fontawesome-svg-core": "1.2.15",
"@fortawesome/free-brands-svg-icons": "5.7.2",
"@fortawesome/free-regular-svg-icons": "5.7.2",
@ -35,7 +36,6 @@
"@types/dateformat": "3.0.0",
"@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.32",
"@types/file-type": "10.9.1",
"@types/gulp": "4.0.6",
"@types/gulp-mocha": "0.0.32",
@ -63,8 +63,8 @@
"@types/lolex": "3.1.1",
"@types/minio": "7.0.1",
"@types/mocha": "5.2.6",
"@types/node": "11.13.4",
"@types/nodemailer": "4.6.7",
"@types/node": "11.13.8",
"@types/nodemailer": "4.6.8",
"@types/nprogress": "0.0.29",
"@types/oauth": "0.9.1",
"@types/parse5": "5.0.0",
@ -77,9 +77,10 @@
"@types/redis": "2.8.12",
"@types/rename": "1.0.1",
"@types/request": "2.48.1",
"@types/request-promise-native": "1.0.15",
"@types/request-promise-native": "1.0.16",
"@types/request-stats": "3.0.0",
"@types/rimraf": "2.0.2",
"@types/seedrandom": "2.4.28",
"@types/sharp": "0.22.1",
"@types/showdown": "1.9.2",
"@types/speakeasy": "2.0.4",
@ -88,18 +89,18 @@
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.4",
"@types/web-push": "3.3.0",
"@types/webpack": "4.4.27",
"@types/webpack": "4.4.29",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.6.8",
"apexcharts": "3.6.9",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"bootstrap-vue": "2.0.0-rc.13",
"bull": "3.7.0",
"bull": "3.8.1",
"cafy": "15.1.1",
"chai": "4.2.0",
"chalk": "2.4.2",
@ -111,17 +112,16 @@
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-equal": "1.0.1",
"diskusage": "1.1.0",
"diskusage": "1.1.1",
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.4.1",
"emojilib": "2.4.0",
"eslint": "5.16.0",
"eslint-plugin-vue": "5.2.2",
"eventemitter3": "3.1.0",
"eventemitter3": "3.1.2",
"feed": "2.0.4",
"file-type": "10.11.0",
"fuckadblock": "3.2.1",
"gulp": "4.0.0",
"gulp": "4.0.1",
"gulp-cssnano": "2.1.3",
"gulp-imagemin": "5.0.3",
"gulp-mocha": "6.0.0",
@ -140,7 +140,7 @@
"is-root": "2.1.0",
"is-svg": "4.1.0",
"js-yaml": "3.13.1",
"jsdom": "14.1.0",
"jsdom": "15.0.0",
"json5": "2.1.0",
"json5-loader": "2.0.0",
"katex": "0.10.1",
@ -160,13 +160,13 @@
"loader-utils": "1.2.3",
"lolex": "3.1.0",
"lookup-dns-cache": "2.1.0",
"minio": "7.0.6",
"mocha": "6.1.3",
"minio": "7.0.8",
"mocha": "6.1.4",
"moji": "0.5.1",
"moment": "2.24.0",
"ms": "2.1.1",
"nested-property": "0.0.7",
"node-fetch": "2.3.0",
"node-fetch": "2.5.0",
"nodemailer": "6.1.1",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
@ -200,7 +200,8 @@
"rimraf": "2.6.3",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sharp": "0.22.0",
"seedrandom": "3.0.1",
"sharp": "0.22.1",
"showdown": "1.9.0",
"showdown-highlightjs-extension": "0.1.2",
"speakeasy": "2.0.0",
@ -209,7 +210,7 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
"systeminformation": "4.1.5",
"systeminformation": "4.1.6",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3",
"textarea-caret": "3.1.0",
@ -233,8 +234,8 @@
"vue-color": "2.7.0",
"vue-content-loading": "1.6.0",
"vue-cropperjs": "3.0.0",
"vue-i18n": "8.10.0",
"vue-js-modal": "1.3.28",
"vue-i18n": "8.11.2",
"vue-js-modal": "1.3.31",
"vue-json-pretty": "1.6.0",
"vue-loader": "15.7.0",
"vue-marquee-text-component": "1.1.1",
@ -250,9 +251,9 @@
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.3",
"webpack": "4.30.0",
"webpack-cli": "3.3.0",
"webpack-cli": "3.3.1",
"websocket": "1.0.28",
"ws": "6.2.1",
"ws": "7.0.0",
"xev": "2.0.1"
}
}

View File

@ -82,6 +82,14 @@
</section>
</ui-card>
<ui-card>
<template #title>{{ $t('pinned-users') }}</template>
<section>
<ui-textarea v-model="pinnedUsers"></ui-textarea>
<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
</section>
</ui-card>
<ui-card>
<template #title>{{ $t('invite') }}</template>
<section>
@ -190,6 +198,7 @@ export default Vue.extend({
enableServiceWorker: false,
swPublicKey: null,
swPrivateKey: null,
pinnedUsers: [],
faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt
};
},
@ -239,6 +248,7 @@ export default Vue.extend({
this.enableServiceWorker = meta.enableServiceWorker;
this.swPublicKey = meta.swPublickey;
this.swPrivateKey = meta.swPrivateKey;
this.pinnedUsers = meta.pinnedUsers.join('\n');
});
},
@ -297,7 +307,8 @@ export default Vue.extend({
smtpPass: this.smtpAuth ? this.smtpPass : '',
enableServiceWorker: this.enableServiceWorker,
swPublicKey: this.swPublicKey,
swPrivateKey: this.swPrivateKey
swPrivateKey: this.swPrivateKey,
pinnedUsers: this.pinnedUsers.split('\n')
}).then(() => {
this.$root.dialog({
type: 'success',

View File

@ -11,7 +11,6 @@
<span class="username">@{{ user | acct }}</span>
<span class="is-admin" v-if="user.isAdmin">admin</span>
<span class="is-moderator" v-if="user.isModerator">moderator</span>
<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
<span class="is-silenced" v-if="user.isSilenced" :title="$t('@.silenced-user')"><fa :icon="faMicrophoneSlash"/></span>
<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
</header>
@ -77,7 +76,6 @@ export default Vue.extend({
background var(--noteHeaderAdminBg)
color var(--noteHeaderAdminFg)
> .is-verified
> .is-silenced
> .is-suspended
margin 0 0 0 .5em

View File

@ -12,10 +12,6 @@
<x-user :user='user'/>
<div class="actions">
<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
<ui-horizon-group>
<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button>
<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button>
</ui-horizon-group>
<ui-horizon-group>
<ui-button @click="silenceUser"><fa :icon="faMicrophoneSlash"/> {{ $t('make-silence') }}</ui-button>
<ui-button @click="unsilenceUser">{{ $t('unmake-silence') }}</ui-button>
@ -47,7 +43,6 @@
<option value="all">{{ $t('users.state.all') }}</option>
<option value="admin">{{ $t('users.state.admin') }}</option>
<option value="moderator">{{ $t('users.state.moderator') }}</option>
<option value="verified">{{ $t('users.state.verified') }}</option>
<option value="silenced">{{ $t('users.state.silenced') }}</option>
<option value="suspended">{{ $t('users.state.suspended') }}</option>
</ui-select>
@ -71,7 +66,7 @@
import Vue from 'vue';
import i18n from '../../i18n';
import parseAcct from "../../../../misc/acct/parse";
import { faCertificate, faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
import { faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
import XUser from './users.user.vue';
@ -84,8 +79,6 @@ export default Vue.extend({
return {
user: null,
target: null,
verifying: false,
unverifying: false,
suspending: false,
unsuspending: false,
sort: '+createdAt',
@ -95,7 +88,7 @@ export default Vue.extend({
offset: 0,
users: [],
existMore: false,
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
faTerminal, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
};
},
@ -181,56 +174,6 @@ export default Vue.extend({
});
},
async verifyUser() {
if (!await this.getConfirmed(this.$t('verify-confirm'))) return;
this.verifying = true;
const process = async () => {
await this.$root.api('admin/verify-user', { userId: this.user.id });
this.$root.dialog({
type: 'success',
text: this.$t('verified')
});
};
await process().catch(e => {
this.$root.dialog({
type: 'error',
text: e.toString()
});
});
this.verifying = false;
this.refreshUser();
},
async unverifyUser() {
if (!await this.getConfirmed(this.$t('unverify-confirm'))) return;
this.unverifying = true;
const process = async () => {
await this.$root.api('admin/unverify-user', { userId: this.user.id });
this.$root.dialog({
type: 'success',
text: this.$t('unverified')
});
};
await process().catch(e => {
this.$root.dialog({
type: 'error',
text: e.toString()
});
});
this.unverifying = false;
this.refreshUser();
},
async silenceUser() {
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;

View File

@ -34,7 +34,7 @@ body
.peg
display block
position absolute
right 0px
right 0
width 100px
height 100%
box-shadow 0 0 10px var(--primary), 0 0 5px var(--primary)

View File

@ -98,7 +98,7 @@ export default Vue.extend({
margin 0 auto
text-align center
background #fff
box-shadow 0px 4px 16px rgba(#000, 0.2)
box-shadow 0 4px 16px rgba(#000, 0.2)
> .fetching
margin 0

View File

@ -52,7 +52,7 @@ function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
pattern.ctrl == e.ctrlKey &&
pattern.shift == e.shiftKey &&
pattern.alt == e.altKey &&
e.metaKey == false
!e.metaKey
);
}

View File

@ -0,0 +1,42 @@
export function collectPageVars(content) {
const pageVars = [];
const collect = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'textInput') {
pageVars.push({
name: x.name,
type: 'string',
value: x.default || ''
});
} else if (x.type === 'textareaInput') {
pageVars.push({
name: x.name,
type: 'string',
value: x.default || ''
});
} else if (x.type === 'numberInput') {
pageVars.push({
name: x.name,
type: 'number',
value: x.default || 0
});
} else if (x.type === 'switch') {
pageVars.push({
name: x.name,
type: 'boolean',
value: x.default || false
});
} else if (x.type === 'counter') {
pageVars.push({
name: x.name,
type: 'number',
value: 0
});
} else if (x.children) {
collect(x.children);
}
}
};
collect(content);
return pageVars;
}

View File

@ -0,0 +1,31 @@
import parseAcct from '../../../../misc/acct/parse';
import { host as localHost } from '../../config';
export async function genSearchQuery(v: any, q: string) {
let host: string;
let userId: string;
if (q.split(' ').some(x => x.startsWith('@'))) {
for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substr(1))) {
if (at.includes('.')) {
if (at === localHost || at === '.') {
host = null;
} else {
host = at;
}
} else {
const user = await v.$root.api('users/show', parseAcct(at)).catch(x => null);
if (user) {
userId = user.id;
} else {
// todo: show error
}
}
}
}
return {
query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
host: host,
userId: userId
};
}

View File

@ -3,7 +3,7 @@ import { faHistory } from '@fortawesome/free-solid-svg-icons';
export async function search(v: any, q: string) {
q = q.trim();
if (q.startsWith('@')) {
if (q.startsWith('@') && !q.includes(' ')) {
v.$router.push(`/${q}`);
return;
}

View File

@ -11,9 +11,9 @@ export default function(me, settings, note) {
return (
(!isMyNote && note.reply && includesMutedWords(note.reply.text)) ||
(!isMyNote && note.renote && includesMutedWords(note.renote.text)) ||
(settings.showMyRenotes === false && isMyNote && isPureRenote) ||
(settings.showRenotedMyNotes === false && isPureRenote && note.renote.userId == me.id) ||
(settings.showLocalRenotes === false && isPureRenote && note.renote.user.host == null) ||
(!settings.showMyRenotes && isMyNote && isPureRenote) ||
(!settings.showRenotedMyNotes && isPureRenote && note.renote.userId == me.id) ||
(!settings.showLocalRenotes && isPureRenote && note.renote.user.host == null) ||
(!isMyNote && includesMutedWords(note.text))
);
}

View File

@ -21,7 +21,7 @@ export default class Stream extends EventEmitter {
const user = os.store.state.i;
this.stream = new ReconnectingWebsocket(wsUrl + (user ? `?i=${user.token}` : ''));
this.stream = new ReconnectingWebsocket(wsUrl + (user ? `?i=${user.token}` : ''), '', { minReconnectionDelay: 1 }); // https://github.com/pladaria/reconnecting-websocket/issues/91
this.stream.addEventListener('open', this.onOpen);
this.stream.addEventListener('close', this.onClose);
this.stream.addEventListener('message', this.onMessage);

View File

@ -80,7 +80,7 @@ export default Vue.extend({
ms(): number {
return this.now.getMilliseconds() * this.smooth;
}
},
s(): number {
return this.now.getSeconds();
},

View File

@ -1,7 +1,7 @@
<template>
<div class="felqjxyj" :class="{ splash }">
<div class="bg" ref="bg" @click="onBgClick"></div>
<div class="main" ref="main">
<div class="main" ref="main" :class="{ round: $store.state.device.roundedCorners }">
<template v-if="type == 'signin'">
<mk-signin/>
</template>
@ -22,7 +22,14 @@
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
<ui-input v-if="user" v-model="userInputValue" autofocus @keydown="onInputKeydown"><template #prefix>@</template></ui-input>
<ui-select v-if="select" v-model="selectedValue" autofocus>
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
<template v-if="select.items">
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
</template>
<template v-else>
<optgroup v-for="groupedItem in select.groupedItems" :label="groupedItem.label">
<option v-for="item in groupedItem.items" :value="item.value">{{ item.text }}</option>
</optgroup>
</template>
</ui-select>
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
<ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
@ -222,15 +229,17 @@ export default Vue.extend({
width calc(100% - 32px)
text-align center
background var(--face)
border-radius 8px
color var(--faceText)
opacity 0
&.round
border-radius 8px
> .icon
font-size 32px
&.success
color #37ec92
color #85da5a
&.error
color #ec4137

View File

@ -200,6 +200,7 @@ export default Vue.extend({
// 通信を取りこぼしてもいいように定期的にポーリングさせる
if (this.game.isStarted && !this.game.isEnded) {
this.pollingClock = setInterval(() => {
if (this.game.isEnded) return;
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
this.connection.send('check', {
crc32: crc32

View File

@ -230,7 +230,7 @@ export default Vue.extend({
this.game.map = Object.values(maps).find(x => x.name == this.mapName).data;
}
this.$forceUpdate();
this.updateSettings();
this.updateSettings('map');
},
onPixelClick(pos, pixel) {

View File

@ -36,7 +36,7 @@ export default Vue.extend({
return {
hide: true
};
}
},
computed: {
style(): any {
let url = `url(${

View File

@ -13,8 +13,8 @@
@click="navigate(user)"
tabindex="-1"
>
<mk-avatar class="avatar" :user="user"/>
<span class="name"><mk-user-name :user="user"/></span>
<mk-avatar class="avatar" :user="user" :key="user.id"/>
<span class="name"><mk-user-name :user="user" :key="user.id"/></span>
<span class="username">@{{ user | acct }}</span>
</li>
</ol>
@ -202,7 +202,7 @@ export default Vue.extend({
left 0
z-index 1
width 100%
box-shadow 0 0px 2px rgba(#000, 0.2)
box-shadow 0 0 2px rgba(#000, 0.2)
> .form
background rgba(0, 0, 0, 0.02)

View File

@ -8,7 +8,6 @@
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="is-cat" v-if="note.user.isCat">cat</span>
<span class="username"><mk-acct :user="note.user"/></span>
<span class="is-verified" v-if="note.user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
<div class="info">
<span class="app" v-if="note.app && !mini && $store.state.settings.showVia">via <b>{{ note.app.name }}</b></span>
<span class="mobile" v-if="note.viaMobile"><fa icon="mobile-alt"/></span>
@ -95,10 +94,6 @@ export default Vue.extend({
color var(--noteHeaderAcct)
flex-shrink 2147483647
> .is-verified
margin 0 .5em 0 0
color #4dabf7
> .info
margin-left auto
font-size 0.9em

View File

@ -0,0 +1,54 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.button') }}</template>
<section class="xfhsjczc">
<ui-input v-model="value.text"><span>{{ $t('blocks._button.text') }}</span></ui-input>
<ui-select v-model="value.action">
<template #label>{{ $t('blocks._button.action') }}</template>
<option value="dialog">{{ $t('blocks._button._action.dialog') }}</option>
<option value="resetRandom">{{ $t('blocks._button._action.resetRandom') }}</option>
</ui-select>
<ui-input v-if="value.action === 'dialog'" v-model="value.content"><span>{{ $t('blocks._button._action._dialog.content') }}</span></ui-input>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt
};
},
created() {
if (this.value.text == null) Vue.set(this.value, 'text', '');
if (this.value.action == null) Vue.set(this.value, 'action', 'dialog');
if (this.value.content == null) Vue.set(this.value, 'content', null);
},
});
</script>
<style lang="stylus" scoped>
.xfhsjczc
padding 0 16px 0 16px
</style>

View File

@ -0,0 +1,42 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.counter') }}</template>
<section style="padding: 0 16px 0 16px;">
<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._counter.name') }}</span></ui-input>
<ui-input v-model="value.text"><span>{{ $t('blocks._counter.text') }}</span></ui-input>
<ui-input v-model="value.inc" type="number"><span>{{ $t('blocks._counter.increment') }}</span></ui-input>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
},
});
</script>

View File

@ -0,0 +1,90 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faQuestion"/> {{ $t('blocks.if') }}</template>
<template #func>
<button @click="add()">
<fa :icon="faPlus"/>
</button>
</template>
<section class="romcojzs">
<ui-select v-model="value.var">
<template #label>{{ $t('blocks._if.variable') }}</template>
<option v-for="v in aiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('script.pageVariables')">
<option v-for="v in aiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$t('script.enviromentVariables')">
<option v-for="v in aiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
</ui-select>
<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
inject: ['getPageBlockList'],
props: {
value: {
required: true
},
aiScript: {
required: true,
},
},
data() {
return {
faPlus, faQuestion
};
},
beforeCreate() {
this.$options.components.XBlocks = require('../page-editor.blocks.vue').default
},
created() {
if (this.value.children == null) Vue.set(this.value, 'children', []);
if (this.value.var === undefined) Vue.set(this.value, 'var', null);
},
methods: {
async add() {
const { canceled, result: type } = await this.$root.dialog({
type: null,
title: this.$t('choose-block'),
select: {
groupedItems: this.getPageBlockList()
},
showCancelButton: true
});
if (canceled) return;
const id = uuid.v4();
this.value.children.push({ id, type });
},
}
});
</script>
<style lang="stylus" scoped>
.romcojzs
padding 0 16px 16px 16px
</style>

View File

@ -0,0 +1,78 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faImage"/> {{ $t('blocks.image') }}</template>
<template #func>
<button @click="choose()">
<fa :icon="faFolderOpen"/>
</button>
</template>
<section class="oyyftmcf">
<x-file-thumbnail class="preview" v-if="file" :file="file" :detail="true" fit="contain" @click="choose()"/>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { faImage, faFolderOpen } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
import XFileThumbnail from '../../drive-file-thumbnail.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer, XFileThumbnail
},
props: {
value: {
required: true
},
},
data() {
return {
file: null,
faPencilAlt, faImage, faFolderOpen
};
},
created() {
if (this.value.fileId === undefined) Vue.set(this.value, 'fileId', null);
},
mounted() {
if (this.value.fileId == null) {
this.choose();
} else {
this.$root.api('drive/files/show', {
fileId: this.value.fileId
}).then(file => {
this.file = file;
});
}
},
methods: {
async choose() {
this.$chooseDriveFile({
multiple: false
}).then(file => {
this.file = file;
this.value.fileId = file.id;
});
},
}
});
</script>
<style lang="stylus" scoped>
.oyyftmcf
> .preview
height 150px
</style>

View File

@ -0,0 +1,42 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.numberInput') }}</template>
<section style="padding: 0 16px 0 16px;">
<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._numberInput.name') }}</span></ui-input>
<ui-input v-model="value.text"><span>{{ $t('blocks._numberInput.text') }}</span></ui-input>
<ui-input v-model="value.default" type="number"><span>{{ $t('blocks._numberInput.default') }}</span></ui-input>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
},
});
</script>

View File

@ -0,0 +1,40 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faPaperPlane"/> {{ $t('blocks.post') }}</template>
<section style="padding: 0 16px 16px 16px;">
<ui-textarea v-model="value.text">{{ $t('blocks._post.text') }}</ui-textarea>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPaperPlane } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faPaperPlane
};
},
created() {
if (this.value.text == null) Vue.set(this.value, 'text', '');
},
});
</script>

View File

@ -0,0 +1,103 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faStickyNote"/> {{ value.title }}</template>
<template #func>
<button @click="rename()">
<fa :icon="faPencilAlt"/>
</button>
<button @click="add()">
<fa :icon="faPlus"/>
</button>
</template>
<section class="ilrvjyvi">
<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
inject: ['getPageBlockList'],
props: {
value: {
required: true
},
aiScript: {
required: true,
},
},
data() {
return {
faStickyNote, faPlus, faPencilAlt
};
},
beforeCreate() {
this.$options.components.XBlocks = require('../page-editor.blocks.vue').default
},
created() {
if (this.value.title == null) Vue.set(this.value, 'title', null);
if (this.value.children == null) Vue.set(this.value, 'children', []);
},
mounted() {
if (this.value.title == null) {
this.rename();
}
},
methods: {
async rename() {
const { canceled, result: title } = await this.$root.dialog({
title: 'Enter title',
input: {
type: 'text',
default: this.value.title
},
showCancelButton: true
});
if (canceled) return;
this.value.title = title;
},
async add() {
const { canceled, result: type } = await this.$root.dialog({
type: null,
title: this.$t('choose-block'),
select: {
groupedItems: this.getPageBlockList()
},
showCancelButton: true
});
if (canceled) return;
const id = uuid.v4();
this.value.children.push({ id, type });
},
}
});
</script>
<style lang="stylus" scoped>
.ilrvjyvi
> .children
padding 16px
</style>

View File

@ -0,0 +1,48 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.switch') }}</template>
<section class="kjuadyyj">
<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._switch.name') }}</span></ui-input>
<ui-input v-model="value.text"><span>{{ $t('blocks._switch.text') }}</span></ui-input>
<ui-switch v-model="value.default"><span>{{ $t('blocks._switch.default') }}</span></ui-switch>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
},
});
</script>
<style lang="stylus" scoped>
.kjuadyyj
padding 0 16px 16px 16px
</style>

View File

@ -0,0 +1,42 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.textInput') }}</template>
<section style="padding: 0 16px 0 16px;">
<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._textInput.name') }}</span></ui-input>
<ui-input v-model="value.text"><span>{{ $t('blocks._textInput.text') }}</span></ui-input>
<ui-input v-model="value.default" type="text"><span>{{ $t('blocks._textInput.default') }}</span></ui-input>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
},
});
</script>

View File

@ -0,0 +1,58 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faAlignLeft"/> {{ $t('blocks.text') }}</template>
<section class="ihymsbbe">
<textarea v-model="value.text"></textarea>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faAlignLeft } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faAlignLeft,
};
},
created() {
if (this.value.text == null) Vue.set(this.value, 'text', '');
},
});
</script>
<style lang="stylus" scoped>
.ihymsbbe
> textarea
display block
-webkit-appearance none
-moz-appearance none
appearance none
width 100%
min-width 100%
min-height 150px
border none
box-shadow none
padding 16px
background transparent
color var(--text)
font-size 14px
</style>

View File

@ -0,0 +1,42 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faBolt"/> {{ $t('blocks.textareaInput') }}</template>
<section style="padding: 0 16px 16px 16px;">
<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._textareaInput.name') }}</span></ui-input>
<ui-input v-model="value.text"><span>{{ $t('blocks._textareaInput.text') }}</span></ui-input>
<ui-textarea v-model="value.default"><span>{{ $t('blocks._textareaInput.default') }}</span></ui-textarea>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faBolt, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
},
});
</script>

View File

@ -0,0 +1,58 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faAlignLeft"/> {{ $t('blocks.textarea') }}</template>
<section class="ihymsbbe">
<textarea v-model="value.text"></textarea>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faAlignLeft } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
props: {
value: {
required: true
},
},
data() {
return {
faAlignLeft,
};
},
created() {
if (this.value.text == null) Vue.set(this.value, 'text', '');
},
});
</script>
<style lang="stylus" scoped>
.ihymsbbe
> textarea
display block
-webkit-appearance none
-moz-appearance none
appearance none
width 100%
min-width 100%
min-height 150px
border none
box-shadow none
padding 16px
background transparent
color var(--text)
font-size 14px
</style>

View File

@ -0,0 +1,65 @@
<template>
<x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :ai-script="aiScript"/>
</x-draggable>
</template>
<script lang="ts">
import Vue from 'vue';
import * as XDraggable from 'vuedraggable';
import XSection from './els/page-editor.el.section.vue';
import XText from './els/page-editor.el.text.vue';
import XTextarea from './els/page-editor.el.textarea.vue';
import XImage from './els/page-editor.el.image.vue';
import XButton from './els/page-editor.el.button.vue';
import XTextInput from './els/page-editor.el.text-input.vue';
import XTextareaInput from './els/page-editor.el.textarea-input.vue';
import XNumberInput from './els/page-editor.el.number-input.vue';
import XSwitch from './els/page-editor.el.switch.vue';
import XIf from './els/page-editor.el.if.vue';
import XPost from './els/page-editor.el.post.vue';
import XCounter from './els/page-editor.el.counter.vue';
export default Vue.extend({
components: {
XDraggable, XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter
},
props: {
value: {
type: Array,
required: true
},
aiScript: {
required: true,
},
},
computed: {
blocks() {
return this.value;
}
},
methods: {
updateItem(v) {
const i = this.blocks.findIndex(x => x.id === v.id);
const newValue = [
...this.blocks.slice(0, i),
v,
...this.blocks.slice(i + 1)
];
this.$emit('input', newValue);
},
removeItem(el) {
const i = this.blocks.findIndex(x => x.id === el.id);
const newValue = [
...this.blocks.slice(0, i),
...this.blocks.slice(i + 1)
];
this.$emit('input', newValue);
},
}
});
</script>

View File

@ -0,0 +1,146 @@
<template>
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
<header>
<div class="title"><slot name="header"></slot></div>
<div class="buttons">
<slot name="func"></slot>
<button v-if="removable" @click="remove()">
<fa :icon="faTrashAlt"/>
</button>
<button v-if="draggable" class="drag-handle">
<fa :icon="faBars"/>
</button>
<button @click="toggleContent(!showBody)">
<template v-if="showBody"><fa icon="angle-up"/></template>
<template v-else><fa icon="angle-down"/></template>
</button>
</div>
</header>
<p v-show="showBody" class="error" v-if="error != null">{{ $t('script.typeError', { slot: error.arg + 1, expect: $t(`script.types.${error.expect}`), actual: $t(`script.types.${error.actual}`) }) }}</p>
<p v-show="showBody" class="warn" v-if="warn != null">{{ $t('script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
<div v-show="showBody">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBars } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../i18n';
export default Vue.extend({
i18n: i18n('pages'),
props: {
expanded: {
type: Boolean,
default: true
},
removable: {
type: Boolean,
default: true
},
draggable: {
type: Boolean,
default: false
},
error: {
required: false,
default: null
},
warn: {
required: false,
default: null
}
},
data() {
return {
showBody: this.expanded,
faTrashAlt, faBars
};
},
methods: {
toggleContent(show: boolean) {
this.showBody = show;
this.$emit('toggle', show);
},
remove() {
this.$emit('remove');
}
}
});
</script>
<style lang="stylus" scoped>
.cpjygsrt
overflow hidden
background var(--face)
border solid 2px var(--pageBlockBorder)
border-radius 6px
&:hover
border solid 2px var(--pageBlockBorderHover)
&.warn
border solid 2px #dec44c
&.error
border solid 2px #f00
& + .cpjygsrt
margin-top 16px
> header
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color var(--faceHeaderText)
box-shadow 0 1px rgba(#000, 0.07)
> [data-icon]
margin-right 6px
&:empty
display none
> .buttons
position absolute
z-index 2
top 0
right 0
> button
padding 0
width 42px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
&:hover
color var(--faceTextButtonHover)
&:active
color var(--faceTextButtonActive)
.drag-handle
cursor move
> .warn
color #b19e49
margin 0
padding 16px 16px 0 16px
font-size 14px
> .error
color #f00
margin 0
padding 16px 16px 0 16px
font-size 14px
</style>

View File

@ -0,0 +1,271 @@
<template>
<x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable">
<template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
<template #func>
<button @click="changeType()">
<fa :icon="faPencilAlt"/>
</button>
</template>
<section v-if="value.type === null" class="pbglfege" @click="changeType()">
{{ $t('script.emptySlot') }}
</section>
<section v-else-if="value.type === 'text'" class="tbwccoaw">
<input v-model="value.value"/>
</section>
<section v-else-if="value.type === 'multiLineText'" class="tbwccoaw">
<textarea v-model="value.value"></textarea>
</section>
<section v-else-if="value.type === 'textList'" class="tbwccoaw">
<textarea v-model="value.value" :placeholder="$t('script.blocks._textList.info')"></textarea>
</section>
<section v-else-if="value.type === 'number'" class="tbwccoaw">
<input v-model="value.value" type="number"/>
</section>
<section v-else-if="value.type === 'ref'" class="hpdwcrvs">
<select v-model="value.value">
<option v-for="v in aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('script.argVariables')">
<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
</optgroup>
<optgroup :label="$t('script.pageVariables')">
<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$t('script.enviromentVariables')">
<option v-for="v in aiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
</select>
</section>
<section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
<ui-textarea v-model="slots">
<span>{{ $t('script.blocks._fn.slots') }}</span>
<template #desc>{{ $t('script.blocks._fn.slots-info') }}</template>
</ui-textarea>
<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`script.blocks._fn.arg1`)" :get-expected-type="() => null" :ai-script="aiScript" :fn-slots="value.value.slots" :name="name"/>
</section>
<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :ai-script="aiScript" :name="name" :key="i"/>
</section>
<section v-else class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :ai-script="aiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
import XContainer from './page-editor.container.vue';
import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
import { isLiteralBlock, funcDefs, blockDefs } from '../../../../../../misc/aiscript/index';
import * as uuid from 'uuid';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XContainer
},
inject: ['getScriptBlockList'],
props: {
getExpectedType: {
required: false,
default: null
},
value: {
required: true
},
title: {
required: false
},
removable: {
required: false,
default: false
},
aiScript: {
required: true,
},
name: {
required: true,
},
fnSlots: {
required: false,
},
draggable: {
required: false,
default: false
}
},
data() {
return {
error: null,
warn: null,
slots: '',
faPencilAlt
};
},
computed: {
icon(): any {
if (this.value.type === null) return null;
if (this.value.type.startsWith('fn:')) return faPlug;
return blockDefs.find(x => x.type === this.value.type).icon;
},
typeText(): any {
if (this.value.type === null) return null;
if (this.value.type.startsWith('fn:')) return this.value.type.split(':')[1];
return this.$t(`script.blocks.${this.value.type}`);
},
},
watch: {
slots() {
this.value.value.slots = this.slots.split('\n').map(x => ({
name: x,
type: null
}));
}
},
beforeCreate() {
this.$options.components.XV = require('./page-editor.script-block.vue').default;
},
created() {
if (this.value.value == null) Vue.set(this.value, 'value', null);
if (this.value.value && this.value.value.slots) this.slots = this.value.value.slots.map(x => x.name).join('\n');
this.$watch('value.type', (t) => {
this.warn = null;
if (this.value.type === 'fn') {
const id = uuid.v4();
this.value.value = {};
Vue.set(this.value.value, 'slots', []);
Vue.set(this.value.value, 'expression', { id, type: null });
return;
}
if (this.value.type && this.value.type.startsWith('fn:')) {
const fnName = this.value.type.split(':')[1];
const fn = this.aiScript.getVarByName(fnName);
const empties = [];
for (let i = 0; i < fn.value.slots.length; i++) {
const id = uuid.v4();
empties.push({ id, type: null });
}
Vue.set(this.value, 'args', empties);
return;
}
if (isLiteralBlock(this.value)) return;
const empties = [];
for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {
const id = uuid.v4();
empties.push({ id, type: null });
}
Vue.set(this.value, 'args', empties);
for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {
const inType = funcDefs[this.value.type].in[i];
if (typeof inType !== 'number') {
if (inType === 'number') this.value.args[i].type = 'number';
if (inType === 'string') this.value.args[i].type = 'text';
}
}
});
this.$watch('value.args', (args) => {
if (args == null) {
this.warn = null;
return;
}
const emptySlotIndex = args.findIndex(x => x.type === null);
if (emptySlotIndex !== -1 && emptySlotIndex < args.length) {
this.warn = {
slot: emptySlotIndex
};
} else {
this.warn = null;
}
}, {
deep: true
});
this.$watch('aiScript.variables', () => {
if (this.type != null && this.value) {
this.error = this.aiScript.typeCheck(this.value);
}
}, {
deep: true
});
},
methods: {
async changeType() {
const { canceled, result: type } = await this.$root.dialog({
type: null,
title: this.$t('select-type'),
select: {
groupedItems: this.getScriptBlockList(this.getExpectedType ? this.getExpectedType() : null)
},
showCancelButton: true
});
if (canceled) return;
this.value.type = type;
},
_getExpectedType(slot: number) {
return this.aiScript.getExpectedType(this.value, slot);
}
}
});
</script>
<style lang="stylus" scoped>
.turmquns
opacity 0.7
.pbglfege
opacity 0.5
padding 16px
text-align center
cursor pointer
color var(--text)
.tbwccoaw
> input
> textarea
display block
-webkit-appearance none
-moz-appearance none
appearance none
width 100%
max-width 100%
min-width 100%
border none
box-shadow none
padding 16px
font-size 16px
background transparent
color var(--text)
> textarea
min-height 100px
.hpdwcrvs
padding 16px
> select
display block
padding 4px
font-size 16px
width 100%
</style>

View File

@ -0,0 +1,473 @@
<template>
<div>
<div class="gwbmwxkm" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<header>
<div class="title"><fa :icon="faStickyNote"/> {{ readonly ? $t('read-page') : pageId ? $t('edit-page') : $t('new-page') }}</div>
<div class="buttons">
<button @click="del()" v-if="!readonly"><fa :icon="faTrashAlt"/></button>
<button @click="() => showOptions = !showOptions"><fa :icon="faCog"/></button>
<button @click="save()" v-if="!readonly"><fa :icon="faSave"/></button>
</div>
</header>
<section>
<a class="view" v-if="pageId" :href="`/@${ author.username }/pages/${ currentName }`" target="_blank"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</a>
<ui-input v-model="title">
<span>{{ $t('title') }}</span>
</ui-input>
<template v-if="showOptions">
<ui-input v-model="summary">
<span>{{ $t('summary') }}</span>
</ui-input>
<ui-input v-model="name">
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
<span>{{ $t('url') }}</span>
</ui-input>
<ui-switch v-model="alignCenter">{{ $t('align-center') }}</ui-switch>
<ui-select v-model="font">
<template #label>{{ $t('font') }}</template>
<option value="serif">{{ $t('fontSerif') }}</option>
<option value="sans-serif">{{ $t('fontSansSerif') }}</option>
</ui-select>
<div class="eyeCatch">
<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button>
<div v-else-if="eyeCatchingImage">
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
<ui-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catching-image') }}</ui-button>
</div>
</div>
</template>
<x-blocks class="content" v-model="content" :ai-script="aiScript"/>
<ui-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></ui-button>
</section>
</div>
<ui-container :body-togglable="true">
<template #header><fa :icon="faMagic"/> {{ $t('variables') }}</template>
<div class="qmuvgica">
<x-draggable tag="div" class="variables" v-show="variables.length > 0" :list="variables" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
<x-variable v-for="variable in variables"
:value="variable"
:removable="true"
@input="v => updateVariable(v)"
@remove="() => removeVariable(variable)"
:key="variable.name"
:ai-script="aiScript"
:name="variable.name"
:title="variable.name"
:draggable="true"
/>
</x-draggable>
<ui-button @click="addVariable()" class="add" v-if="!readonly"><fa :icon="faPlus"/></ui-button>
<ui-info><span v-html="$t('variables-info')"></span><a @click="() => moreDetails = true" style="display:block;">{{ $t('more-details') }}</a></ui-info>
<template v-if="moreDetails">
<ui-info><span v-html="$t('variables-info2')"></span></ui-info>
<ui-info><span v-html="$t('variables-info3')"></span></ui-info>
<ui-info><span v-html="$t('variables-info4')"></span></ui-info>
</template>
</div>
</ui-container>
<ui-container :body-togglable="true" :expanded="false">
<template #header><fa :icon="faCode"/> {{ $t('inspector') }}</template>
<div style="padding:0 32px 32px 32px;">
<ui-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('content') }}</ui-textarea>
<ui-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('variables') }}</ui-textarea>
</div>
</ui-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as XDraggable from 'vuedraggable';
import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../i18n';
import XVariable from './page-editor.script-block.vue';
import XBlocks from './page-editor.blocks.vue';
import * as uuid from 'uuid';
import { blockDefs } from '../../../../../../misc/aiscript/index';
import { ASTypeChecker } from '../../../../../../misc/aiscript/type-checker';
import { url } from '../../../../config';
import { collectPageVars } from '../../../scripts/collect-page-vars';
export default Vue.extend({
i18n: i18n('pages'),
components: {
XDraggable, XVariable, XBlocks
},
props: {
page: {
type: Object,
required: false
},
readonly: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {
author: this.$store.state.i,
pageId: null,
currentName: null,
title: '',
summary: null,
name: Date.now().toString(),
eyeCatchingImage: null,
eyeCatchingImageId: null,
font: 'sans-serif',
content: [],
alignCenter: false,
variables: [],
aiScript: null,
showOptions: false,
moreDetails: false,
url,
faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
};
},
watch: {
async eyeCatchingImageId() {
if (this.eyeCatchingImageId == null) {
this.eyeCatchingImage = null;
} else {
this.eyeCatchingImage = await this.$root.api('drive/files/show', {
fileId: this.eyeCatchingImageId,
});
}
},
},
created() {
this.aiScript = new ASTypeChecker();
this.$watch('variables', () => {
this.aiScript.variables = this.variables;
}, { deep: true });
this.$watch('content', () => {
this.aiScript.pageVars = collectPageVars(this.content);
}, { deep: true });
if (this.page) {
this.author = this.page.user;
this.pageId = this.page.id;
this.title = this.page.title;
this.name = this.page.name;
this.currentName = this.page.name;
this.summary = this.page.summary;
this.font = this.page.font;
this.alignCenter = this.page.alignCenter;
this.content = this.page.content;
this.variables = this.page.variables;
this.eyeCatchingImageId = this.page.eyeCatchingImageId;
} else {
const id = uuid.v4();
this.content = [{
id,
type: 'text',
text: 'Hello World!'
}];
}
},
provide() {
return {
readonly: this.readonly,
getScriptBlockList: this.getScriptBlockList,
getPageBlockList: this.getPageBlockList
}
},
methods: {
save() {
if (this.pageId) {
this.$root.api('pages/update', {
pageId: this.pageId,
title: this.title.trim(),
name: this.name.trim(),
summary: this.summary,
font: this.font,
alignCenter: this.alignCenter,
content: this.content,
variables: this.variables,
eyeCatchingImageId: this.eyeCatchingImageId,
}).then(page => {
this.currentName = this.name.trim();
this.$root.dialog({
type: 'success',
text: this.$t('page-updated')
});
});
} else {
this.$root.api('pages/create', {
title: this.title.trim(),
name: this.name.trim(),
summary: this.summary,
font: this.font,
alignCenter: this.alignCenter,
content: this.content,
variables: this.variables,
eyeCatchingImageId: this.eyeCatchingImageId,
}).then(page => {
this.pageId = page.id;
this.currentName = this.name.trim();
this.$root.dialog({
type: 'success',
text: this.$t('page-created')
});
this.$router.push(`/i/pages/edit/${this.pageId}`);
});
}
},
del() {
this.$root.dialog({
type: 'warning',
text: this.$t('are-you-sure-delete'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('pages/delete', {
pageId: this.pageId,
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('page-deleted')
});
this.$router.push(`/i/pages`);
});
});
},
async add() {
const { canceled, result: type } = await this.$root.dialog({
type: null,
title: this.$t('choose-block'),
select: {
groupedItems: this.getPageBlockList()
},
showCancelButton: true
});
if (canceled) return;
const id = uuid.v4();
this.content.push({ id, type });
},
async addVariable() {
let { canceled, result: name } = await this.$root.dialog({
title: this.$t('enter-variable-name'),
input: {
type: 'text',
},
showCancelButton: true
});
if (canceled) return;
name = name.trim();
if (this.aiScript.isUsedName(name)) {
this.$root.dialog({
type: 'error',
text: this.$t('the-variable-name-is-already-used')
});
return;
}
const id = uuid.v4();
this.variables.push({ id, name, type: null });
},
removeVariable(v) {
const i = this.variables.findIndex(x => x.name === v.name);
const newValue = [
...this.variables.slice(0, i),
...this.variables.slice(i + 1)
];
this.variables = newValue;
},
getPageBlockList() {
return [{
label: this.$t('content-blocks'),
items: [
{ value: 'section', text: this.$t('blocks.section') },
{ value: 'text', text: this.$t('blocks.text') },
{ value: 'image', text: this.$t('blocks.image') },
{ value: 'textarea', text: this.$t('blocks.textarea') },
]
}, {
label: this.$t('input-blocks'),
items: [
{ value: 'button', text: this.$t('blocks.button') },
{ value: 'textInput', text: this.$t('blocks.textInput') },
{ value: 'textareaInput', text: this.$t('blocks.textareaInput') },
{ value: 'numberInput', text: this.$t('blocks.numberInput') },
{ value: 'switch', text: this.$t('blocks.switch') },
{ value: 'counter', text: this.$t('blocks.counter') }
]
}, {
label: this.$t('special-blocks'),
items: [
{ value: 'if', text: this.$t('blocks.if') },
{ value: 'post', text: this.$t('blocks.post') }
]
}];
},
getScriptBlockList(type: string = null) {
const list = [];
const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type || typeof block.out === 'number');
for (const block of blocks) {
const category = list.find(x => x.category === block.category);
if (category) {
category.items.push({
value: block.type,
text: this.$t(`script.blocks.${block.type}`)
});
} else {
list.push({
category: block.category,
label: this.$t(`script.categories.${block.category}`),
items: [{
value: block.type,
text: this.$t(`script.blocks.${block.type}`)
}]
});
}
}
const userFns = this.variables.filter(x => x.type === 'fn');
if (userFns.length > 0) {
list.unshift({
label: this.$t(`script.categories.fn`),
items: userFns.map(v => ({
value: 'fn:' + v.name,
text: v.name
}))
});
}
return list;
},
setEyeCatchingImage() {
this.$chooseDriveFile({
multiple: false
}).then(file => {
this.eyeCatchingImageId = file.id;
});
},
removeEyeCatchingImage() {
this.eyeCatchingImageId = null;
}
}
});
</script>
<style lang="stylus" scoped>
.gwbmwxkm
overflow hidden
background var(--face)
margin-bottom 16px
&.round
border-radius 6px
&.shadow
box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
> header
background var(--faceHeader)
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color var(--faceHeaderText)
box-shadow 0 var(--lineWidth) rgba(#000, 0.07)
> [data-icon]
margin-right 6px
&:empty
display none
> .buttons
position absolute
z-index 2
top 0
right 0
> button
padding 0
width 42px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
&:hover
color var(--faceTextButtonHover)
&:active
color var(--faceTextButtonActive)
> section
padding 0 32px 32px 32px
@media (max-width 500px)
padding 0 16px 16px 16px
> .view
display inline-block
margin 16px 0 0 0
font-size 14px
> .content
margin-bottom 16px
> .eyeCatch
margin-bottom 16px
> div
> img
max-width 100%
.qmuvgica
padding 32px
@media (max-width 500px)
padding 16px
> .variables
margin-bottom 16px
> .add
margin-bottom 16px
</style>

View File

@ -0,0 +1,141 @@
<template>
<router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<article>
<header>
<h1 :title="page.title">{{ page.title }}</h1>
</header>
<p v-if="page.summary" :title="page.summary">{{ page.summary.length > 85 ? page.summary.slice(0, 85) + '…' : page.summary }}</p>
<footer>
<img class="icon" :src="page.user.avatarUrl"/>
<p>{{ page.user | userName }}</p>
</footer>
</article>
</router-link>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
page: {
type: Object,
required: true
},
},
});
</script>
<style lang="stylus" scoped>
.vhpxefrj
display block
overflow hidden
width 100%
background var(--face)
&.round
border-radius 8px
&.shadow
box-shadow 0 4px 16px rgba(#000, 0.1)
@media (min-width 500px)
box-shadow 0 8px 32px rgba(#000, 0.1)
> .thumbnail
position absolute
width 100px
height 100%
background-position center
background-size cover
display flex
justify-content center
align-items center
> button
font-size 3.5em
opacity: 0.7
&:hover
font-size 4em
opacity 0.9
& + article
left 100px
width calc(100% - 100px)
> article
padding 16px
> header
margin-bottom 8px
> h1
margin 0
font-size 1em
color var(--urlPreviewTitle)
> p
margin 0
color var(--urlPreviewText)
font-size 0.8em
> footer
margin-top 8px
height 16px
> img
display inline-block
width 16px
height 16px
margin-right 4px
vertical-align top
> p
display inline-block
margin 0
color var(--urlPreviewInfo)
font-size 0.8em
line-height 16px
vertical-align top
@media (max-width 700px)
> .thumbnail
position relative
width 100%
height 100px
& + article
left 0
width 100%
@media (max-width 550px)
font-size 12px
> .thumbnail
height 80px
> article
padding 12px
@media (max-width 500px)
font-size 10px
> .thumbnail
height 70px
> article
padding 8px
> header
margin-bottom 4px
> footer
margin-top 4px
> img
width 12px
height 12px
</style>

View File

@ -14,7 +14,7 @@
<section>
<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
<ui-input v-model="endpoint" :datalist="endpoints">
<ui-input v-model="endpoint" :datalist="endpoints" @change="onEndpointChange()">
<span>{{ $t('console.endpoint') }}</span>
</ui-input>
<ui-textarea v-model="body">
@ -80,6 +80,22 @@ export default Vue.extend({
this.sending = false;
this.res = JSON5.stringify(err, null, 2);
});
},
onEndpointChange() {
this.$root.api('endpoint', { endpoint: this.endpoint }).then(endpoint => {
const body = {};
for (const p of endpoint.params) {
body[p.name] =
p.type === 'String' ? '' :
p.type === 'Number' ? 0 :
p.type === 'Boolean' ? false :
p.type === 'Array' ? [] :
p.type === 'Object' ? {} :
null;
}
this.body = JSON5.stringify(body, null, 2);
});
}
}
});

View File

@ -54,7 +54,11 @@ export default Vue.extend({
},
mounted() {
document.cookie = `i=${this.$store.state.i.token}`;
if (!document.cookie.match(/i=(\w+)/)) {
document.cookie = `i=${this.$store.state.i.token}; path=/;` +
` domain=${document.location.hostname}; max-age=31536000;` +
(document.location.protocol.startsWith('https') ? ' secure' : '');
}
this.$watch('$store.state.i', () => {
if (this.$store.state.i.twitter) {
if (this.twitterForm) this.twitterForm.close();

View File

@ -290,12 +290,17 @@ export default Vue.extend({
this.exportTarget == 'mute' ? 'i/export-mute' :
this.exportTarget == 'blocking' ? 'i/export-blocking' :
this.exportTarget == 'user-lists' ? 'i/export-user-lists' :
null, {});
this.$root.dialog({
type: 'info',
text: this.$t('export-requested')
});
null, {}).then(() => {
this.$root.dialog({
type: 'info',
text: this.$t('export-requested')
});
}).catch((e: any) => {
this.$root.dialog({
type: 'error',
text: e.message
});
});
},
doImport() {

View File

@ -273,7 +273,7 @@ export default Vue.extend({
import_() {
(this.$refs.file as any).click();
}
},
export_() {
const blob = new Blob([this.selectedThemeCode], {

View File

@ -10,14 +10,14 @@
<span>{{ $t('password') }}</span>
<template #prefix><fa icon="lock"/></template>
</ui-input>
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required>
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
<span>{{ $t('@.2fa') }}</span>
<template #prefix><fa icon="gavel"/></template>
</ui-input>
<ui-button type="submit" :disabled="signing">{{ signing ? $t('signing-in') : $t('@.signin') }}</ui-button>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`">{{ $t('signin-with-twitter') }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`">{{ $t('signin-with-github') }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`">{{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="['fab', 'twitter']"/> {{ $t('signin-with-twitter') }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="['fab', 'github']"/> {{ $t('signin-with-github') }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="['fab', 'discord']"/> {{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p>
</form>
</template>

View File

@ -1,7 +1,7 @@
<template>
<component class="dmtdnykelhudezerjlfpbhgovrgnqqgr"
:is="link ? 'a' : 'button'"
:class="{ inline, primary, wait }"
:class="{ inline, primary, wait, round: $store.state.device.roundedCorners }"
:type="type"
@click="$emit('click')"
@mousedown="onMousedown"
@ -116,7 +116,6 @@ export default Vue.extend({
font-size 16px
line-height 24px
border none
border-radius 6px
outline none
box-shadow none
text-decoration none
@ -124,6 +123,9 @@ export default Vue.extend({
color var(--text)
background var(--buttonBg)
&.round
border-radius 6px
&:not(:disabled):hover
background var(--buttonHoverBg)
@ -157,7 +159,9 @@ export default Vue.extend({
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 10px
&.round:focus:after
border-radius 10px
&:not(.inline) + .dmtdnykelhudezerjlfpbhgovrgnqqgr
margin-top 16px
@ -197,7 +201,6 @@ export default Vue.extend({
left 0
width 100%
height 100%
border-radius 6px
overflow hidden
>>> div
@ -210,6 +213,9 @@ export default Vue.extend({
transform scale(1)
transition all 0.5s cubic-bezier(0, .5, .5, 1)
&.round > .ripples
border-radius 6px
&.primary > .ripples >>> div
background rgba(0, 0, 0, 0.15)

View File

@ -23,6 +23,7 @@
@focus="focused = true"
@blur="focused = false"
@keydown="$emit('keydown', $event)"
@change="$emit('change', $event)"
:list="id"
>
<input v-else ref="input"
@ -38,6 +39,7 @@
@focus="focused = true"
@blur="focused = false"
@keydown="$emit('keydown', $event)"
@change="$emit('change', $event)"
:list="id"
>
<datalist :id="id" v-if="datalist">
@ -60,7 +62,7 @@
<div class="suffix" ref="suffix"><slot name="suffix"></slot></div>
</div>
<div class="toggle" v-if="withPasswordToggle">
<a @click='togglePassword'>
<a @click="togglePassword">
<span v-if="type == 'password'"><fa :icon="['fa', 'eye']"/> {{ $t('@.show-password') }}</span>
<span v-if="type != 'password'"><fa :icon="['far', 'eye-slash']"/> {{ $t('@.hide-password') }}</span>
</a>
@ -182,7 +184,11 @@ export default Vue.extend({
this.v = v;
},
v(v) {
this.$emit('input', v);
if (this.type === 'number') {
this.$emit('input', parseInt(v, 10));
} else {
this.$emit('input', v);
}
if (this.withPasswordMeter) {
if (v == '') {
@ -316,7 +322,7 @@ root(fill)
> .value
display block
width 0%
width 0
height 100%
background transparent
border-radius 6px

View File

@ -5,10 +5,9 @@
<span class="label" ref="label"><slot name="label"></slot></span>
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<select ref="input"
:value="v"
v-model="v"
:required="required"
:disabled="disabled"
@input="$emit('input', $event.target.value)"
@focus="focused = true"
@blur="focused = false"
>
@ -56,20 +55,22 @@ export default Vue.extend({
},
data() {
return {
v: this.value,
focused: false
};
},
computed: {
v: {
get() {
return this.value;
},
set(v) {
this.$emit('input', v);
}
},
filled(): boolean {
return this.v != '' && this.v != null;
}
},
watch: {
value(v) {
this.v = v;
}
},
mounted() {
if (this.$refs.prefix) {
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';

View File

@ -60,9 +60,9 @@ export default Vue.extend({
},
methods: {
init() {
async init() {
this.fetching = true;
this.makePromise().then(x => {
await (this.makePromise()).then(x => {
if (Array.isArray(x)) {
this.us = x;
} else {
@ -76,9 +76,9 @@ export default Vue.extend({
});
},
fetchMoreUsers() {
async fetchMoreUsers() {
this.fetchingMoreUsers = true;
this.makePromise(this.cursor).then(x => {
await (this.makePromise(this.cursor)).then(x => {
this.us = this.us.concat(x.users);
this.cursor = x.cursor;
this.fetchingMoreUsers = false;
@ -166,7 +166,7 @@ export default Vue.extend({
> .follow-button
position absolute
top 8px
right 0px
right 0
> .more
display block

View File

@ -160,7 +160,7 @@ export default Vue.extend({
this.$emit('top');
}
if (this.$store.state.settings.fetchOnScroll !== false) {
if (this.$store.state.settings.fetchOnScroll) {
const current = this.$refs.body.scrollTop + this.$refs.body.clientHeight;
if (current > this.$refs.body.scrollHeight - 1) this.$emit('bottom');
}

View File

@ -110,11 +110,11 @@ export default Vue.extend({
this.init();
},
init() {
async init() {
this.queue = [];
this.notes = [];
this.fetching = true;
this.makePromise().then(x => {
await (this.makePromise()).then(x => {
if (Array.isArray(x)) {
this.notes = x;
} else {
@ -129,10 +129,10 @@ export default Vue.extend({
});
},
fetchMore() {
async fetchMore() {
if (!this.more || this.moreFetching) return;
this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
this.notes = this.notes.concat(x.notes);
this.more = x.more;
this.moreFetching = false;

View File

@ -14,6 +14,7 @@
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20;
@ -25,10 +26,10 @@ export default Vue.extend({
data() {
return {
makePromise: cursor => this.$root.api('notes/search', {
makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1,
offset: cursor ? cursor : undefined,
query: this.q
...(await genSearchQuery(this, this.q))
}).then(notes => {
if (notes.length == limit + 1) {
notes.pop();

View File

@ -205,7 +205,7 @@ export default Vue.extend({
top -32px
left 0
right 0
width 0px
width 0
margin 0 auto
border-top solid 16px transparent
border-left solid 16px transparent

View File

@ -102,7 +102,7 @@ class Autocomplete {
}
}
if (isHashtag && opened == false) {
if (isHashtag && !opened) {
const hashtag = text.substr(hashtagIndex + 1);
if (!hashtag.includes(' ')) {
this.open('hashtag', hashtag);
@ -110,7 +110,7 @@ class Autocomplete {
}
}
if (isEmoji && opened == false) {
if (isEmoji && !opened) {
const emoji = text.substr(emojiIndex + 1);
if (!emoji.includes(' ')) {
this.open('emoji', emoji);

View File

@ -13,8 +13,8 @@
<template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template>
<div class="vxjfqztj">
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.name}`" :key="'local:' + tag.name" class="local">{{ tag.name }}</router-link>
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.name}`" :key="'remote:' + tag.name">{{ tag.name }}</router-link>
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link>
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link>
</div>
</ui-container>
@ -26,8 +26,8 @@
</mk-user-list>
<template v-if="tag == null">
<mk-user-list :make-promise="verifiedUsers">
<fa :icon="faBookmark" fixed-width/>{{ $t('verified-users') }}
<mk-user-list :make-promise="pinnedUsers">
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
</mk-user-list>
<mk-user-list :make-promise="popularUsers">
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
@ -60,12 +60,7 @@ export default Vue.extend({
data() {
return {
verifiedUsers: () => this.$root.api('users', {
state: 'verified',
origin: 'local',
sort: '+follower',
limit: 10
}),
pinnedUsers: () => this.$root.api('pinned-users'),
popularUsers: () => this.$root.api('users', {
state: 'alive',
origin: 'local',

View File

@ -0,0 +1,40 @@
<template>
<component :is="'x-' + value.type" :value="value" :page="page" :script="script" :key="value.id" :h="h"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XText from './page.text.vue';
import XSection from './page.section.vue';
import XImage from './page.image.vue';
import XButton from './page.button.vue';
import XNumberInput from './page.number-input.vue';
import XTextInput from './page.text-input.vue';
import XTextareaInput from './page.textarea-input.vue';
import XSwitch from './page.switch.vue';
import XIf from './page.if.vue';
import XTextarea from './page.textarea.vue';
import XPost from './page.post.vue';
import XCounter from './page.counter.vue';
export default Vue.extend({
components: {
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter
},
props: {
value: {
required: true
},
script: {
required: true
},
page: {
required: true
},
h: {
required: true
}
},
});
</script>

View File

@ -0,0 +1,42 @@
<template>
<div>
<ui-button class="kudkigyw" @click="click()">{{ script.interpolate(value.text) }}</ui-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
methods: {
click() {
if (this.value.action === 'dialog') {
this.script.eval();
this.$root.dialog({
text: this.script.interpolate(this.value.content)
});
} else if (this.value.action === 'resetRandom') {
this.script.aiScript.updateRandomSeed(Math.random());
this.script.eval();
}
}
}
});
</script>
<style lang="stylus" scoped>
.kudkigyw
display inline-block
min-width 300px
max-width 450px
margin 8px 0
</style>

View File

@ -0,0 +1,47 @@
<template>
<div>
<ui-button class="llumlmnx" @click="click()">{{ script.interpolate(value.text) }}</ui-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
v: 0,
};
},
watch: {
v() {
this.script.aiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
}
},
methods: {
click() {
this.v = this.v + (this.value.inc || 1);
}
}
});
</script>
<style lang="stylus" scoped>
.llumlmnx
display inline-block
min-width 300px
max-width 450px
margin 8px 0
</style>

View File

@ -0,0 +1,30 @@
<template>
<div v-show="script.vars[value.var]">
<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
},
page: {
required: true
},
h: {
required: true
}
},
beforeCreate() {
this.$options.components.XBlock = require('./page.block.vue').default
},
});
</script>

View File

@ -0,0 +1,36 @@
<template>
<div class="lzyxtsnt">
<img v-if="image" :src="image.url"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
page: {
required: true
},
},
data() {
return {
image: null,
};
},
created() {
this.image = this.page.attachedFiles.find(x => x.id === this.value.fileId);
}
});
</script>
<style lang="stylus" scoped>
.lzyxtsnt
> img
max-width 100%
</style>

View File

@ -0,0 +1,41 @@
<template>
<div>
<ui-input class="kudkigyw" v-model="v" type="number">{{ script.interpolate(value.text) }}</ui-input>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
v: this.value.default,
};
},
watch: {
v() {
this.script.aiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
}
}
});
</script>
<style lang="stylus" scoped>
.kudkigyw
display inline-block
min-width 300px
max-width 450px
margin 8px 0
</style>

View File

@ -0,0 +1,68 @@
<template>
<div class="ngbfujlo">
<ui-textarea class="textarea" :value="text" readonly></ui-textarea>
<ui-button primary @click="post()" :disabled="posting || posted">{{ posted ? $t('posted-from-post-form') : $t('post-from-post-form') }}</ui-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
export default Vue.extend({
i18n: i18n('pages'),
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
posted: false,
posting: false,
};
},
created() {
this.$watch('script.vars', () => {
this.text = this.script.interpolate(this.value.text);
}, { deep: true });
},
methods: {
post() {
this.posting = true;
this.$root.api('notes/create', {
text: this.text,
}).then(() => {
this.posted = true;
this.$root.dialog({
type: 'success',
splash: true
});
});
}
}
});
</script>
<style lang="stylus" scoped>
.ngbfujlo
padding 0 32px 32px 32px
border solid 2px var(--pageBlockBorder)
border-radius 6px
@media (max-width 600px)
padding 0 16px 16px 16px
> .textarea
margin-top 16px
margin-bottom 16px
</style>

View File

@ -0,0 +1,55 @@
<template>
<section class="sdgxphyu">
<component :is="'h' + h">{{ value.title }}</component>
<div class="children">
<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h + 1"/>
</div>
</section>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
},
page: {
required: true
},
h: {
required: true
}
},
beforeCreate() {
this.$options.components.XBlock = require('./page.block.vue').default
},
});
</script>
<style lang="stylus" scoped>
.sdgxphyu
margin 1.5em 0
> h2
font-size 1.35em
margin 0 0 0.5em 0
> h3
font-size 1em
margin 0 0 0.5em 0
> h4
font-size 1em
margin 0 0 0.5em 0
> .children
//padding 16px
</style>

View File

@ -0,0 +1,43 @@
<template>
<div class="hkcxmtwj">
<ui-switch v-model="v">{{ script.interpolate(value.text) }}</ui-switch>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
v: this.value.default,
};
},
watch: {
v() {
this.script.aiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
}
}
});
</script>
<style lang="stylus" scoped>
.hkcxmtwj
display inline-block
margin 16px auto
& + .hkcxmtwj
margin-left 16px
</style>

View File

@ -0,0 +1,41 @@
<template>
<div>
<ui-input class="kudkigyw" v-model="v" type="text">{{ script.interpolate(value.text) }}</ui-input>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
v: this.value.default,
};
},
watch: {
v() {
this.script.aiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
}
}
});
</script>
<style lang="stylus" scoped>
.kudkigyw
display inline-block
min-width 300px
max-width 450px
margin 8px 0
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="">
<mfm :text="text" :is-note="false" :i="$store.state.i" :key="text"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
};
},
created() {
this.$watch('script.vars', () => {
this.text = this.script.interpolate(this.value.text);
}, { deep: true });
}
});
</script>
<style lang="stylus" scoped>
</style>

View File

@ -0,0 +1,36 @@
<template>
<div>
<ui-textarea class="" v-model="v">{{ script.interpolate(value.text) }}</ui-textarea>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
v: this.value.default,
};
},
watch: {
v() {
this.script.aiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
}
}
});
</script>
<style lang="stylus" scoped>
</style>

View File

@ -0,0 +1,33 @@
<template>
<ui-textarea class="" :value="text" readonly></ui-textarea>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
};
},
created() {
this.$watch('script.vars', () => {
this.text = this.script.interpolate(this.value.text);
}, { deep: true });
}
});
</script>
<style lang="stylus" scoped>
</style>

View File

@ -0,0 +1,164 @@
<template>
<div v-if="page" class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }">
<header>
<div class="title">{{ page.title }}</div>
</header>
<div v-if="script">
<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/>
</div>
<footer>
<small>@{{ page.user.username }}</small>
<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
</footer>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
import { faICursor, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons';
import XBlock from './page.block.vue';
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
import { collectPageVars } from '../../../scripts/collect-page-vars';
import { url } from '../../../../config';
class Script {
public aiScript: ASEvaluator;
private onError: any;
public vars: Record<string, any>;
constructor(aiScript, onError) {
this.aiScript = aiScript;
this.onError = onError;
this.eval();
}
public eval() {
try {
this.vars = this.aiScript.evaluateVars();
} catch (e) {
this.onError(e);
}
}
public interpolate(str: string) {
if (str == null) return null;
return str.replace(/{(.+?)}/g, match => {
const v = this.vars[match.slice(1, -1).trim()];
return v == null ? 'NULL' : v.toString();
});
}
}
export default Vue.extend({
i18n: i18n('pages'),
components: {
XBlock
},
props: {
pageName: {
type: String,
required: true
},
username: {
type: String,
required: true
},
},
data() {
return {
page: null,
script: null,
faPlus, faICursor, faSave, faStickyNote
};
},
created() {
this.$root.api('pages/show', {
name: this.pageName,
username: this.username,
}).then(page => {
this.page = page;
const pageVars = this.getPageVars();
this.script = new Script(new ASEvaluator(this.page.variables, pageVars, {
randomSeed: Math.random(),
user: page.user,
visitor: this.$store.state.i,
page: page,
url: url
}), e => {
console.dir(e);
});
});
},
methods: {
getPageVars() {
return collectPageVars(this.page.content);
},
}
});
</script>
<style lang="stylus" scoped>
.iroscrza
overflow hidden
background var(--face)
&.center
text-align center
&.round
border-radius 6px
&.shadow
box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
> header
> .title
z-index 1
margin 0
padding 32px 64px
font-size 24px
font-weight bold
color var(--text)
box-shadow 0 var(--lineWidth) rgba(#000, 0.07)
@media (max-width 600px)
padding 16px 32px
font-size 20px
> div
color var(--text)
padding 48px 64px
font-size 18px
@media (max-width 600px)
padding 24px 32px
font-size 16px
> footer
color var(--text)
padding 0 64px 38px 64px
@media (max-width 600px)
padding 0 32px 28px 32px
> small
display block
opacity 0.5
> a
font-size 14px
> a + a
margin-left 8px
</style>

View File

@ -18,7 +18,7 @@
<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
<p v-if="!fetching">
<mfm v-if="announcements.length != 0" :text="announcements[i].text"/>
<mfm v-if="announcements.length != 0" :text="announcements[i].text" :key="i"/>
<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
</p>

View File

@ -156,7 +156,12 @@ init(async (launch, os) => {
{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
{ path: '/i/pages', component: () => import('./views/home/pages.vue').then(m => m.default) },
]},
{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
{ path: '/i/messaging/:user', component: MkMessagingRoom },
{ path: '/i/drive', component: MkDrive },
{ path: '/i/drive/folder/:folder', component: MkDrive },

View File

@ -750,12 +750,17 @@ export default Vue.extend({
bottom 0
animation-delay -1.0s
@keyframes sk-rotate { 100% { transform: rotate(360deg); }}
@keyframes sk-rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes sk-bounce {
0%, 100% {
transform: scale(0.0);
} 50% {
}
50% {
transform: scale(1.0);
}
}

View File

@ -105,9 +105,9 @@ export default Vue.extend({
this.init();
},
init() {
async init() {
this.fetching = true;
this.makePromise().then(x => {
await (this.makePromise()).then(x => {
if (Array.isArray(x)) {
this.notes = x;
} else {
@ -122,7 +122,7 @@ export default Vue.extend({
});
},
fetchMore() {
async fetchMore() {
if (!this.more || this.moreFetching || this.notes.length === 0) return;
this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
@ -181,7 +181,7 @@ export default Vue.extend({
this.releaseQueue();
}
if (this.$store.state.settings.fetchOnScroll !== false) {
if (this.$store.state.settings.fetchOnScroll) {
const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.fetchMore();
}

View File

@ -377,7 +377,7 @@ export default Vue.extend({
}, err => {
this.$root.dialog({
type: 'error',
title: this.$t('error')
title: this.$t('error'),
text: err.message
});
}, {

View File

@ -9,35 +9,42 @@
<ul>
<li>
<router-link :to="`/@${ $store.state.i.username }`">
<i><fa icon="user"/></i>
<i><fa icon="user" fixed-width/></i>
<span>{{ $t('profile') }}</span>
<i><fa icon="angle-right"/></i>
</router-link>
</li>
<li @click="drive">
<p>
<i><fa icon="cloud"/></i>
<i><fa icon="cloud" fixed-width/></i>
<span>{{ $t('@.drive') }}</span>
<i><fa icon="angle-right"/></i>
</p>
</li>
<li>
<router-link to="/i/favorites">
<i><fa icon="star"/></i>
<i><fa icon="star" fixed-width/></i>
<span>{{ $t('@.favorites') }}</span>
<i><fa icon="angle-right"/></i>
</router-link>
</li>
<li @click="list">
<p>
<i><fa icon="list"/></i>
<i><fa icon="list" fixed-width/></i>
<span>{{ $t('lists') }}</span>
<i><fa icon="angle-right"/></i>
</p>
</li>
<li>
<router-link to="/i/pages">
<i><fa :icon="faStickyNote" fixed-width/></i>
<span>{{ $t('@.pages') }}</span>
<i><fa icon="angle-right"/></i>
</router-link>
</li>
<li @click="followRequests" v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
<p>
<i><fa :icon="['far', 'envelope']"/></i>
<i><fa :icon="['far', 'envelope']" fixed-width/></i>
<span>{{ $t('follow-requests') }}<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>
<i><fa icon="angle-right"/></i>
</p>
@ -46,14 +53,14 @@
<ul>
<li>
<router-link to="/i/settings">
<i><fa icon="cog"/></i>
<i><fa icon="cog" fixed-width/></i>
<span>{{ $t('@.settings') }}</span>
<i><fa icon="angle-right"/></i>
</router-link>
</li>
<li v-if="$store.state.i.isAdmin || $store.state.i.isModerator">
<a href="/admin">
<i><fa icon="terminal"/></i>
<i><fa icon="terminal" fixed-width/></i>
<span>{{ $t('admin') }}</span>
<i><fa icon="angle-right"/></i>
</a>
@ -76,7 +83,7 @@
<ul>
<li @click="signout">
<p class="signout">
<i><fa icon="power-off"/></i>
<i><fa icon="power-off" fixed-width/></i>
<span>{{ $t('@.signout') }}</span>
</p>
</li>
@ -95,14 +102,14 @@ import MkFollowRequestsWindow from './received-follow-requests-window.vue';
import MkDriveWindow from './drive-window.vue';
import contains from '../../../common/scripts/contains';
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('desktop/views/components/ui.header.account.vue'),
data() {
return {
isOpen: false,
faHome, faColumns, faMoon, faSun
faHome, faColumns, faMoon, faSun, faStickyNote
};
},
computed: {

View File

@ -480,7 +480,7 @@ export default Vue.extend({
&:focus
&:not([data-is-modal])
> .body
box-shadow 0 0 0px 1px var(--primaryAlpha05), 0 2px 12px 0 var(--desktopWindowShadow)
box-shadow 0 0 0 1px var(--primaryAlpha05), 0 2px 12px 0 var(--desktopWindowShadow)
> .handle
$size = 8px

View File

@ -0,0 +1,92 @@
<template>
<div class="rknalgpo" v-if="!fetching">
<ui-button @click="create()"><fa :icon="faPlus"/></ui-button>
<sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="page in pages">
<x-page-preview class="page" :page="page" :key="page.id"/>
</template>
</sequential-entrance>
<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
import XPagePreview from '../../../common/views/components/page-preview.vue';
export default Vue.extend({
i18n: i18n(),
components: {
XPagePreview
},
data() {
return {
fetching: true,
pages: [],
existMore: false,
moreFetching: false,
faStickyNote, faPlus
};
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.fetching = true;
this.$root.api('i/pages', {
limit: 11
}).then(pages => {
if (pages.length == 11) {
this.existMore = true;
pages.pop();
}
this.pages = pages;
this.fetching = false;
Progress.done();
});
},
fetchMore() {
this.moreFetching = true;
this.$root.api('i/pages', {
limit: 11,
untilId: this.pages[this.pages.length - 1].id
}).then(pages => {
if (pages.length == 11) {
this.existMore = true;
pages.pop();
} else {
this.existMore = false;
}
this.pages = this.pages.concat(pages);
this.moreFetching = false;
});
},
create() {
this.$router.push(`/i/pages/new`);
}
}
});
</script>
<style lang="stylus" scoped>
.rknalgpo
margin 0 auto
> * > .page
margin-bottom 8px
@media (min-width 500px)
> * > .page
margin-bottom 16px
</style>

View File

@ -14,6 +14,7 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20;
@ -21,10 +22,10 @@ export default Vue.extend({
i18n: i18n('desktop/views/pages/search.vue'),
data() {
return {
makePromise: cursor => this.$root.api('notes/search', {
makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1,
offset: cursor ? cursor : undefined,
query: this.q
...(await genSearchQuery(this, this.q))
}).then(notes => {
if (notes.length == limit + 1) {
notes.pop();

View File

@ -0,0 +1,67 @@
<template>
<mk-ui>
<main>
<x-page-editor v-if="page !== undefined" :page="page" :readonly="readonly"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPageEditor: () => import('../../../common/views/components/page-editor/page-editor.vue').then(m => m.default)
},
props: {
pageId: {
type: String,
required: false
},
pageName: {
type: String,
required: false
},
user: {
type: String,
required: false
}
},
data() {
return {
page: undefined,
readonly: false
};
},
created() {
if (this.pageId) {
this.$root.api('pages/show', {
pageId: this.pageId,
}).then(page => {
this.page = page;
});
} else if (this.pageName && this.user) {
this.$root.api('pages/show', {
name: this.pageName,
username: this.user,
}).then(page => {
this.readonly = true;
this.page = page;
});
} else {
this.page = null;
}
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 900px
</style>

View File

@ -0,0 +1,36 @@
<template>
<mk-ui>
<main>
<x-page :page-name="page" :username="user"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPage: () => import('../../../common/views/pages/page/page.vue').then(m => m.default)
},
props: {
page: {
type: String,
required: true
},
user: {
type: String,
required: true
},
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 950px
</style>

View File

@ -352,7 +352,7 @@ export default Vue.extend({
padding 0 16px
line-height 48px
background var(--faceHeader)
box-shadow 0 1px 0px rgba(0, 0, 0, 0.1)
box-shadow 0 1px 0 rgba(0, 0, 0, 0.1)
& + div
max-height calc(100% - 48px)

Some files were not shown because too many files have changed in this diff Show More