Compare commits
	
		
			124 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d5ab6b41c9 | ||
|   | ffdd0b7de7 | ||
|   | 1808eb6eee | ||
|   | 438563b505 | ||
|   | 92dfcdad57 | ||
|   | c178cfabfa | ||
|   | 260e4c955d | ||
|   | 0c46f5ce70 | ||
|   | 6d67cd07a0 | ||
|   | fb8af53751 | ||
|   | 37999f4af7 | ||
|   | 3b6ab327c1 | ||
|   | d3ff3a7d54 | ||
|   | cf36106520 | ||
|   | 1642fbec31 | ||
|   | b195fd8145 | ||
|   | 5f59b980a7 | ||
|   | 2a5c19cd01 | ||
|   | 42e007ddb7 | ||
|   | 756dc397d9 | ||
|   | 8f714b5b12 | ||
|   | 06bb2a1c7c | ||
|   | ac50bb9225 | ||
|   | 8fd95de25b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0e14b2eba4 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 08413a7550 | ||
|   | 5e0f2a5b06 | ||
|   | 3b505709c6 | ||
|   | af32d1f81e | ||
|   | 67d8773e38 | ||
|   | e445d39c2f | ||
|   | 961ed969db | ||
|   | e9a3495225 | ||
|   | 6c5a78aeb2 | ||
|   | 34e249317a | ||
|   | 6d8ea89f09 | ||
|   | 64f89ba13e | ||
|   | f6b2f76bbf | ||
|   | 1235bef038 | ||
|   | 2e11f3a843 | ||
|   | 84b7e0bb7d | ||
|   | 9f5dc2c0df | ||
|   | e640dbc501 | ||
|   | 85db090d9f | ||
|   | 9f2d8e1d51 | ||
|   | 0c98a90b75 | ||
|   | 0047920c1a | ||
|   | e4bb534f20 | ||
|   | 3fc04fcdc5 | ||
|   | e542dcac30 | ||
|   | a0b13505a0 | ||
|   | 389f9bfea2 | ||
|   | 630a534cee | ||
|   | 5744c391e6 | ||
|   | b9b05a7401 | ||
|   | 359470a263 | ||
|   | 3fe934ee62 | ||
|   | 3abe632f06 | ||
|   | 65961bc15b | ||
|   | 12f932d48a | ||
|   | 54e9147782 | ||
|   | 31b7626d01 | ||
|   | 200ebefe92 | ||
|   | 9d29a2e85a | ||
|   | c62a225542 | ||
|   | d5d995a3e6 | ||
|   | b7f10fdc10 | ||
|   | cbba03b376 | ||
|   | f84e9c7dc8 | ||
|   | a22ddb1fb9 | ||
|   | 0d23ce3d45 | ||
|   | 9719387bee | ||
|   | dca110ebaa | ||
|   | 136f23c7ad | ||
|   | 0963e6d6e1 | ||
|   | 712802e682 | ||
|   | abe99c3c73 | ||
|   | d7a3b71028 | ||
|   | 10c434f24a | ||
|   | fe46c53ea6 | ||
|   | cdd123dfd3 | ||
|   | a1a3ee44b5 | ||
|   | 4e7fbd8967 | ||
|   | a86c419f95 | ||
|   | e3ec0ad97e | ||
|   | 75791981ce | ||
|   | e813fe16b9 | ||
|   | 42ac7b954d | ||
|   | c1bbf5dab6 | ||
|   | e16dc2a910 | ||
|   | e236c05d79 | ||
|   | 454c1e3faf | ||
|   | 43daf814df | ||
|   | c40b630530 | ||
|   | 7fc0698ecf | ||
|   | 4f3c8b940e | ||
|   | 1855ab60f1 | ||
|   | af4f1a7bd6 | ||
|   | 8646a9c49c | ||
|   | 8d7c033cf5 | ||
|   | b8900e32de | ||
|   | d48c25d2c9 | ||
|   | a87c5899c5 | ||
|   | 147ad69864 | ||
|   | c146006476 | ||
|   | a0f10d7ca1 | ||
|   | 299b91edc4 | ||
|   | 95c89ca6db | ||
|   | 7fe0d71e7f | ||
|   | fbbb506e86 | ||
|   | ec80b06a45 | ||
|   | 41e1619f1f | ||
|   | ba6a9c6a93 | ||
|   | 18571c52fb | ||
|   | 5d5dfeaa83 | ||
|   | 3669d8c0f3 | ||
|   | 69d72819c6 | ||
|   | 54dcc10250 | ||
|   | 1edfce8f73 | ||
|   | 675e573a8c | ||
|   | 1080fa63a9 | ||
|   | 8047086988 | ||
|   | 449b9f7fa0 | ||
|   | b7a15bf6ca | 
| @@ -23,6 +23,10 @@ jobs: | ||||
|     executor: default | ||||
|     steps: | ||||
|       - checkout | ||||
|       - run: | ||||
|           name: Ensure package-lock.json | ||||
|           command: | | ||||
|             [ ! -e package-lock.json ] && echo '{}' > package-lock.json | ||||
|       - restore_cache: | ||||
|           name: Restore npm package caches | ||||
|           keys: | ||||
| @@ -35,11 +39,12 @@ jobs: | ||||
|           name: Install Dependencies | ||||
|           command: | | ||||
|             npm install | ||||
|             npm prune | ||||
|       - run: | ||||
|           name: Configure | ||||
|           command: | | ||||
|             cp .ci/default.yml .config | ||||
|             cp .ci/test.yml .config | ||||
|             cp .circleci/misskey/default.yml .config | ||||
|             cp .circleci/misskey/test.yml .config | ||||
|       - run: | ||||
|           name: Build | ||||
|           command: | | ||||
| @@ -50,8 +55,8 @@ jobs: | ||||
|           key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }} | ||||
|           paths: | ||||
|             - node_modules | ||||
|       - store_artifacts: | ||||
|           path: built | ||||
| #      - store_artifacts: | ||||
| #          path: built | ||||
|       - persist_to_workspace: | ||||
|           root: . | ||||
|           paths: | ||||
| @@ -98,7 +103,6 @@ jobs: | ||||
|           name: Build | ||||
|           command: | | ||||
|             docker build . | tee docker.log | ||||
|             tail -n 1 docker.log | read __Successfully __built tag | ||||
|       - when: | ||||
|           condition: <<parameters.with_deploy>> | ||||
|           steps: | ||||
| @@ -107,6 +111,7 @@ jobs: | ||||
|                 command: | | ||||
|                   if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] | ||||
|                    then | ||||
|                     tail -n 1 docker.log | read __Successfully __built tag | ||||
|                     docker tag $tag misskey/misskey | ||||
|                     docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD | ||||
|                     docker push misskey/misskey | ||||
| @@ -126,10 +131,13 @@ workflows: | ||||
|           without_redis: "true" | ||||
|           requires: | ||||
|             - build | ||||
|       - docker: | ||||
|           filters: | ||||
|             branches: | ||||
|               ignore: master | ||||
|               only: master | ||||
| #      - docker: | ||||
| #          filters: | ||||
| #            branches: | ||||
| #              ignore: master | ||||
|       - docker: | ||||
|           with_deploy: "true" | ||||
|           filters: | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| name: example-instance-name # Name of your instance | ||||
| description: example-description # Description of your instance | ||||
|  | ||||
| maintainer: | ||||
|   name: example-maitainer-name # Your name | ||||
|   url: http://example.com/ # Your contact (http or mailto) | ||||
| @@ -25,7 +22,7 @@ url: https://example.tld/ | ||||
| #   +------+      |+-------------+      +----------------+| | ||||
| #                 +---------------------------------------+ | ||||
| # | ||||
| #   You need to setup reverse proxy. (eg. Nginx) | ||||
| #   You need to setup reverse proxy. (eg. nginx) | ||||
| #   You do not define 'https' section. | ||||
|  | ||||
| # Option 2: Standalone | ||||
| @@ -60,21 +57,6 @@ mongodb: | ||||
|   user: example-misskey-user | ||||
|   pass: example-misskey-pass | ||||
|  | ||||
| # Drive capacity of a local user (MB) | ||||
| localDriveCapacityMb: 256 | ||||
|  | ||||
| # Drive capacity of a remote user (MB) | ||||
| remoteDriveCapacityMb: 8 | ||||
|  | ||||
| # If enabled: | ||||
| #  Server will not cache remote files (Using direct link instead). | ||||
| #  You can save your storage. | ||||
| # | ||||
| #  NOTE: | ||||
| #  * Users cannot see remote images when they turn off "Show media from a remote server" setting. | ||||
| #  * Since thumbnails are not provided, traffic increases. | ||||
| preventCacheRemoteFiles: false | ||||
|  | ||||
| drive: | ||||
|   storage: 'db' | ||||
|  | ||||
| @@ -113,6 +95,10 @@ drive: | ||||
|   #   accessKey: XXX | ||||
|   #   secretKey: YYY | ||||
|  | ||||
| # If enabled: | ||||
| #  The first account created is automatically marked as Admin. | ||||
| autoAdmin: true | ||||
|  | ||||
| # | ||||
| # Below settings are optional | ||||
| # | ||||
| @@ -148,6 +134,12 @@ drive: | ||||
| #  consumer_key: example-twitter-consumer-key | ||||
| #  consumer_secret: example-twitter-consumer-secret-key | ||||
|  | ||||
| # GitHub integration | ||||
| # You need to set the oauth callback url as : https://<your-misskey-instance>/api/gh/cb | ||||
| #github: | ||||
| #  client_id: example-github-client-id | ||||
| #  client_secret: example-github-client-secret | ||||
|  | ||||
| # Ghost | ||||
| # Ghost account is an account used for the purpose of delegating | ||||
| # followers when putting users in the list. | ||||
| @@ -164,6 +156,3 @@ drive: | ||||
| #  external: true | ||||
| #  engine: http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}} | ||||
| #  timeout: 300000 | ||||
|  | ||||
| # Max allowed note text length in charactors | ||||
| maxNoteTextLength: 1000 | ||||
|   | ||||
							
								
								
									
										41
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,41 +0,0 @@ | ||||
| # travis file | ||||
| # https://docs.travis-ci.com/user/customizing-the-build | ||||
|  | ||||
| notifications: | ||||
|   email: false | ||||
|  | ||||
| branches: | ||||
|   except: | ||||
|     - l10n_master | ||||
|  | ||||
| language: node_js | ||||
|  | ||||
| node_js: | ||||
|   - 11.0.0 | ||||
|  | ||||
| env: | ||||
|   - CXX=g++-4.8 NODE_ENV=production | ||||
|  | ||||
| addons: | ||||
|   apt: | ||||
|     sources: | ||||
|       - ubuntu-toolchain-r-test | ||||
|     packages: | ||||
|       - g++-4.8 | ||||
|  | ||||
| cache: | ||||
|   directories: | ||||
|     - node_modules | ||||
|  | ||||
| services: | ||||
|   - mongodb | ||||
|   - redis-server | ||||
|  | ||||
| before_script: | ||||
|   - npm install | ||||
|  | ||||
|   # 設定ファイルを配置 | ||||
|   - cp ./.ci/default.yml ./.config | ||||
|   - cp ./.ci/test.yml ./.config | ||||
|  | ||||
|   - travis_wait npm run build | ||||
| @@ -23,5 +23,5 @@ Please use [Crowdin](https://crowdin.com/project/misskey) for localization. | ||||
| * Test codes are located in `/test`. | ||||
|  | ||||
| ## Continuous integration | ||||
| Misskey uses Travis for automated test. | ||||
| Configuration files are located in `/.travis`. | ||||
| Misskey uses CircleCI for automated test. | ||||
| Configuration files are located in `/.circleci`. | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
| ================================================================ | ||||
|  | ||||
| [](https://circleci.com/gh/syuilo/misskey) | ||||
| [![][travis-badge]][travis-link] | ||||
| [![][dependencies-badge]][dependencies-link] | ||||
| [](http://makeapullrequest.com) | ||||
|  | ||||
| @@ -44,7 +43,7 @@ Easiest way to tell your emotions. Misskey allows you to add various type of rea | ||||
|  | ||||
| <h3 align="left">Interface</h3> | ||||
| <p align="left"> | ||||
| No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home. | ||||
| Highly customizable UI for your taste. We understand no UI fits for everyone. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home. | ||||
| </p> | ||||
|  | ||||
| --- | ||||
| @@ -124,8 +123,6 @@ Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE). | ||||
|  | ||||
| [agpl-3.0]:           https://www.gnu.org/licenses/agpl-3.0.en.html | ||||
| [agpl-3.0-badge]:     https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=flat-square | ||||
| [travis-link]:        https://travis-ci.org/syuilo/misskey | ||||
| [travis-badge]:       http://img.shields.io/travis/syuilo/misskey/master.svg?style=flat-square | ||||
| [dependencies-link]:  https://david-dm.org/syuilo/misskey | ||||
| [dependencies-badge]: https://img.shields.io/david/syuilo/misskey.svg?style=flat-square | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,7 @@ adduser --disabled-password --disabled-login misskey | ||||
| Please install and setup these softwares: | ||||
|  | ||||
| #### Dependencies :package: | ||||
| * **[Node.js](https://nodejs.org/en/)** | ||||
| * **[Node.js](https://nodejs.org/en/)** >= 10.0.0 | ||||
| * **[MongoDB](https://www.mongodb.com/)** >= 3.6 | ||||
|  | ||||
| ##### Optional | ||||
|   | ||||
| @@ -22,7 +22,7 @@ adduser --disabled-password --disabled-login misskey | ||||
| これらのソフトウェアをインストール・設定してください: | ||||
|  | ||||
| #### 依存関係 :package: | ||||
| * **[Node.js](https://nodejs.org/en/)** | ||||
| * **[Node.js](https://nodejs.org/en/)** (10.0.0以上) | ||||
| * **[MongoDB](https://www.mongodb.com/)** (3.6以上) | ||||
|  | ||||
| ##### オプション | ||||
|   | ||||
| @@ -21,7 +21,6 @@ import * as htmlmin from 'gulp-htmlmin'; | ||||
| const uglifyes = require('uglify-es'); | ||||
|  | ||||
| const locales = require('./locales'); | ||||
| import { fa } from './src/misc/fa'; | ||||
|  | ||||
| const uglify = uglifyComposer(uglifyes, console); | ||||
|  | ||||
| @@ -164,8 +163,7 @@ gulp.task('build:client:pug', [ | ||||
| 		gulp.src('./src/client/app/base.pug') | ||||
| 			.pipe(pug({ | ||||
| 				locals: { | ||||
| 					themeColor: constants.themeColor, | ||||
| 					facss: fa.dom.css() | ||||
| 					themeColor: constants.themeColor | ||||
| 				} | ||||
| 			})) | ||||
| 			.pipe(htmlmin({ | ||||
|   | ||||
| @@ -131,6 +131,7 @@ common: | ||||
|   show-full-acct: "ユーザー名のホストを省略しない" | ||||
|   reduce-motion: "UIの動きを減らす" | ||||
|   this-setting-is-this-device-only: "このデバイスのみ" | ||||
|   use-os-default-emojis: "OS標準の絵文字を使用" | ||||
|  | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|  | ||||
| @@ -417,6 +418,7 @@ common/views/components/signin.vue: | ||||
|   signin: "サインイン" | ||||
|   or: "または" | ||||
|   signin-with-twitter: "Twitterでログイン" | ||||
|   signin-with-github: "GitHubでログイン" | ||||
|   login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" | ||||
|  | ||||
| common/views/components/signup.vue: | ||||
| @@ -460,6 +462,14 @@ common/views/components/twitter-setting.vue: | ||||
|   connect: "Twitterと接続する" | ||||
|   disconnect: "切断する" | ||||
|  | ||||
| common/views/components/github-setting.vue: | ||||
|   description: "お使いのGitHubアカウントをお使いのMisskeyアカウントに接続しておくと、プロフィールでGitHubアカウント情報が表示されるようになったり、GitHubを用いた便利なサインインを利用できるようになります。" | ||||
|   connected-to: "次のGitHubアカウントに接続されています" | ||||
|   detail: "詳細..." | ||||
|   reconnect: "再接続する" | ||||
|   connect: "GitHubと接続する" | ||||
|   disconnect: "切断する" | ||||
|  | ||||
| common/views/components/uploader.vue: | ||||
|   waiting: "待機中" | ||||
|  | ||||
| @@ -599,32 +609,6 @@ desktop/views/components/calendar.vue: | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
|  | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   federation: "フェデレーション" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   network: "ネットワーク" | ||||
|   charts: | ||||
|     federation-instances: "インスタンスの増減" | ||||
|     federation-instances-total: "インスタンスの積算" | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の積算" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの積算" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の積算" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の積算" | ||||
|     network-requests: "リクエスト" | ||||
|     network-time: "応答時間" | ||||
|     network-usage: "通信量" | ||||
|  | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -963,6 +947,7 @@ common/views/components/api-settings.vue: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     credential-info: "「i」パラメータは自動で付与されます。" | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| @@ -1088,10 +1073,24 @@ admin/views/dashboard.vue: | ||||
|   instances: "インスタンス" | ||||
|   this-instance: "このインスタンス" | ||||
|   federated: "連合" | ||||
|  | ||||
| admin/views/instance.vue: | ||||
|   instance: "インスタンス" | ||||
|   instance-name: "インスタンス名" | ||||
|   instance-description: "インスタンスの紹介" | ||||
|   banner-url: "バナー画像URL" | ||||
|   drive-config: "ドライブの設定" | ||||
|   cache-remote-files: "リモートのファイルをキャッシュする" | ||||
|   cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。" | ||||
|   local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量" | ||||
|   remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量" | ||||
|   mb: "メガバイト単位" | ||||
|   max-note-text-length: "投稿の最大文字数" | ||||
|   disable-registration: "ユーザー登録の受付を停止する" | ||||
|   disable-local-timeline: "ローカルタイムラインを無効にする" | ||||
|   invite: "招待" | ||||
|   banner-url: "Banner URL" | ||||
|   disableRegistration: "Disable new user registration" | ||||
|   disableLocalTimeline: "Disable the local timeline" | ||||
|   save: "保存" | ||||
|   saved: "保存しました" | ||||
|  | ||||
| admin/views/charts.vue: | ||||
|   title: "チャート" | ||||
| @@ -1142,10 +1141,16 @@ admin/views/emoji.vue: | ||||
|     aliases-desc: "スペースで区切って複数設定できます。" | ||||
|     url: "絵文字画像URL" | ||||
|     add: "追加" | ||||
|     info: "50KB以下のPNG画像をおすすめします。" | ||||
|     added: "絵文字を登録しました" | ||||
|   emojis: | ||||
|     title: "絵文字一覧" | ||||
|     update: "更新" | ||||
|     remove: "削除" | ||||
|   updated: "更新しました" | ||||
|   remove-emoji: | ||||
|     are-you-sure: "「$1」を削除しますか?" | ||||
|     removed: "削除しました" | ||||
|  | ||||
| admin/views/announcements.vue: | ||||
|   announcements: "お知らせ" | ||||
| @@ -1154,6 +1159,10 @@ admin/views/announcements.vue: | ||||
|   add: "追加" | ||||
|   title: "タイトル" | ||||
|   text: "内容" | ||||
|   saved: "保存しました" | ||||
|   _remove: | ||||
|     are-you-sure: "「$1」を削除しますか?" | ||||
|     removed: "削除しました" | ||||
|  | ||||
| admin/views/hashtags.vue: | ||||
|   hided-tags: "Hidden Tags" | ||||
| @@ -1173,12 +1182,6 @@ desktop/views/pages/deck/deck.user-column.vue: | ||||
|   pinned-notes: "ピン留めされた投稿" | ||||
|   push-to-a-list: "リストに追加" | ||||
|  | ||||
| desktop/views/pages/stats/stats.vue: | ||||
|   all-users: "全てのユーザー" | ||||
|   original-users: "このインスタンスのユーザー" | ||||
|   all-notes: "全ての投稿" | ||||
|   original-notes: "このインスタンスの投稿" | ||||
|  | ||||
| desktop/views/pages/welcome.vue: | ||||
|   about: "詳しく..." | ||||
|   gotit: "わかった" | ||||
| @@ -1560,6 +1563,10 @@ mobile/views/pages/settings.vue: | ||||
|   twitter-connect: "Twitterアカウントに接続する" | ||||
|   twitter-reconnect: "再接続する" | ||||
|   twitter-disconnect: "切断する" | ||||
|   github: "GitHub連携" | ||||
|   github-connect: "GitHubアカウントに接続する" | ||||
|   github-reconnect: "再接続する" | ||||
|   github-disconnect: "切断する" | ||||
|   update: "Misskey Update" | ||||
|   version: "バージョン:" | ||||
|   latest-version: "最新のバージョン:" | ||||
|   | ||||
| @@ -186,7 +186,7 @@ common: | ||||
|     stack-left: "左に重ねんで!" | ||||
|     pop-right: "右に出すで!" | ||||
|   dev: "アプリの作成あかんかったわ。もっぺんやってみて。" | ||||
|   ai-chan-kawaii: "藍ちゃかわいい" | ||||
|   ai-chan-kawaii: "藍ちゃめっさべっぴんさんや" | ||||
| auth/views/form.vue: | ||||
|   share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?" | ||||
|   permission-ask: "このアプリは次の権限を要求してんで:" | ||||
| @@ -744,7 +744,7 @@ desktop/views/components/settings.vue: | ||||
|   apps: "アプリ" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "守護神セキュリティ" | ||||
|   security: "セキュリティ" | ||||
|   signin: "こんな感じでサインインしたらしいで" | ||||
|   password: "パスワード" | ||||
|   2fa: "二段階認証" | ||||
| @@ -873,15 +873,15 @@ common/views/components/mute-and-block.vue: | ||||
|   mute-and-block: "ミュートとブロック" | ||||
|   mute: "ミュート" | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   no-muted-users: "ミュートしとるユーザーはおらんで" | ||||
|   no-blocked-users: "ブロックしとるユーザーはおらんで" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
|   enter-new-password: "新しいパスワードを入力してください" | ||||
|   enter-new-password-again: "もう一度新しいパスワードを入力してください" | ||||
|   not-match: "新しいパスワードが一致しません" | ||||
|   changed: "パスワードを変更しました" | ||||
|   reset: "パスワード変える" | ||||
|   enter-current-password: "今のパスワードを入れてや" | ||||
|   enter-new-password: "こんどのパスワード入れてや" | ||||
|   enter-new-password-again: "もっぺん入れてや" | ||||
|   not-match: "パスワードがおうとらん" | ||||
|   changed: "パスワード変えたわ" | ||||
| desktop/views/components/sub-note-content.vue: | ||||
|   private: "この投稿は見せられへんわ" | ||||
|   deleted: "この投稿なんか無くなってもうたわ" | ||||
| @@ -953,7 +953,7 @@ admin/views/index.vue: | ||||
|   emoji: "カスタム絵文字" | ||||
|   users: "ユーザー" | ||||
|   update: "更新" | ||||
|   announcements: "お知らせ" | ||||
|   announcements: "知っといてや" | ||||
|   hashtags: "ハッシュタグ" | ||||
|   back-to-misskey: "Misskeyに戻る" | ||||
| admin/views/dashboard.vue: | ||||
| @@ -962,9 +962,9 @@ admin/views/dashboard.vue: | ||||
|   notes: "投稿" | ||||
|   drive: "ドライブ" | ||||
|   instances: "インスタンス" | ||||
|   this-instance: "このインスタンス" | ||||
|   this-instance: "ワイのインスタンス" | ||||
|   federated: "連合" | ||||
|   invite: "招待" | ||||
|   invite: "来てや" | ||||
|   banner-url: "Banner URL" | ||||
|   disableRegistration: "Disable new user registration" | ||||
|   disableLocalTimeline: "Disable the local timeline" | ||||
| @@ -980,7 +980,7 @@ admin/views/charts.vue: | ||||
|   charts: | ||||
|     federation-instances: "インスタンスの増減" | ||||
|     federation-instances-total: "インスタンスの積算" | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     notes: "投稿の増減(統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の積算" | ||||
| @@ -1387,7 +1387,7 @@ mobile/views/pages/user.vue: | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
|   unblock: "ブロックやめたる" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近儲かりまっか?" | ||||
|   images: "画像" | ||||
|   | ||||
							
								
								
									
										17362
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17362
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "10.38.1", | ||||
| 	"clientVersion": "1.0.11482", | ||||
| 	"version": "10.42.2", | ||||
| 	"clientVersion": "1.0.11604", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -20,10 +20,11 @@ | ||||
| 		"format": "gulp format" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@fortawesome/fontawesome-svg-core": "1.2.6", | ||||
| 		"@fortawesome/free-brands-svg-icons": "5.4.1", | ||||
| 		"@fortawesome/free-regular-svg-icons": "5.4.1", | ||||
| 		"@fortawesome/free-solid-svg-icons": "5.4.1", | ||||
| 		"@fortawesome/fontawesome-svg-core": "1.2.8", | ||||
| 		"@fortawesome/free-brands-svg-icons": "5.5.0", | ||||
| 		"@fortawesome/free-regular-svg-icons": "5.5.0", | ||||
| 		"@fortawesome/free-solid-svg-icons": "5.5.0", | ||||
| 		"@fortawesome/vue-fontawesome": "0.1.2", | ||||
| 		"@koa/cors": "2.2.2", | ||||
| 		"@prezzemolo/rap": "0.1.2", | ||||
| 		"@prezzemolo/zip": "0.0.3", | ||||
| @@ -62,6 +63,7 @@ | ||||
| 		"@types/mongodb": "3.1.12", | ||||
| 		"@types/ms": "0.7.30", | ||||
| 		"@types/node": "10.12.2", | ||||
| 		"@types/oauth": "0.9.1", | ||||
| 		"@types/portscanner": "2.1.0", | ||||
| 		"@types/pug": "2.0.4", | ||||
| 		"@types/qrcode": "1.3.0", | ||||
| @@ -95,7 +97,6 @@ | ||||
| 		"chai": "4.2.0", | ||||
| 		"chai-http": "4.2.0", | ||||
| 		"chalk": "2.4.1", | ||||
| 		"chart.js": "2.7.3", | ||||
| 		"commander": "2.19.0", | ||||
| 		"crc-32": "1.2.0", | ||||
| 		"css-loader": "1.0.1", | ||||
| @@ -112,7 +113,7 @@ | ||||
| 		"eslint-plugin-vue": "4.7.1", | ||||
| 		"eventemitter3": "3.1.0", | ||||
| 		"file-loader": "2.0.0", | ||||
| 		"file-type": "10.3.0", | ||||
| 		"file-type": "10.4.0", | ||||
| 		"fuckadblock": "3.2.1", | ||||
| 		"gulp": "3.9.1", | ||||
| 		"gulp-cssnano": "2.1.3", | ||||
| @@ -211,12 +212,10 @@ | ||||
| 		"uuid": "3.3.2", | ||||
| 		"v-animate-css": "0.0.2", | ||||
| 		"vue": "2.5.17", | ||||
| 		"vue-chartjs": "3.4.0", | ||||
| 		"vue-color": "2.7.0", | ||||
| 		"vue-content-loading": "1.5.3", | ||||
| 		"vue-cropperjs": "2.2.2", | ||||
| 		"vue-js-modal": "1.3.26", | ||||
| 		"vue-json-tree-view": "2.1.4", | ||||
| 		"vue-loader": "15.4.2", | ||||
| 		"vue-router": "3.0.1", | ||||
| 		"vue-style-loader": "4.1.2", | ||||
| @@ -229,7 +228,7 @@ | ||||
| 		"vuex-persistedstate": "2.5.4", | ||||
| 		"web-push": "3.3.3", | ||||
| 		"webfinger.js": "2.6.6", | ||||
| 		"webpack": "4.23.1", | ||||
| 		"webpack": "4.25.1", | ||||
| 		"webpack-cli": "3.1.2", | ||||
| 		"websocket": "1.0.28", | ||||
| 		"ws": "6.1.0", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div> | ||||
| <div class="cdeuzmsthagexbkpofbmatmugjuvogfb"> | ||||
| 	<ui-card> | ||||
| 		<div slot="title">%fa:broadcast-tower% %i18n:@announcements%</div> | ||||
| 		<div slot="title"><fa icon="broadcast-tower"/> %i18n:@announcements%</div> | ||||
| 		<section v-for="(announcement, i) in announcements" class="fit-top"> | ||||
| 			<ui-input v-model="announcement.title" @change="save"> | ||||
| 				<span>%i18n:@title%</span> | ||||
| @@ -9,13 +9,13 @@ | ||||
| 			<ui-textarea v-model="announcement.text"> | ||||
| 				<span>%i18n:@text%</span> | ||||
| 			</ui-textarea> | ||||
| 			<ui-button-group> | ||||
| 				<ui-button inline @click="save">%fa:save R% %i18n:@save%</ui-button> | ||||
| 				<ui-button inline @click="remove(i)">%fa:trash-alt R% %i18n:@remove%</ui-button> | ||||
| 			</ui-button-group> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-button @click="save()"><fa :icon="['far', 'save']"/> %i18n:@save%</ui-button> | ||||
| 				<ui-button @click="remove(i)"><fa :icon="['far', 'trash-alt']"/> %i18n:@remove%</ui-button> | ||||
| 			</ui-horizon-group> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="add">%fa:plus% %i18n:@add%</ui-button> | ||||
| 			<ui-button @click="add"><fa icon="plus"/> %i18n:@add%</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| </div> | ||||
| @@ -46,19 +46,45 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		remove(i) { | ||||
| 			this.announcements = this.announcements.filter((_, j) => j !== i); | ||||
| 			this.save(); | ||||
| 			this.$swal({ | ||||
| 				type: 'warning', | ||||
| 				text: '%i18n:@_remove.are-you-sure%'.replace('$1', this.announcements.find((_, j) => j == i).title), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res.value) return; | ||||
| 				this.announcements = this.announcements.filter((_, j) => j !== i); | ||||
| 				this.save(true); | ||||
| 				this.$swal({ | ||||
| 					type: 'success', | ||||
| 					text: '%i18n:@_remove.removed%' | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		save() { | ||||
| 		save(silent) { | ||||
| 			(this as any).api('admin/update-meta', { | ||||
| 				broadcasts: this.announcements | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Saved` }); | ||||
| 				if (!silent) { | ||||
| 					this.$swal({ | ||||
| 						type: 'success', | ||||
| 						text: '%i18n:@saved%' | ||||
| 					}); | ||||
| 				} | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .cdeuzmsthagexbkpofbmatmugjuvogfb | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -3,10 +3,10 @@ | ||||
| 	<table> | ||||
| 		<thead> | ||||
| 			<tr> | ||||
| 				<th>%fa:exchange-alt% In/Out</th> | ||||
| 				<th>%fa:server% Host</th> | ||||
| 				<th>%fa:bolt% Activity</th> | ||||
| 				<th>%fa:user% Actor</th> | ||||
| 				<th><fa icon="exchange-alt"/> In/Out</th> | ||||
| 				<th><fa icon="server"/> Host</th> | ||||
| 				<th><fa icon="bolt"/> Activity</th> | ||||
| 				<th><fa icon="user"/> Actor</th> | ||||
| 			</tr> | ||||
| 		</thead> | ||||
| 		<tbody> | ||||
| @@ -63,11 +63,11 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| .hyhctythnmwihguaaapnbrbszsjqxpio | ||||
| 	display block | ||||
| 	padding 16px | ||||
| 	padding 12px 16px 16px 16px | ||||
| 	height 250px | ||||
| 	overflow auto | ||||
| 	overflow hidden | ||||
| 	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) | ||||
| 	background var(--face) | ||||
| 	background var(--adminDashboardCardBg) | ||||
| 	border-radius 8px | ||||
|  | ||||
| 	> table | ||||
| @@ -76,10 +76,11 @@ export default Vue.extend({ | ||||
| 		overflow auto | ||||
| 		border-spacing 0 | ||||
| 		border-collapse collapse | ||||
| 		color #555 | ||||
| 		color var(--adminDashboardCardFg) | ||||
| 		font-size 14px | ||||
|  | ||||
| 		thead | ||||
| 			border-bottom solid 2px #eee | ||||
| 			border-bottom solid 1px var(--adminDashboardCardDivider) | ||||
|  | ||||
| 			tr | ||||
| 				th | ||||
| @@ -89,7 +90,7 @@ export default Vue.extend({ | ||||
| 		tbody | ||||
| 			tr | ||||
| 				&:nth-child(odd) | ||||
| 					background #fbfbfb | ||||
| 					background rgba(0, 0, 0, 0.025) | ||||
|  | ||||
| 		th, td | ||||
| 			padding 8px 16px | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="qvgidhudpqhjttdhxubzuyrhyzgslujw"> | ||||
| 	<header> | ||||
| 		<b>%fa:chart-bar R% %i18n:@title%:</b> | ||||
| 		<b><fa :icon="['far', 'chart-bar']"/> %i18n:@title%:</b> | ||||
| 		<select v-model="src"> | ||||
| 			<optgroup label="%i18n:@federation%"> | ||||
| 				<option value="federation-instances">%i18n:@charts.federation-instances%</option> | ||||
| @@ -39,6 +39,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as tinycolor from 'tinycolor2'; | ||||
| import * as ApexCharts from 'apexcharts'; | ||||
|  | ||||
| const limit = 90; | ||||
| @@ -147,7 +148,7 @@ export default Vue.extend({ | ||||
| 				this.chartInstance.destroy(); | ||||
| 			} | ||||
|  | ||||
| 			this.chartInstance = new ApexCharts(this.$refs.chart, Object.assign({ | ||||
| 			this.chartInstance = new ApexCharts(this.$refs.chart, { | ||||
| 				chart: { | ||||
| 					type: 'area', | ||||
| 					height: 300, | ||||
| @@ -168,17 +169,41 @@ export default Vue.extend({ | ||||
| 				}, | ||||
| 				grid: { | ||||
| 					clipMarkers: false, | ||||
| 					borderColor: 'rgba(0, 0, 0, 0.1)' | ||||
| 				}, | ||||
| 				stroke: { | ||||
| 					curve: 'straight', | ||||
| 					width: 2 | ||||
| 				}, | ||||
| 				legend: { | ||||
| 					labels: { | ||||
| 						color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() | ||||
| 					}, | ||||
| 				}, | ||||
| 				xaxis: { | ||||
| 					type: 'datetime' | ||||
| 					type: 'datetime', | ||||
| 					labels: { | ||||
| 						style: { | ||||
| 							colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() | ||||
| 						} | ||||
| 					}, | ||||
| 					axisBorder: { | ||||
| 						color: 'rgba(0, 0, 0, 0.1)' | ||||
| 					}, | ||||
| 					axisTicks: { | ||||
| 						color: 'rgba(0, 0, 0, 0.1)' | ||||
| 					}, | ||||
| 				}, | ||||
| 				yaxis: { | ||||
| 				} | ||||
| 			}, this.data)); | ||||
| 					labels: { | ||||
| 						formatter: this.data.bytes ? v => Vue.filter('bytes')(v, 0) : v => Vue.filter('number')(v), | ||||
| 						style: { | ||||
| 							color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				series: this.data.series | ||||
| 			}); | ||||
|  | ||||
| 			this.chartInstance.render(); | ||||
| 		}, | ||||
| @@ -249,12 +274,15 @@ export default Vue.extend({ | ||||
| 			return { | ||||
| 				series: [{ | ||||
| 					name: 'Combined', | ||||
| 					type: 'line', | ||||
| 					data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total)) | ||||
| 				}, { | ||||
| 					name: 'Local', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.notes.local.total) | ||||
| 				}, { | ||||
| 					name: 'Remote', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.notes.remote.total) | ||||
| 				}] | ||||
| 			}; | ||||
| @@ -264,18 +292,21 @@ export default Vue.extend({ | ||||
| 			return { | ||||
| 				series: [{ | ||||
| 					name: 'Combined', | ||||
| 					type: 'line', | ||||
| 					data: this.format(total | ||||
| 						? sum(this.stats.users.local.total, this.stats.users.remote.total) | ||||
| 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||
| 					) | ||||
| 				}, { | ||||
| 					name: 'Local', | ||||
| 					type: 'area', | ||||
| 					data: this.format(total | ||||
| 						? this.stats.users.local.total | ||||
| 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec)) | ||||
| 					) | ||||
| 				}, { | ||||
| 					name: 'Remote', | ||||
| 					type: 'area', | ||||
| 					data: this.format(total | ||||
| 						? this.stats.users.remote.total | ||||
| 						: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||
| @@ -286,8 +317,10 @@ export default Vue.extend({ | ||||
|  | ||||
| 		driveChart(): any { | ||||
| 			return { | ||||
| 				bytes: true, | ||||
| 				series: [{ | ||||
| 					name: 'All', | ||||
| 					type: 'line', | ||||
| 					data: this.format( | ||||
| 						sum( | ||||
| 							this.stats.drive.local.incSize, | ||||
| @@ -298,15 +331,19 @@ export default Vue.extend({ | ||||
| 					) | ||||
| 				}, { | ||||
| 					name: 'Local +', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.local.incSize) | ||||
| 				}, { | ||||
| 					name: 'Local -', | ||||
| 					type: 'area', | ||||
| 					data: this.format(negate(this.stats.drive.local.decSize)) | ||||
| 				}, { | ||||
| 					name: 'Remote +', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.remote.incSize) | ||||
| 				}, { | ||||
| 					name: 'Remote -', | ||||
| 					type: 'area', | ||||
| 					data: this.format(negate(this.stats.drive.remote.decSize)) | ||||
| 				}] | ||||
| 			}; | ||||
| @@ -314,14 +351,18 @@ export default Vue.extend({ | ||||
|  | ||||
| 		driveTotalChart(): any { | ||||
| 			return { | ||||
| 				bytes: true, | ||||
| 				series: [{ | ||||
| 					name: 'Combined', | ||||
| 					type: 'line', | ||||
| 					data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize)) | ||||
| 				}, { | ||||
| 					name: 'Local', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.local.totalSize) | ||||
| 				}, { | ||||
| 					name: 'Remote', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.remote.totalSize) | ||||
| 				}] | ||||
| 			}; | ||||
| @@ -331,6 +372,7 @@ export default Vue.extend({ | ||||
| 			return { | ||||
| 				series: [{ | ||||
| 					name: 'All', | ||||
| 					type: 'line', | ||||
| 					data: this.format( | ||||
| 						sum( | ||||
| 							this.stats.drive.local.incCount, | ||||
| @@ -341,15 +383,19 @@ export default Vue.extend({ | ||||
| 					) | ||||
| 				}, { | ||||
| 					name: 'Local +', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.local.incCount) | ||||
| 				}, { | ||||
| 					name: 'Local -', | ||||
| 					type: 'area', | ||||
| 					data: this.format(negate(this.stats.drive.local.decCount)) | ||||
| 				}, { | ||||
| 					name: 'Remote +', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.remote.incCount) | ||||
| 				}, { | ||||
| 					name: 'Remote -', | ||||
| 					type: 'area', | ||||
| 					data: this.format(negate(this.stats.drive.remote.decCount)) | ||||
| 				}] | ||||
| 			}; | ||||
| @@ -359,12 +405,15 @@ export default Vue.extend({ | ||||
| 			return { | ||||
| 				series: [{ | ||||
| 					name: 'Combined', | ||||
| 					type: 'line', | ||||
| 					data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount)) | ||||
| 				}, { | ||||
| 					name: 'Local', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.local.totalCount) | ||||
| 				}, { | ||||
| 					name: 'Remote', | ||||
| 					type: 'area', | ||||
| 					data: this.format(this.stats.drive.remote.totalCount) | ||||
| 				}] | ||||
| 			}; | ||||
| @@ -396,6 +445,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 		networkUsageChart(): any { | ||||
| 			return { | ||||
| 				bytes: true, | ||||
| 				series: [{ | ||||
| 					name: 'Incoming', | ||||
| 					data: this.format(this.stats.network.incomingBytes) | ||||
| @@ -424,8 +474,8 @@ export default Vue.extend({ | ||||
| 		margin 0 8px | ||||
| 		padding 0 0 8px 0 | ||||
| 		font-size 1em | ||||
| 		color #555 | ||||
| 		border-bottom solid 1px #eee | ||||
| 		color var(--adminDashboardCardFg) | ||||
| 		border-bottom solid 1px var(--adminDashboardCardDivider) | ||||
|  | ||||
| 		> b | ||||
| 			margin-right 8px | ||||
|   | ||||
| @@ -2,14 +2,14 @@ | ||||
| <div class="zyknedwtlthezamcjlolyusmipqmjgxz"> | ||||
| 	<div> | ||||
| 		<header> | ||||
| 			<span>%fa:microchip% CPU <span>{{ cpuP }}%</span></span> | ||||
| 			<span><fa icon="microchip"/> CPU <span>{{ cpuP }}%</span></span> | ||||
| 			<span v-if="meta">{{ meta.cpu.model }}</span> | ||||
| 		</header> | ||||
| 		<div ref="cpu"></div> | ||||
| 	</div> | ||||
| 	<div> | ||||
| 		<header> | ||||
| 			<span>%fa:memory% MEM <span>{{ memP }}%</span></span> | ||||
| 			<span><fa icon="memory"/> MEM <span>{{ memP }}%</span></span> | ||||
| 			<span v-if="meta"></span> | ||||
| 		</header> | ||||
| 		<div ref="mem"></div> | ||||
| @@ -79,6 +79,7 @@ export default Vue.extend({ | ||||
| 			}, | ||||
| 			grid: { | ||||
| 				clipMarkers: false, | ||||
| 				borderColor: 'rgba(0, 0, 0, 0.1)' | ||||
| 			}, | ||||
| 			stroke: { | ||||
| 				curve: 'straight', | ||||
| @@ -153,7 +154,7 @@ export default Vue.extend({ | ||||
| 			display flex | ||||
| 			padding 0 8px | ||||
| 			margin-bottom -16px | ||||
| 			color #555 | ||||
| 			color var(--adminDashboardCardFg) | ||||
| 			font-size 14px | ||||
|  | ||||
| 			> span | ||||
| @@ -167,4 +168,13 @@ export default Vue.extend({ | ||||
| 		> div | ||||
| 			margin-bottom -10px | ||||
|  | ||||
| 	@media (max-width 1000px) | ||||
| 		display block | ||||
| 		margin-bottom 26px | ||||
|  | ||||
| 		> div | ||||
| 			&:first-child | ||||
| 				margin-right 0 | ||||
| 				margin-bottom 26px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -11,54 +11,54 @@ | ||||
| 	<div v-if="stats" class="stats"> | ||||
| 		<div> | ||||
| 			<div> | ||||
| 				<div>%fa:user%</div> | ||||
| 				<div><fa icon="user"/></div> | ||||
| 				<div> | ||||
| 					<span>%i18n:@accounts%</span> | ||||
| 					<b class="primary">{{ stats.originalUsersCount | number }}</b> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<span>%fa:home% %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('users')">%fa:chart-bar R%</span> | ||||
| 				<span><fa icon="home"/> %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('users')"><fa :icon="['far', 'chart-bar']"/></span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<div> | ||||
| 				<div>%fa:pencil-alt%</div> | ||||
| 				<div><fa icon="pencil-alt"/></div> | ||||
| 				<div> | ||||
| 					<span>%i18n:@notes%</span> | ||||
| 					<b class="primary">{{ stats.originalNotesCount | number }}</b> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<span>%fa:home% %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('notes')">%fa:chart-bar R%</span> | ||||
| 				<span><fa icon="home"/> %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('notes')"><fa :icon="['far', 'chart-bar']"/></span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<div> | ||||
| 				<div>%fa:database%</div> | ||||
| 				<div><fa icon="database"/></div> | ||||
| 				<div> | ||||
| 					<span>%i18n:@drive%</span> | ||||
| 					<b>{{ stats.driveUsageLocal | bytes }}</b> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<span>%fa:home% %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('drive')">%fa:chart-bar R%</span> | ||||
| 				<span><fa icon="home"/> %i18n:@this-instance%</span> | ||||
| 				<span @click="setChartSrc('drive')"><fa :icon="['far', 'chart-bar']"/></span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<div> | ||||
| 				<div>%fa:hdd R%</div> | ||||
| 				<div><fa :icon="['far', 'hdd']"/></div> | ||||
| 				<div> | ||||
| 					<span>%i18n:@instances%</span> | ||||
| 					<b>{{ stats.instances | number }}</b> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<span>%fa:globe% %i18n:@federated%</span> | ||||
| 				<span @click="setChartSrc('federation-instances-total')">%fa:chart-bar R%</span> | ||||
| 				<span><fa icon="globe"/> %i18n:@federated%</span> | ||||
| 				<span @click="setChartSrc('federation-instances-total')"><fa :icon="['far', 'chart-bar']"/></span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| @@ -124,17 +124,28 @@ export default Vue.extend({ | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .obdskegsannmntldydackcpzezagxqfy | ||||
| 	padding 16px | ||||
|  | ||||
| 	@media (min-width 500px) | ||||
| 		padding 32px | ||||
|  | ||||
| 	> header | ||||
| 		display flex | ||||
| 		margin-bottom 16px | ||||
| 		padding-bottom 16px | ||||
| 		border-bottom solid 1px #ccc | ||||
| 		color #777 | ||||
| 		border-bottom solid 1px var(--adminDashboardHeaderBorder) | ||||
| 		color var(--adminDashboardHeaderFg) | ||||
| 		font-size 14px | ||||
| 		white-space nowrap | ||||
|  | ||||
| 		@media (max-width 1000px) | ||||
| 			display none | ||||
|  | ||||
| 		> p | ||||
| 			display inline | ||||
| 			display block | ||||
| 			margin 0 32px 0 0 | ||||
| 			overflow hidden | ||||
| 			text-overflow ellipsis | ||||
|  | ||||
| 			> b | ||||
| 				&:after | ||||
| @@ -152,11 +163,10 @@ export default Vue.extend({ | ||||
|  | ||||
| 		> div | ||||
| 			flex 1 | ||||
| 			max-width 300px | ||||
| 			margin-right 16px | ||||
| 			color var(--text) | ||||
| 			color var(--adminDashboardCardFg) | ||||
| 			box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) | ||||
| 			background var(--face) | ||||
| 			background var(--adminDashboardCardBg) | ||||
| 			border-radius 8px | ||||
|  | ||||
| 			&:last-child | ||||
| @@ -192,7 +202,7 @@ export default Vue.extend({ | ||||
| 			> div:last-child | ||||
| 				display flex | ||||
| 				padding 6px 16px | ||||
| 				border-top solid 1px #eee | ||||
| 				border-top solid 1px var(--adminDashboardCardDivider) | ||||
|  | ||||
| 				> span | ||||
| 					font-size 70% | ||||
| @@ -202,6 +212,21 @@ export default Vue.extend({ | ||||
| 						margin-left auto | ||||
| 						cursor pointer | ||||
|  | ||||
| 		@media (max-width 900px) | ||||
| 			display grid | ||||
| 			grid-template-columns 1fr 1fr | ||||
| 			grid-template-rows 1fr 1fr | ||||
| 			gap 16px | ||||
|  | ||||
| 			> div | ||||
| 				margin-right 0 | ||||
|  | ||||
| 		@media (max-width 500px) | ||||
| 			display block | ||||
|  | ||||
| 			> div:not(:last-child) | ||||
| 				margin-bottom 16px | ||||
|  | ||||
| 	> .charts | ||||
| 		margin-bottom 16px | ||||
|  | ||||
|   | ||||
| @@ -1,42 +1,47 @@ | ||||
| <template> | ||||
| <div> | ||||
| <div class="tumhkfkmgtvzljezfvmgkeurkfncshbe"> | ||||
| 	<ui-card> | ||||
| 		<div slot="title">%fa:plus% %i18n:@add-emoji.title%</div> | ||||
| 		<div slot="title"><fa icon="plus"/> %i18n:@add-emoji.title%</div> | ||||
| 		<section class="fit-top"> | ||||
| 			<ui-input v-model="name"> | ||||
| 				<span>%i18n:@add-emoji.name%</span> | ||||
| 				<span slot="text">%i18n:@add-emoji.name-desc%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-input v-model="aliases"> | ||||
| 				<span>%i18n:@add-emoji.aliases%</span> | ||||
| 				<span slot="text">%i18n:@add-emoji.aliases-desc%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-horizon-group inputs> | ||||
| 				<ui-input v-model="name"> | ||||
| 					<span>%i18n:@add-emoji.name%</span> | ||||
| 					<span slot="desc">%i18n:@add-emoji.name-desc%</span> | ||||
| 				</ui-input> | ||||
| 				<ui-input v-model="aliases"> | ||||
| 					<span>%i18n:@add-emoji.aliases%</span> | ||||
| 					<span slot="desc">%i18n:@add-emoji.aliases-desc%</span> | ||||
| 				</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-input v-model="url"> | ||||
| 				<i slot="icon"><fa icon="link"/></i> | ||||
| 				<span>%i18n:@add-emoji.url%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-info>%i18n:@add-emoji.info%</ui-info> | ||||
| 			<ui-button @click="add">%i18n:@add-emoji.add%</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
|  | ||||
| 	<ui-card> | ||||
| 		<div slot="title">%fa:grin R% %i18n:@emojis.title%</div> | ||||
| 		<div slot="title"><fa :icon="['far', 'grin']"/> %i18n:@emojis.title%</div> | ||||
| 		<section v-for="emoji in emojis"> | ||||
| 			<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/> | ||||
| 			<ui-input v-model="emoji.name"> | ||||
| 				<span>%i18n:@add-emoji.name%</span> | ||||
| 				<span slot="text">%i18n:@add-emoji.name-desc%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-input v-model="emoji.aliases"> | ||||
| 				<span>%i18n:@add-emoji.aliases%</span> | ||||
| 				<span slot="text">%i18n:@add-emoji.aliases-desc%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-horizon-group inputs> | ||||
| 				<ui-input v-model="emoji.name"> | ||||
| 					<span>%i18n:@add-emoji.name%</span> | ||||
| 				</ui-input> | ||||
| 				<ui-input v-model="emoji.aliases"> | ||||
| 					<span>%i18n:@add-emoji.aliases%</span> | ||||
| 				</ui-input> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-input v-model="emoji.url"> | ||||
| 				<i slot="icon"><fa icon="link"/></i> | ||||
| 				<span>%i18n:@add-emoji.url%</span> | ||||
| 			</ui-input> | ||||
| 			<ui-button-group> | ||||
| 				<ui-button inline @click="updateEmoji(emoji)">%fa:save R% %i18n:@emojis.update%</ui-button> | ||||
| 				<ui-button inline @click="removeEmoji(emoji)">%fa:trash-alt R% %i18n:@emojis.remove%</ui-button> | ||||
| 			</ui-button-group> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-button @click="updateEmoji(emoji)"><fa :icon="['far', 'save']"/> %i18n:@emojis.update%</ui-button> | ||||
| 				<ui-button @click="removeEmoji(emoji)"><fa :icon="['far', 'trash-alt']"/> %i18n:@emojis.remove%</ui-button> | ||||
| 			</ui-horizon-group> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| </div> | ||||
| @@ -64,17 +69,24 @@ export default Vue.extend({ | ||||
| 			(this as any).api('admin/emoji/add', { | ||||
| 				name: this.name, | ||||
| 				url: this.url, | ||||
| 				aliases: this.aliases.split(' ') | ||||
| 				aliases: this.aliases.split(' ').filter(x => x.length > 0) | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Added` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'success', | ||||
| 					text: '%i18n:@add-emoji.added%' | ||||
| 				}); | ||||
| 				this.fetchEmojis(); | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		fetchEmojis() { | ||||
| 			(this as any).api('admin/emoji/list').then(emojis => { | ||||
| 				emojis.reverse(); | ||||
| 				emojis.forEach(e => e.aliases = (e.aliases || []).join(' ')); | ||||
| 				this.emojis = emojis; | ||||
| 			}); | ||||
| @@ -85,24 +97,51 @@ export default Vue.extend({ | ||||
| 				id: emoji.id, | ||||
| 				name: emoji.name, | ||||
| 				url: emoji.url, | ||||
| 				aliases: emoji.aliases.split(' ') | ||||
| 				aliases: emoji.aliases.split(' ').filter(x => x.length > 0) | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Updated` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'success', | ||||
| 					text: '%i18n:@updated%' | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		removeEmoji(emoji) { | ||||
| 			(this as any).api('admin/emoji/remove', { | ||||
| 				id: emoji.id | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Removed` }); | ||||
| 				this.fetchEmojis(); | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 			this.$swal({ | ||||
| 				type: 'warning', | ||||
| 				text: '%i18n:@remove-emoji.are-you-sure%'.replace('$1', emoji.name), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res.value) return; | ||||
|  | ||||
| 				(this as any).api('admin/emoji/remove', { | ||||
| 					id: emoji.id | ||||
| 				}).then(() => { | ||||
| 					this.$swal({ | ||||
| 						type: 'success', | ||||
| 						text: '%i18n:@remove-emoji.removed%' | ||||
| 					}); | ||||
| 					this.fetchEmojis(); | ||||
| 				}).catch(e => { | ||||
| 					this.$swal({ | ||||
| 						type: 'error', | ||||
| 						text: e | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .tumhkfkmgtvzljezfvmgkeurkfncshbe | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -29,9 +29,9 @@ export default Vue.extend({ | ||||
| 			(this as any).api('admin/update-meta', { | ||||
| 				hidedTags: this.hidedTags.split('\n') | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Saved` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Saved` }); | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| <template> | ||||
| <div class="mk-admin"> | ||||
| 	<nav> | ||||
| <div class="mk-admin" :class="{ isMobile }"> | ||||
| 	<header v-show="isMobile"> | ||||
| 		<button class="nav" @click="navOpend = true"><fa icon="bars"/></button> | ||||
| 		<span>MisskeyMyAdmin</span> | ||||
| 	</header> | ||||
| 	<div class="nav-backdrop" | ||||
| 		v-if="navOpend && isMobile" | ||||
| 		@click="navOpend = false" | ||||
| 		@touchstart="navOpend = false" | ||||
| 	></div> | ||||
| 	<nav v-show="navOpend"> | ||||
| 		<div class="mi"> | ||||
| 			<img svg-inline src="../assets/header-icon.svg"/> | ||||
| 		</div> | ||||
| @@ -9,30 +18,30 @@ | ||||
| 			<p class="name">{{ $store.state.i | userName }}</p> | ||||
| 		</div> | ||||
| 		<ul> | ||||
| 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:home .fw%%i18n:@dashboard%</li> | ||||
| 			<li @click="nav('instance')" :class="{ active: page == 'instance' }">%fa:cog .fw%%i18n:@instance%</li> | ||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li> | ||||
| 			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }">%fa:grin R .fw%%i18n:@emoji%</li> | ||||
| 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li> | ||||
| 			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li> | ||||
| 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>%i18n:@dashboard%</li> | ||||
| 			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>%i18n:@instance%</li> | ||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>%i18n:@users%</li> | ||||
| 			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="['far', 'grin']" fixed-width/>%i18n:@emoji%</li> | ||||
| 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>%i18n:@announcements%</li> | ||||
| 			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>%i18n:@hashtags%</li> | ||||
|  | ||||
| 			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:common.drive%</li> --> | ||||
| 			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>%i18n:common.drive%</li> --> | ||||
| 			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> --> | ||||
| 		</ul> | ||||
| 		<div class="back-to-misskey"> | ||||
| 			<a href="/">%fa:arrow-left% %i18n:@back-to-misskey%</a> | ||||
| 			<a href="/"><fa icon="arrow-left"/> %i18n:@back-to-misskey%</a> | ||||
| 		</div> | ||||
| 		<div class="version"> | ||||
| 			<small>Misskey {{ version }}</small> | ||||
| 		</div> | ||||
| 	</nav> | ||||
| 	<main> | ||||
| 		<div v-show="page == 'dashboard'"><x-dashboard/></div> | ||||
| 		<div v-show="page == 'instance'"><x-instance/></div> | ||||
| 		<div v-if="page == 'dashboard'"><x-dashboard/></div> | ||||
| 		<div v-if="page == 'instance'"><x-instance/></div> | ||||
| 		<div v-if="page == 'users'"><x-users/></div> | ||||
| 		<div v-show="page == 'emoji'"><x-emoji/></div> | ||||
| 		<div v-show="page == 'announcements'"><x-announcements/></div> | ||||
| 		<div v-show="page == 'hashtags'"><x-hashtags/></div> | ||||
| 		<div v-if="page == 'emoji'"><x-emoji/></div> | ||||
| 		<div v-if="page == 'announcements'"><x-announcements/></div> | ||||
| 		<div v-if="page == 'hashtags'"><x-hashtags/></div> | ||||
| 		<div v-if="page == 'drive'"></div> | ||||
| 		<div v-if="page == 'update'"></div> | ||||
| 	</main> | ||||
| @@ -49,6 +58,10 @@ import XAnnouncements from "./announcements.vue"; | ||||
| import XHashtags from "./hashtags.vue"; | ||||
| import XUsers from "./users.vue"; | ||||
|  | ||||
| // Detect the user agent | ||||
| const ua = navigator.userAgent.toLowerCase(); | ||||
| const isMobile = /mobile|iphone|ipad|android/.test(ua); | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XDashboard, | ||||
| @@ -58,10 +71,15 @@ export default Vue.extend({ | ||||
| 		XHashtags, | ||||
| 		XUsers | ||||
| 	}, | ||||
| 	provide: { | ||||
| 		isMobile | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			page: 'dashboard', | ||||
| 			version | ||||
| 			version, | ||||
| 			isMobile, | ||||
| 			navOpend: !isMobile | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
| @@ -74,12 +92,46 @@ export default Vue.extend({ | ||||
|  | ||||
| <style lang="stylus"> | ||||
| .mk-admin | ||||
| 	$headerHeight = 48px | ||||
|  | ||||
| 	display flex | ||||
| 	height 100% | ||||
|  | ||||
| 	> header | ||||
| 		position fixed | ||||
| 		top 0 | ||||
| 		z-index 10000 | ||||
| 		width 100% | ||||
| 		color var(--mobileHeaderFg) | ||||
| 		background-color var(--mobileHeaderBg) | ||||
| 		box-shadow 0 1px 0 rgba(#000, 0.075) | ||||
|  | ||||
| 		&, * | ||||
| 			user-select none | ||||
|  | ||||
| 		> span | ||||
| 			display block | ||||
| 			line-height $headerHeight | ||||
| 			text-align center | ||||
|  | ||||
| 		> .nav | ||||
| 			display block | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			left 0 | ||||
| 			z-index 10001 | ||||
| 			padding 0 | ||||
| 			width $headerHeight | ||||
| 			font-size 1.4em | ||||
| 			line-height $headerHeight | ||||
| 			border-right solid 1px rgba(#000, 0.1) | ||||
|  | ||||
| 			> [data-icon] | ||||
| 				transition all 0.2s ease | ||||
|  | ||||
| 	> nav | ||||
| 		position fixed | ||||
| 		z-index 10000 | ||||
| 		z-index 20001 | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		width 250px | ||||
| @@ -136,7 +188,7 @@ export default Vue.extend({ | ||||
| 				&:hover | ||||
| 					color #fff | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 6px | ||||
|  | ||||
| 		> .version | ||||
| @@ -166,7 +218,7 @@ export default Vue.extend({ | ||||
| 				&:hover | ||||
| 					color #fff | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 6px | ||||
|  | ||||
| 				&.active | ||||
| @@ -187,9 +239,22 @@ export default Vue.extend({ | ||||
| 						border-bottom solid 16px transparent | ||||
| 						border-left solid 16px transparent | ||||
|  | ||||
| 	> .nav-backdrop | ||||
| 		position fixed | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		z-index 20000 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background var(--mobileNavBackdrop) | ||||
|  | ||||
| 	> main | ||||
| 		width 100% | ||||
| 		padding 32px 32px 32px calc(32px + 250px) | ||||
| 		padding 0 0 0 250px | ||||
| 		max-width 1300px | ||||
|  | ||||
| 	&.isMobile | ||||
| 		> main | ||||
| 			padding $headerHeight 0 0 0 | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,9 +1,22 @@ | ||||
| <template> | ||||
| <div> | ||||
| <div class="axbwjelsbymowqjyywpirzhdlszoncqs"> | ||||
| 	<ui-card> | ||||
| 		<div slot="title">%i18n:@banner-url%</div> | ||||
| 		<section class="fit-top"> | ||||
| 			<ui-input v-model="bannerUrl"/> | ||||
| 		<div slot="title"><fa icon="cog"/> %i18n:@instance%</div> | ||||
| 		<section class="fit-top fit-bottom"> | ||||
| 			<ui-input v-model="name">%i18n:@instance-name%</ui-input> | ||||
| 			<ui-textarea v-model="description">%i18n:@instance-description%</ui-textarea> | ||||
| 			<ui-input v-model="bannerUrl"><i slot="icon"><fa icon="link"/></i>%i18n:@banner-url%</ui-input> | ||||
| 		</section> | ||||
| 		<section class="fit-top fit-bottom"> | ||||
| 			<ui-input v-model="maxNoteTextLength">%i18n:@max-note-text-length%</ui-input> | ||||
| 		</section> | ||||
| 		<section class="fit-bottom"> | ||||
| 			<header><fa icon="cloud"/> %i18n:@drive-config%</header> | ||||
| 			<ui-switch v-model="cacheRemoteFiles">%i18n:@cache-remote-files%<span slot="desc">%i18n:@cache-remote-files-desc%</span></ui-switch> | ||||
| 			<ui-input v-model="localDriveCapacityMb">%i18n:@local-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input> | ||||
| 			<ui-input v-model="remoteDriveCapacityMb" :disabled="!cacheRemoteFiles">%i18n:@remote-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<ui-button @click="updateMeta">%i18n:@save%</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| @@ -35,28 +48,70 @@ export default Vue.extend({ | ||||
| 			disableRegistration: false, | ||||
| 			disableLocalTimeline: false, | ||||
| 			bannerUrl: null, | ||||
| 			name: null, | ||||
| 			description: null, | ||||
| 			cacheRemoteFiles: false, | ||||
| 			localDriveCapacityMb: null, | ||||
| 			remoteDriveCapacityMb: null, | ||||
| 			maxNoteTextLength: null, | ||||
| 			inviteCode: null, | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.bannerUrl = meta.bannerUrl; | ||||
| 			this.name = meta.name; | ||||
| 			this.description = meta.description; | ||||
| 			this.cacheRemoteFiles = meta.cacheRemoteFiles; | ||||
| 			this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; | ||||
| 			this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; | ||||
| 			this.maxNoteTextLength = meta.maxNoteTextLength; | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		invite() { | ||||
| 			(this as any).api('admin/invite').then(x => { | ||||
| 				this.inviteCode = x.code; | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		updateMeta() { | ||||
| 			(this as any).api('admin/update-meta', { | ||||
| 				disableRegistration: this.disableRegistration, | ||||
| 				disableLocalTimeline: this.disableLocalTimeline, | ||||
| 				bannerUrl: this.bannerUrl | ||||
| 				bannerUrl: this.bannerUrl, | ||||
| 				name: this.name, | ||||
| 				description: this.description, | ||||
| 				cacheRemoteFiles: this.cacheRemoteFiles, | ||||
| 				localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), | ||||
| 				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), | ||||
| 				maxNoteTextLength: parseInt(this.maxNoteTextLength, 10) | ||||
| 			}).then(() => { | ||||
| 				(this as any).os.apis.dialog({ text: `Saved` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'success', | ||||
| 					text: '%i18n:@saved%' | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed ${e}` }); | ||||
| 				this.$swal({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .axbwjelsbymowqjyywpirzhdlszoncqs | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div> | ||||
| <div class="ucnffhbtogqgscfmqcymwmmupoknpfsw"> | ||||
| 	<ui-card> | ||||
| 		<div slot="title">%i18n:@verify-user%</div> | ||||
| 		<section class="fit-top"> | ||||
| @@ -67,11 +67,11 @@ export default Vue.extend({ | ||||
| 			const process = async () => { | ||||
| 				const user = await (this as any).os.api('users/show', parseAcct(this.verifyUsername)); | ||||
| 				await (this as any).os.api('admin/verify-user', { userId: user.id }); | ||||
| 				(this as any).os.apis.dialog({ text: '%i18n:@verified%' }); | ||||
| 				//(this as any).os.apis.dialog({ text: '%i18n:@verified%' }); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 			}); | ||||
|  | ||||
| 			this.verifying = false; | ||||
| @@ -83,11 +83,11 @@ export default Vue.extend({ | ||||
| 			const process = async () => { | ||||
| 				const user = await (this as any).os.api('users/show', parseAcct(this.unverifyUsername)); | ||||
| 				await (this as any).os.api('admin/unverify-user', { userId: user.id }); | ||||
| 				(this as any).os.apis.dialog({ text: '%i18n:@unverified%' }); | ||||
| 				//(this as any).os.apis.dialog({ text: '%i18n:@unverified%' }); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 			}); | ||||
|  | ||||
| 			this.unverifying = false; | ||||
| @@ -99,11 +99,11 @@ export default Vue.extend({ | ||||
| 			const process = async () => { | ||||
| 				const user = await (this as any).os.api('users/show', parseAcct(this.suspendUsername)); | ||||
| 				await (this as any).os.api('admin/suspend-user', { userId: user.id }); | ||||
| 				(this as any).os.apis.dialog({ text: '%i18n:@suspended%' }); | ||||
| 				//(this as any).os.apis.dialog({ text: '%i18n:@suspended%' }); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 			}); | ||||
|  | ||||
| 			this.suspending = false; | ||||
| @@ -115,11 +115,11 @@ export default Vue.extend({ | ||||
| 			const process = async () => { | ||||
| 				const user = await (this as any).os.api('users/show', parseAcct(this.unsuspendUsername)); | ||||
| 				await (this as any).os.api('admin/unsuspend-user', { userId: user.id }); | ||||
| 				(this as any).os.apis.dialog({ text: '%i18n:@unsuspended%' }); | ||||
| 				//(this as any).os.apis.dialog({ text: '%i18n:@unsuspended%' }); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				//(this as any).os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 			}); | ||||
|  | ||||
| 			this.unsuspending = false; | ||||
| @@ -127,3 +127,10 @@ export default Vue.extend({ | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .ucnffhbtogqgscfmqcymwmmupoknpfsw | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -128,7 +128,7 @@ pre | ||||
| 		overflow auto | ||||
| 		tab-size 2 | ||||
|  | ||||
| [data-fa] | ||||
| [data-icon] | ||||
| 	display inline-block | ||||
|  | ||||
| .swal2-container | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:key% API</div> | ||||
| 	<div slot="title"><fa icon="key"/> API</div> | ||||
|  | ||||
| 	<section class="fit-top"> | ||||
| 		<ui-input :value="$store.state.i.token" readonly> | ||||
| @@ -9,20 +9,21 @@ | ||||
| 		<p>%i18n:@intro%</p> | ||||
| 		<ui-info warn>%i18n:@caution%</ui-info> | ||||
| 		<p>%i18n:@regeneration-of-token%</p> | ||||
| 		<ui-button @click="regenerateToken">%fa:sync-alt% %i18n:@regenerate-token%</ui-button> | ||||
| 		<ui-button @click="regenerateToken"><fa icon="sync-alt"/> %i18n:@regenerate-token%</ui-button> | ||||
| 	</section> | ||||
|  | ||||
| 	<section> | ||||
| 		<header>%fa:terminal% %i18n:@console.title%</header> | ||||
| 		<header><fa icon="terminal"/> %i18n:@console.title%</header> | ||||
| 		<ui-input v-model="endpoint"> | ||||
| 			<span>%i18n:@console.endpoint%</span> | ||||
| 		</ui-input> | ||||
| 		<ui-textarea v-model="body"> | ||||
| 			<span>%i18n:@console.parameter% (JSON or JSON5)</span> | ||||
| 			<span slot="desc">%i18n:@console.credential-info%</span> | ||||
| 		</ui-textarea> | ||||
| 		<ui-button @click="send" :disabled="sending"> | ||||
| 			<template v-if="sending">%i18n:@console.sending%</template> | ||||
| 			<template v-else>%fa:paper-plane% %i18n:@console.send%</template> | ||||
| 			<template v-else><fa icon="paper-plane"/> %i18n:@console.send%</template> | ||||
| 		</ui-button> | ||||
| 		<ui-textarea v-if="res" v-model="res" readonly tall> | ||||
| 			<span>%i18n:@console.response%</span> | ||||
|   | ||||
| @@ -14,7 +14,8 @@ | ||||
| 	</ol> | ||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<span class="emoji" v-if="emoji.url"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||
| 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||
| 			<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||
| 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | ||||
| 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | ||||
| 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | ||||
| @@ -33,16 +34,24 @@ type EmojiDef = { | ||||
| 	name: string; | ||||
| 	aliasOf?: string; | ||||
| 	url?: string; | ||||
| 	isCustomEmoji?: boolean; | ||||
| }; | ||||
|  | ||||
| const lib = Object.entries(emojilib.lib).filter((x: any) => { | ||||
| 	return x[1].category != 'flags'; | ||||
| }); | ||||
|  | ||||
| const char2file = (char: string) => { | ||||
| 	let codes = [...char].map(x => x.codePointAt(0).toString(16)); | ||||
| 	if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); | ||||
| 	return codes.join('-'); | ||||
| }; | ||||
|  | ||||
| const emjdb: EmojiDef[] = lib.map((x: any) => ({ | ||||
| 	emoji: x[1].char, | ||||
| 	name: x[0], | ||||
| 	aliasOf: null | ||||
| 	aliasOf: null, | ||||
| 	url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg` | ||||
| })); | ||||
|  | ||||
| lib.forEach((x: any) => { | ||||
| @@ -51,7 +60,8 @@ lib.forEach((x: any) => { | ||||
| 			emjdb.push({ | ||||
| 				emoji: x[1].char, | ||||
| 				name: k, | ||||
| 				aliasOf: x[0] | ||||
| 				aliasOf: x[0], | ||||
| 				url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg` | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| @@ -77,6 +87,10 @@ export default Vue.extend({ | ||||
| 	computed: { | ||||
| 		items(): HTMLCollection { | ||||
| 			return (this.$refs.suggests as Element).children; | ||||
| 		}, | ||||
|  | ||||
| 		useOsDefaultEmojis(): boolean { | ||||
| 			return this.$store.state.device.useOsDefaultEmojis; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @@ -107,7 +121,8 @@ export default Vue.extend({ | ||||
| 			emojiDefinitions.push({ | ||||
| 				name: x.name, | ||||
| 				emoji: `:${x.name}:`, | ||||
| 				url: x.url | ||||
| 				url: x.url, | ||||
| 				isCustomEmoji: true | ||||
| 			}); | ||||
|  | ||||
| 			if (x.aliases) { | ||||
| @@ -116,7 +131,8 @@ export default Vue.extend({ | ||||
| 						name: alias, | ||||
| 						aliasOf: x.name, | ||||
| 						emoji: `:${x.name}:`, | ||||
| 						url: x.url | ||||
| 						url: x.url, | ||||
| 						isCustomEmoji: true | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| @@ -205,6 +221,15 @@ export default Vue.extend({ | ||||
| 					} | ||||
| 				} | ||||
| 			} else if (this.type == 'emoji') { | ||||
| 				if (this.q == null || this.q == '') { | ||||
| 					this.emojis = this.emojiDb.filter(x => x.isCustomEmoji && !x.aliasOf).sort((a, b) => { | ||||
| 						var textA = a.name.toUpperCase(); | ||||
| 						var textB = b.name.toUpperCase(); | ||||
| 						return (textA < textB) ? -1 : (textA > textB) ? 1 : 0; | ||||
| 					}); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				const matched = []; | ||||
| 				const max = 30; | ||||
|  | ||||
|   | ||||
| @@ -1,35 +1,35 @@ | ||||
| <template> | ||||
| <div class="troubleshooter"> | ||||
| 	<div class="body"> | ||||
| 		<h1>%fa:wrench%%i18n:@title%</h1> | ||||
| 		<h1><fa icon="wrench"/>%i18n:@title%</h1> | ||||
| 		<div> | ||||
| 			<p :data-wip="network == null"> | ||||
| 				<template v-if="network != null"> | ||||
| 					<template v-if="network">%fa:check%</template> | ||||
| 					<template v-if="!network">%fa:times%</template> | ||||
| 					<template v-if="network"><fa icon="check"/></template> | ||||
| 					<template v-if="!network"><fa icon="times"/></template> | ||||
| 				</template> | ||||
| 				{{ network == null ? '%i18n:@checking-network%' : '%i18n:@network%' }}<mk-ellipsis v-if="network == null"/> | ||||
| 			</p> | ||||
| 			<p v-if="network == true" :data-wip="internet == null"> | ||||
| 				<template v-if="internet != null"> | ||||
| 					<template v-if="internet">%fa:check%</template> | ||||
| 					<template v-if="!internet">%fa:times%</template> | ||||
| 					<template v-if="internet"><fa icon="check"/></template> | ||||
| 					<template v-if="!internet"><fa icon="times"/></template> | ||||
| 				</template> | ||||
| 				{{ internet == null ? '%i18n:@checking-internet%' : '%i18n:@internet%' }}<mk-ellipsis v-if="internet == null"/> | ||||
| 			</p> | ||||
| 			<p v-if="internet == true" :data-wip="server == null"> | ||||
| 				<template v-if="server != null"> | ||||
| 					<template v-if="server">%fa:check%</template> | ||||
| 					<template v-if="!server">%fa:times%</template> | ||||
| 					<template v-if="server"><fa icon="check"/></template> | ||||
| 					<template v-if="!server"><fa icon="times"/></template> | ||||
| 				</template> | ||||
| 				{{ server == null ? '%i18n:@checking-server%' : '%i18n:@server%' }}<mk-ellipsis v-if="server == null"/> | ||||
| 			</p> | ||||
| 		</div> | ||||
| 		<p v-if="!end">%i18n:@finding%<mk-ellipsis/></p> | ||||
| 		<p v-if="network === false"><b>%fa:exclamation-triangle%%i18n:@no-network%</b><br>%i18n:@no-network-desc%</p> | ||||
| 		<p v-if="internet === false"><b>%fa:exclamation-triangle%%i18n:@no-internet%</b><br>%i18n:@no-internet-desc%</p> | ||||
| 		<p v-if="server === false"><b>%fa:exclamation-triangle%%i18n:@no-server%</b><br>%i18n:@no-server-desc%</p> | ||||
| 		<p v-if="server === true" class="success"><b>%fa:info-circle%%i18n:@success%</b><br>%i18n:@success-desc%</p> | ||||
| 		<p v-if="network === false"><b><fa icon="exclamation-triangle"/>%i18n:@no-network%</b><br>%i18n:@no-network-desc%</p> | ||||
| 		<p v-if="internet === false"><b><fa icon="exclamation-triangle"/>%i18n:@no-internet%</b><br>%i18n:@no-internet-desc%</p> | ||||
| 		<p v-if="server === false"><b><fa icon="exclamation-triangle"/>%i18n:@no-server%</b><br>%i18n:@no-server-desc%</p> | ||||
| 		<p v-if="server === true" class="success"><b><fa icon="info-circle"/>%i18n:@success%</b><br>%i18n:@success-desc%</p> | ||||
| 	</div> | ||||
| 	<footer> | ||||
| 		<a href="/assets/flush.html">%i18n:@flush%</a> | <a href="/assets/version.html">%i18n:@set-version%</a> | ||||
| @@ -100,7 +100,7 @@ export default Vue.extend({ | ||||
| 			color #444 | ||||
| 			border-bottom solid 1px #eee | ||||
|  | ||||
| 			> [data-fa] | ||||
| 			> [data-icon] | ||||
| 				margin-right 0.25em | ||||
|  | ||||
| 		> div | ||||
| @@ -115,7 +115,7 @@ export default Vue.extend({ | ||||
| 				&[data-wip] | ||||
| 					color #888 | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 0.25em | ||||
|  | ||||
| 					&.times | ||||
| @@ -132,7 +132,7 @@ export default Vue.extend({ | ||||
| 			border-top solid 1px #eee | ||||
|  | ||||
| 			> b | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 0.25em | ||||
|  | ||||
| 			&.success | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="mk-connect-failed"> | ||||
| 	<img src="data:image/jpeg;base64,%base64:/assets/error.jpg%" alt=""/> | ||||
| 	<img src="https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg" alt=""/> | ||||
| 	<h1>%i18n:@title%</h1> | ||||
| 	<p class="text"> | ||||
| 		<span>{{ '%i18n:@description%'.substr(0, '%i18n:@description%'.indexOf('{')) }}</span> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:cloud% %i18n:common.drive%</div> | ||||
| 	<div slot="title"><fa icon="cloud"/> %i18n:common.drive%</div> | ||||
|  | ||||
| 	<section v-if="!fetching" class="juakhbxthdewydyreaphkepoxgxvfogn"> | ||||
| 		<div class="meter"><div :style="meterStyle"></div></div> | ||||
|   | ||||
							
								
								
									
										85
									
								
								src/client/app/common/views/components/emoji.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/client/app/common/views/components/emoji.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| <template> | ||||
| <img v-if="customEmoji" class="fvgwvorwhxigeolkkrcderjzcawqrscl custom" :src="url" :alt="alt" :title="alt"/> | ||||
| <img v-else-if="char && !useOsDefaultEmojis" class="fvgwvorwhxigeolkkrcderjzcawqrscl" :src="url" :alt="alt" :title="alt"/> | ||||
| <span v-else-if="char && useOsDefaultEmojis">{{ char }}</span> | ||||
| <span v-else>:{{ name }}:</span> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { lib } from 'emojilib'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		name: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		emoji: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		customEmojis: { | ||||
| 			required: false, | ||||
| 			default: [] | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			url: null, | ||||
| 			char: null, | ||||
| 			customEmoji: null | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		alt(): string { | ||||
| 			return this.customEmoji ? `:${this.customEmoji.name}:` : this.char; | ||||
| 		}, | ||||
|  | ||||
| 		useOsDefaultEmojis(): boolean { | ||||
| 			return this.$store.state.device.useOsDefaultEmojis; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		if (this.name) { | ||||
| 			const customEmoji = this.customEmojis.find(x => x.name == this.name); | ||||
| 			if (customEmoji) { | ||||
| 				this.customEmoji = customEmoji; | ||||
| 				this.url = customEmoji.url; | ||||
| 			} else { | ||||
| 				const emoji = lib[this.name]; | ||||
| 				if (emoji) { | ||||
| 					this.char = emoji.char; | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			this.char = this.emoji; | ||||
| 		} | ||||
|  | ||||
| 		if (this.char) { | ||||
| 			let codes = [...this.char].map(x => x.codePointAt(0).toString(16)); | ||||
| 			if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); | ||||
|  | ||||
| 			this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .fvgwvorwhxigeolkkrcderjzcawqrscl | ||||
| 	height 1.25em | ||||
| 	vertical-align -0.25em | ||||
|  | ||||
| 	&.custom | ||||
| 		height 2.5em | ||||
| 		vertical-align middle | ||||
| 		transition transform 0.2s ease | ||||
|  | ||||
| 		&:hover | ||||
| 			transform scale(1.2) | ||||
|  | ||||
| </style> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="wjqjnyhzogztorhrdgcpqlkxhkmuetgj"> | ||||
| 	<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> | ||||
| 	<p><fa icon="exclamation-triangle"/> %i18n:common.error.title%</p> | ||||
| 	<ui-button @click="() => $emit('retry')">%i18n:common.error.retry%</ui-button> | ||||
| </div> | ||||
| </template> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <span class="mk-file-type-icon"> | ||||
| 	<template v-if="kind == 'image'">%fa:file-image%</template> | ||||
| 	<template v-if="kind == 'image'"><fa icon="file-image"/></template> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="xqnhankfuuilcwvhgsopeqncafzsquya"> | ||||
| 	<button class="go-index" v-if="selfNav" @click="goIndex">%fa:arrow-left%</button> | ||||
| 	<button class="go-index" v-if="selfNav" @click="goIndex"><fa icon="arrow-left"/></button> | ||||
| 	<header><b><router-link :to="blackUser | userPage">{{ blackUser | userName }}</router-link></b>(%i18n:common.reversi.black%) vs <b><router-link :to="whiteUser | userPage">{{ whiteUser | userName }}</router-link></b>(%i18n:common.reversi.white%)</header> | ||||
|  | ||||
| 	<div style="overflow: hidden; line-height: 28px;"> | ||||
| @@ -51,13 +51,13 @@ | ||||
|  | ||||
| 	<div class="player" v-if="game.isEnded"> | ||||
| 		<div> | ||||
| 			<button @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</button> | ||||
| 			<button @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</button> | ||||
| 			<button @click="logPos = 0" :disabled="logPos == 0"><fa icon="angle-double-left"/></button> | ||||
| 			<button @click="logPos--" :disabled="logPos == 0"><fa icon="angle-left"/></button> | ||||
| 		</div> | ||||
| 		<span>{{ logPos }} / {{ logs.length }}</span> | ||||
| 		<div> | ||||
| 			<button @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</button> | ||||
| 			<button @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</button> | ||||
| 			<button @click="logPos++" :disabled="logPos == logs.length"><fa icon="angle-right"/></button> | ||||
| 			<button @click="logPos = logs.length" :disabled="logPos == logs.length"><fa icon="angle-double-right"/></button> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
|   | ||||
| @@ -17,13 +17,13 @@ | ||||
| 			</header> | ||||
|  | ||||
| 			<div> | ||||
| 				<div class="random" v-if="game.settings.map == null">%fa:dice%</div> | ||||
| 				<div class="random" v-if="game.settings.map == null"><fa icon="dice"/></div> | ||||
| 				<div class="board" v-else :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }"> | ||||
| 					<div v-for="(x, i) in game.settings.map.join('')" | ||||
| 							:data-none="x == ' '" | ||||
| 							@click="onPixelClick(i, x)"> | ||||
| 						<template v-if="x == 'b'"><template v-if="$store.state.device.darkmode">%fa:circle R%</template><template v-else>%fa:circle%</template></template> | ||||
| 						<template v-if="x == 'w'"><template v-if="$store.state.device.darkmode">%fa:circle%</template><template v-else>%fa:circle R%</template></template> | ||||
| 						<template v-if="x == 'b'"><template v-if="$store.state.device.darkmode"><fa :icon="['far', 'circle']"/></template><template v-else><fa icon="circle"/></template></template> | ||||
| 						<template v-if="x == 'w'"><template v-if="$store.state.device.darkmode"><fa :icon="['far', 'circle']"/></template><template v-else><fa icon="circle"/></template></template> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
|   | ||||
							
								
								
									
										63
									
								
								src/client/app/common/views/components/github-setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/client/app/common/views/components/github-setting.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| <template> | ||||
| <div class="mk-github-setting"> | ||||
| 	<p>%i18n:@description%<a :href="`${docsUrl}/link-to-github`" target="_blank">%i18n:@detail%</a></p> | ||||
| 	<p class="account" v-if="$store.state.i.github" :title="`GitHub ID: ${$store.state.i.github.id}`">%i18n:@connected-to%: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> | ||||
| 	<p> | ||||
| 		<a :href="`${apiUrl}/connect/github`" target="_blank" @click.prevent="connect">{{ $store.state.i.github ? '%i18n:@reconnect%' : '%i18n:@connect%' }}</a> | ||||
| 		<span v-if="$store.state.i.github"> or </span> | ||||
| 		<a :href="`${apiUrl}/disconnect/github`" target="_blank" v-if="$store.state.i.github" @click.prevent="disconnect">%i18n:@disconnect%</a> | ||||
| 	</p> | ||||
| 	<p class="id" v-if="$store.state.i.github">GitHub ID: {{ $store.state.i.github.id }}</p> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { apiUrl, docsUrl } from '../../../config'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			form: null, | ||||
| 			apiUrl, | ||||
| 			docsUrl | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$watch('$store.state.i', () => { | ||||
| 			if (this.$store.state.i.github && this.form) | ||||
| 				this.form.close(); | ||||
| 		}, { | ||||
| 			deep: true | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		connect() { | ||||
| 			this.form = window.open(apiUrl + '/connect/github', | ||||
| 				'github_connect_window', | ||||
| 				'height=570, width=520'); | ||||
| 		}, | ||||
|  | ||||
| 		disconnect() { | ||||
| 			window.open(apiUrl + '/disconnect/github', | ||||
| 				'github_disconnect_window', | ||||
| 				'height=570, width=520'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-github-setting | ||||
| 	.account | ||||
| 		border solid 1px #e1e8ed | ||||
| 		border-radius 4px | ||||
| 		padding 16px | ||||
|  | ||||
| 		a | ||||
| 			font-weight bold | ||||
| 			color inherit | ||||
|  | ||||
| 	.id | ||||
| 		color #8899a6 | ||||
| </style> | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="mk-google"> | ||||
| 	<input type="search" v-model="query" :placeholder="q"> | ||||
| 	<button @click="search">%fa:search% %i18n:common.search%</button> | ||||
| 	<button @click="search"><fa icon="search"/> %i18n:common.search%</button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -37,12 +37,13 @@ import messaging from './messaging.vue'; | ||||
| import messagingRoom from './messaging-room.vue'; | ||||
| import urlPreview from './url-preview.vue'; | ||||
| import twitterSetting from './twitter-setting.vue'; | ||||
| import githubSetting from './github-setting.vue'; | ||||
| import fileTypeIcon from './file-type-icon.vue'; | ||||
| import Reversi from './games/reversi/reversi.vue'; | ||||
| import emoji from './emoji.vue'; | ||||
| import welcomeTimeline from './welcome-timeline.vue'; | ||||
| import uiInput from './ui/input.vue'; | ||||
| import uiButton from './ui/button.vue'; | ||||
| import uiButtonGroup from './ui/button-group.vue'; | ||||
| import uiHorizonGroup from './ui/horizon-group.vue'; | ||||
| import uiCard from './ui/card.vue'; | ||||
| import uiForm from './ui/form.vue'; | ||||
| import uiTextarea from './ui/textarea.vue'; | ||||
| @@ -90,12 +91,13 @@ Vue.component('mk-messaging', messaging); | ||||
| Vue.component('mk-messaging-room', messagingRoom); | ||||
| Vue.component('mk-url-preview', urlPreview); | ||||
| Vue.component('mk-twitter-setting', twitterSetting); | ||||
| Vue.component('mk-github-setting', githubSetting); | ||||
| Vue.component('mk-file-type-icon', fileTypeIcon); | ||||
| Vue.component('mk-reversi', Reversi); | ||||
| Vue.component('mk-emoji', emoji); | ||||
| Vue.component('mk-welcome-timeline', welcomeTimeline); | ||||
| Vue.component('ui-input', uiInput); | ||||
| Vue.component('ui-button', uiButton); | ||||
| Vue.component('ui-button-group', uiButtonGroup); | ||||
| Vue.component('ui-horizon-group', uiHorizonGroup); | ||||
| Vue.component('ui-card', uiCard); | ||||
| Vue.component('ui-form', uiForm); | ||||
| Vue.component('ui-textarea', uiTextarea); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="mk-media-banner"> | ||||
| 	<div class="sensitive" v-if="media.isSensitive && hide" @click="hide = false"> | ||||
| 		<span class="icon">%fa:exclamation-triangle%</span> | ||||
| 		<span class="icon"><fa icon="exclamation-triangle"/></span> | ||||
| 		<b>%i18n:@sensitive%</b> | ||||
| 		<span>%i18n:@click-to-show%</span> | ||||
| 	</div> | ||||
| @@ -18,7 +18,7 @@ | ||||
| 		:title="media.name" | ||||
| 		:download="media.name" | ||||
| 	> | ||||
| 		<span class="icon">%fa:download%</span> | ||||
| 		<span class="icon"><fa icon="download"/></span> | ||||
| 		<b>{{ media.name }}</b> | ||||
| 	</a> | ||||
| </div> | ||||
|   | ||||
| @@ -4,7 +4,9 @@ | ||||
| 	<div class="popover" :class="{ hukidasi }" ref="popover"> | ||||
| 		<template v-for="item, i in items"> | ||||
| 			<div v-if="item === null"></div> | ||||
| 			<button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text" :tabindex="i"></button> | ||||
| 			<button v-if="item" @click="clicked(item.action)" :tabindex="i"> | ||||
| 				<fa v-if="item.icon" :icon="item.icon"/>{{ item.text }} | ||||
| 			</button> | ||||
| 		</template> | ||||
| 	</div> | ||||
| </div> | ||||
| @@ -188,6 +190,9 @@ export default Vue.extend({ | ||||
| 				color var(--primaryForeground) | ||||
| 				background var(--primaryDarken10) | ||||
|  | ||||
| 			> [data-icon] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		> div | ||||
| 			margin 8px 0 | ||||
| 			height 1px | ||||
|   | ||||
| @@ -14,13 +14,13 @@ | ||||
| 	<div class="file" @click="file = null" v-if="file">{{ file.name }}</div> | ||||
| 	<mk-uploader ref="uploader" @uploaded="onUploaded"/> | ||||
| 	<button class="send" @click="send" :disabled="!canSend || sending" title="%i18n:@send%"> | ||||
| 		<template v-if="!sending">%fa:paper-plane%</template><template v-if="sending">%fa:spinner .spin%</template> | ||||
| 		<template v-if="!sending"><fa icon="paper-plane"/></template><template v-if="sending"><fa icon="spinner .spin"/></template> | ||||
| 	</button> | ||||
| 	<button class="attach-from-local" @click="chooseFile" title="%i18n:@attach-from-local%"> | ||||
| 		%fa:upload% | ||||
| 		<fa icon="upload"/> | ||||
| 	</button> | ||||
| 	<button class="attach-from-drive" @click="chooseFileFromDrive" title="%i18n:@attach-from-drive%"> | ||||
| 		%fa:R folder-open% | ||||
| 		<fa :icon="['far', 'folder-open']"/> | ||||
| 	</button> | ||||
| 	<input ref="file" type="file" @change="onChangeFile"/> | ||||
| </div> | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| 		<footer> | ||||
| 			<span class="read" v-if="isMe && message.isRead">%i18n:@is-read%</span> | ||||
| 			<mk-time :time="message.createdAt"/> | ||||
| 			<template v-if="message.is_edited">%fa:pencil-alt%</template> | ||||
| 			<template v-if="message.is_edited"><fa icon="pencil-alt"/></template> | ||||
| 		</footer> | ||||
| 	</div> | ||||
| </div> | ||||
| @@ -179,7 +179,7 @@ export default Vue.extend({ | ||||
| 			font-size 10px | ||||
| 			color var(--messagingRoomMessageInfo) | ||||
|  | ||||
| 			> [data-fa] | ||||
| 			> [data-icon] | ||||
| 				margin-left 4px | ||||
|  | ||||
| 	&:not([data-is-me]) | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
| 	@drop.prevent.stop="onDrop" | ||||
| > | ||||
| 	<div class="body"> | ||||
| 		<p class="init" v-if="init">%fa:spinner .spin%%i18n:common.loading%</p> | ||||
| 		<p class="empty" v-if="!init && messages.length == 0">%fa:info-circle%%i18n:@empty%</p> | ||||
| 		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages">%fa:flag%%i18n:@no-history%</p> | ||||
| 		<p class="init" v-if="init"><fa icon="spinner .spin"/>%i18n:common.loading%</p> | ||||
| 		<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>%i18n:@empty%</p> | ||||
| 		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>%i18n:@no-history%</p> | ||||
| 		<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> | ||||
| 			<template v-if="fetchingMoreMessages">%fa:spinner .pulse .fw%</template>{{ fetchingMoreMessages ? '%i18n:common.loading%' : '%i18n:@more%' }} | ||||
| 			<template v-if="fetchingMoreMessages"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreMessages ? '%i18n:common.loading%' : '%i18n:@more%' }} | ||||
| 		</button> | ||||
| 		<template v-for="(message, i) in _messages"> | ||||
| 			<x-message :message="message" :key="message.id"/> | ||||
| @@ -20,7 +20,7 @@ | ||||
| 	<footer> | ||||
| 		<transition name="fade"> | ||||
| 			<div class="new-message" v-show="showIndicator"> | ||||
| 				<button @click="onIndicatorClick">%fa:arrow-circle-down%%i18n:@new-message%</button> | ||||
| 				<button @click="onIndicatorClick"><i><fa icon="arrow-circle-down"/></i>%i18n:@new-message%</button> | ||||
| 			</div> | ||||
| 		</transition> | ||||
| 		<x-form :user="user" ref="form"/> | ||||
| @@ -280,7 +280,7 @@ export default Vue.extend({ | ||||
| 			color var(--messagingRoomInfo) | ||||
| 			opacity 0.5 | ||||
|  | ||||
| 			[data-fa] | ||||
| 			[data-icon] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		> .no-history | ||||
| @@ -292,7 +292,7 @@ export default Vue.extend({ | ||||
| 			color var(--messagingRoomInfo) | ||||
| 			opacity 0.5 | ||||
|  | ||||
| 			[data-fa] | ||||
| 			[data-icon] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		> .more | ||||
| @@ -313,7 +313,7 @@ export default Vue.extend({ | ||||
| 			&.fetching | ||||
| 				cursor wait | ||||
|  | ||||
| 			> [data-fa] | ||||
| 			> [data-icon] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		> .message | ||||
| @@ -381,7 +381,7 @@ export default Vue.extend({ | ||||
| 				&:active | ||||
| 					background var(--primaryDarken10) | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> i | ||||
| 					position absolute | ||||
| 					top 0 | ||||
| 					left 10px | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="mk-messaging" :data-compact="compact"> | ||||
| 	<div class="search" v-if="!compact" :style="{ top: headerTop + 'px' }"> | ||||
| 		<div class="form"> | ||||
| 			<label for="search-input">%fa:search%</label> | ||||
| 			<label for="search-input"><i><fa icon="search"/></i></label> | ||||
| 			<input v-model="q" type="search" @input="search" @keydown="onSearchKeydown" placeholder="%i18n:@search-user%"/> | ||||
| 		</div> | ||||
| 		<div class="result"> | ||||
| @@ -45,7 +45,7 @@ | ||||
| 		</template> | ||||
| 	</div> | ||||
| 	<p class="no-history" v-if="!fetching && messages.length == 0">%i18n:@no-history%</p> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -213,7 +213,7 @@ export default Vue.extend({ | ||||
| 				width 38px | ||||
| 				pointer-events none | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> i | ||||
| 					display block | ||||
| 					position absolute | ||||
| 					top 0 | ||||
| @@ -418,7 +418,7 @@ export default Vue.extend({ | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	// TODO: element base media query | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import Vue, { VNode } from 'vue'; | ||||
| import * as emojilib from 'emojilib'; | ||||
| import { length } from 'stringz'; | ||||
| import parse from '../../../../../mfm/parse'; | ||||
| import getAcct from '../../../../../misc/acct/render'; | ||||
| @@ -188,24 +187,16 @@ export default Vue.component('misskey-flavored-markdown', { | ||||
| 				} | ||||
|  | ||||
| 				case 'emoji': { | ||||
| 					//#region カスタム絵文字 | ||||
| 					if (this.customEmojis != null) { | ||||
| 						const customEmoji = this.customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji)); | ||||
| 						if (customEmoji) { | ||||
| 							return [createElement('img', { | ||||
| 								attrs: { | ||||
| 									src: customEmoji.url, | ||||
| 									alt: token.emoji, | ||||
| 									title: token.emoji, | ||||
| 									style: 'height: 2.5em; vertical-align: middle;' | ||||
| 								} | ||||
| 							})]; | ||||
| 					const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis || []; | ||||
| 					return [createElement('mk-emoji', { | ||||
| 						attrs: { | ||||
| 							emoji: token.emoji, | ||||
| 							name: token.name | ||||
| 						}, | ||||
| 						props: { | ||||
| 							customEmojis: this.customEmojis || customEmojis | ||||
| 						} | ||||
| 					} | ||||
| 					//#endregion | ||||
|  | ||||
| 					const emoji = emojilib.lib[token.emoji]; | ||||
| 					return [createElement('span', emoji ? emoji.char : token.content)]; | ||||
| 					})]; | ||||
| 				} | ||||
|  | ||||
| 				case 'search': { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:ban% %i18n:@mute-and-block%</div> | ||||
| 	<div slot="title"><fa icon="ban"/> %i18n:@mute-and-block%</div> | ||||
|  | ||||
| 	<section> | ||||
| 		<header>%i18n:@mute%</header> | ||||
|   | ||||
| @@ -2,15 +2,11 @@ | ||||
| <span class="mk-nav"> | ||||
| 	<a :href="aboutUrl">%i18n:@about%</a> | ||||
| 	<i>・</i> | ||||
| 	<a href="/stats">%i18n:@stats%</a> | ||||
| 	<i>・</i> | ||||
| 	<a :href="repositoryUrl">%i18n:@repository%</a> | ||||
| 	<i>・</i> | ||||
| 	<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a> | ||||
| 	<i>・</i> | ||||
| 	<a href="/dev">%i18n:@develop%</a> | ||||
| 	<i>・</i> | ||||
| 	<a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -6,18 +6,18 @@ | ||||
| 	<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="%i18n:common.verified-user%">%fa:star%</span> | ||||
| 	<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%"><fa icon="star"/></span> | ||||
| 	<div class="info"> | ||||
| 		<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span> | ||||
| 		<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> | ||||
| 		<span class="mobile" v-if="note.viaMobile"><fa icon="mobile-alt"/></span> | ||||
| 		<router-link class="created-at" :to="note | notePage"> | ||||
| 			<mk-time :time="note.createdAt"/> | ||||
| 		</router-link> | ||||
| 		<span class="visibility" v-if="note.visibility != 'public'"> | ||||
| 			<template v-if="note.visibility == 'home'">%fa:home%</template> | ||||
| 			<template v-if="note.visibility == 'followers'">%fa:unlock%</template> | ||||
| 			<template v-if="note.visibility == 'specified'">%fa:envelope%</template> | ||||
| 			<template v-if="note.visibility == 'private'">%fa:lock%</template> | ||||
| 			<template v-if="note.visibility == 'home'"><fa icon="home"/></template> | ||||
| 			<template v-if="note.visibility == 'followers'"><fa icon="unlock"/></template> | ||||
| 			<template v-if="note.visibility == 'specified'"><fa icon="envelope"/></template> | ||||
| 			<template v-if="note.visibility == 'private'"><fa icon="lock"/></template> | ||||
| 		</span> | ||||
| 	</div> | ||||
| </header> | ||||
|   | ||||
| @@ -15,18 +15,18 @@ export default Vue.extend({ | ||||
| 	computed: { | ||||
| 		items() { | ||||
| 			const items = [{ | ||||
| 				icon: '%fa:info-circle%', | ||||
| 				icon: 'info-circle', | ||||
| 				text: '%i18n:@detail%', | ||||
| 				action: this.detail | ||||
| 			}, { | ||||
| 				icon: '%fa:link%', | ||||
| 				icon: 'link', | ||||
| 				text: '%i18n:@copy-link%', | ||||
| 				action: this.copyLink | ||||
| 			}]; | ||||
|  | ||||
| 			if (this.note.uri) { | ||||
| 				items.push({ | ||||
| 					icon: '%fa:external-link-square-alt%', | ||||
| 					icon: 'external-link-square-alt', | ||||
| 					text: '%i18n:@remote%', | ||||
| 					action: () => { | ||||
| 						window.open(this.note.uri, '_blank'); | ||||
| @@ -38,13 +38,13 @@ export default Vue.extend({ | ||||
|  | ||||
| 			if (this.note.isFavorited) { | ||||
| 				items.push({ | ||||
| 					icon: '%fa:star%', | ||||
| 					icon: 'star', | ||||
| 					text: '%i18n:@unfavorite%', | ||||
| 					action: this.unfavorite | ||||
| 				}); | ||||
| 			} else { | ||||
| 				items.push({ | ||||
| 					icon: '%fa:star%', | ||||
| 					icon: 'star', | ||||
| 					text: '%i18n:@favorite%', | ||||
| 					action: this.favorite | ||||
| 				}); | ||||
| @@ -53,13 +53,13 @@ export default Vue.extend({ | ||||
| 			if (this.note.userId == this.$store.state.i.id) { | ||||
| 				if ((this.$store.state.i.pinnedNoteIds || []).includes(this.note.id)) { | ||||
| 					items.push({ | ||||
| 						icon: '%fa:thumbtack%', | ||||
| 						icon: 'thumbtack', | ||||
| 						text: '%i18n:@unpin%', | ||||
| 						action: this.unpin | ||||
| 					}); | ||||
| 				} else { | ||||
| 					items.push({ | ||||
| 						icon: '%fa:thumbtack%', | ||||
| 						icon: 'thumbtack', | ||||
| 						text: '%i18n:@pin%', | ||||
| 						action: this.pin | ||||
| 					}); | ||||
| @@ -69,7 +69,7 @@ export default Vue.extend({ | ||||
| 			if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) { | ||||
| 				items.push(null); | ||||
| 				items.push({ | ||||
| 					icon: '%fa:trash-alt R%', | ||||
| 					icon: ['far', 'trash-alt'], | ||||
| 					text: '%i18n:@delete%', | ||||
| 					action: this.del | ||||
| 				}); | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| <template> | ||||
| <div class="mk-poll-editor"> | ||||
| 	<p class="caution" v-if="choices.length < 2"> | ||||
| 		%fa:exclamation-triangle%%i18n:@no-only-one-choice% | ||||
| 		<fa icon="exclamation-triangle"/>%i18n:@no-only-one-choice% | ||||
| 	</p> | ||||
| 	<ul ref="choices"> | ||||
| 		<li v-for="(choice, i) in choices"> | ||||
| 			<input :value="choice" @input="onInput(i, $event)" :placeholder="'%i18n:@choice-n%'.replace('{}', i + 1)"> | ||||
| 			<button @click="remove(i)" title="%i18n:@remove%"> | ||||
| 				%fa:times% | ||||
| 				<fa icon="times"/> | ||||
| 			</button> | ||||
| 		</li> | ||||
| 	</ul> | ||||
| 	<button class="add" v-if="choices.length < 10" @click="add">%i18n:@add%</button> | ||||
| 	<button class="destroy" @click="destroy" title="%i18n:@destroy%"> | ||||
| 		%fa:times% | ||||
| 		<fa icon="times"/> | ||||
| 	</button> | ||||
| </div> | ||||
| </template> | ||||
| @@ -76,7 +76,7 @@ export default Vue.extend({ | ||||
| 		font-size 0.8em | ||||
| 		color #f00 | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> ul | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<li v-for="choice in poll.choices" :key="choice.id" @click="vote(choice.id)" :class="{ voted: choice.voted }" :title="!isVoted ? '%i18n:@vote-to%'.replace('{}', choice.text) : ''"> | ||||
| 			<div class="backdrop" :style="{ 'width': (showResult ? (choice.votes / total * 100) : 0) + '%' }"></div> | ||||
| 			<span> | ||||
| 				<template v-if="choice.isVoted">%fa:check%</template> | ||||
| 				<template v-if="choice.isVoted"><fa icon="check"/></template> | ||||
| 				<span>{{ choice.text }}</span> | ||||
| 				<span class="votes" v-if="showResult">({{ '%i18n:@vote-count%'.replace('{}', choice.votes) }})</span> | ||||
| 			</span> | ||||
| @@ -100,7 +100,7 @@ export default Vue.extend({ | ||||
| 				transition width 1s ease | ||||
|  | ||||
| 			> span | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 4px | ||||
|  | ||||
| 				> .votes | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:user% %i18n:@title%</div> | ||||
| 	<div slot="title"><fa icon="user"/> %i18n:@title%</div> | ||||
|  | ||||
| 	<section class="fit-top"> | ||||
| 		<ui-form :disabled="saving"> | ||||
| @@ -16,12 +16,12 @@ | ||||
|  | ||||
| 			<ui-input v-model="location"> | ||||
| 				<span>%i18n:@location%</span> | ||||
| 				<span slot="prefix">%fa:map-marker-alt%</span> | ||||
| 				<span slot="prefix"><fa icon="map-marker-alt"/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 			<ui-input v-model="birthday" type="date"> | ||||
| 				<span>%i18n:@birthday%</span> | ||||
| 				<span slot="prefix">%fa:birthday-cake%</span> | ||||
| 				<span slot="prefix"><fa icon="birthday-cake"/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 			<ui-textarea v-model="description" :max="500"> | ||||
| @@ -30,14 +30,14 @@ | ||||
|  | ||||
| 			<ui-input type="file" @change="onAvatarChange"> | ||||
| 				<span>%i18n:@avatar%</span> | ||||
| 				<span slot="icon">%fa:image%</span> | ||||
| 				<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 				<span slot="icon"><fa icon="image"/></span> | ||||
| 				<span slot="desc" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 			<ui-input type="file" @change="onBannerChange"> | ||||
| 				<span>%i18n:@banner%</span> | ||||
| 				<span slot="icon">%fa:image%</span> | ||||
| 				<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 				<span slot="icon"><fa icon="image"/></span> | ||||
| 				<span slot="desc" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 			<ui-button @click="save(true)">%i18n:@save%</ui-button> | ||||
|   | ||||
| @@ -8,11 +8,12 @@ | ||||
| 	</ui-input> | ||||
| 	<ui-input v-model="password" type="password" required styl="fill"> | ||||
| 		<span>%i18n:@password%</span> | ||||
| 		<span slot="prefix">%fa:lock%</span> | ||||
| 		<span slot="prefix"><fa icon="lock"/></span> | ||||
| 	</ui-input> | ||||
| 	<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required styl="fill"/> | ||||
| 	<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button> | ||||
| 	<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p> | ||||
| 	<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/github`">%i18n:@signin-with-github%</a></p> | ||||
| </form> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -3,36 +3,36 @@ | ||||
| 	<template v-if="meta"> | ||||
| 		<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required styl="fill"> | ||||
| 			<span>%i18n:@invitation-code%</span> | ||||
| 			<span slot="prefix">%fa:id-card-alt%</span> | ||||
| 			<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p> | ||||
| 			<span slot="prefix"><fa icon="id-card-alt"/></span> | ||||
| 			<p slot="desc" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p> | ||||
| 		</ui-input> | ||||
| 		<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername" styl="fill"> | ||||
| 			<span>%i18n:@username%</span> | ||||
| 			<span slot="prefix">@</span> | ||||
| 			<span slot="suffix">@{{ host }}</span> | ||||
| 			<p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p> | ||||
| 			<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner .pulse" fixed-width/> %i18n:@checking%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@available%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@unavailable%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@error%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@invalid-format%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'min-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-short%</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'max-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-long%</p> | ||||
| 		</ui-input> | ||||
| 		<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true" styl="fill"> | ||||
| 			<span>%i18n:@password%</span> | ||||
| 			<span slot="prefix">%fa:lock%</span> | ||||
| 			<div slot="text"> | ||||
| 				<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p> | ||||
| 				<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p> | ||||
| 				<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p> | ||||
| 			<span slot="prefix"><fa icon="lock"/></span> | ||||
| 			<div slot="desc"> | ||||
| 				<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@weak-password%</p> | ||||
| 				<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@normal-password%</p> | ||||
| 				<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@strong-password%</p> | ||||
| 			</div> | ||||
| 		</ui-input> | ||||
| 		<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype" styl="fill"> | ||||
| 			<span>%i18n:@password% (%i18n:@retype%)</span> | ||||
| 			<span slot="prefix">%fa:lock%</span> | ||||
| 			<div slot="text"> | ||||
| 				<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p> | ||||
| 				<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p> | ||||
| 			<span slot="prefix"><fa icon="lock"/></span> | ||||
| 			<div slot="desc"> | ||||
| 				<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@password-matched%</p> | ||||
| 				<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@password-not-matched%</p> | ||||
| 			</div> | ||||
| 		</ui-input> | ||||
| 		<div v-if="meta.recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="meta.recaptchaSitekey" style="margin: 16px 0;"></div> | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| <template> | ||||
| <div class="mk-stream-indicator"> | ||||
| 	<p v-if="stream.state == 'initializing'"> | ||||
| 		%fa:spinner .pulse% | ||||
| 		<fa icon="spinner .pulse"/> | ||||
| 		<span>%i18n:@connecting%<mk-ellipsis/></span> | ||||
| 	</p> | ||||
| 	<p v-if="stream.state == 'reconnecting'"> | ||||
| 		%fa:spinner .pulse% | ||||
| 		<fa icon="spinner .pulse"/> | ||||
| 		<span>%i18n:@reconnecting%<mk-ellipsis/></span> | ||||
| 	</p> | ||||
| 	<p v-if="stream.state == 'connected'"> | ||||
| 		%fa:check% | ||||
| 		<fa icon="check"/> | ||||
| 		<span>%i18n:@connected%</span> | ||||
| 	</p> | ||||
| </div> | ||||
| @@ -80,7 +80,7 @@ export default Vue.extend({ | ||||
| 		display block | ||||
| 		margin 0 | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 0.25em | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="jtivnzhfwquxpsfidertopbmwmchmnmo"> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="tags.length == 0">%fa:exclamation-circle%%i18n:@empty%</p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="tags.length == 0"><fa icon="exclamation-circle"/>%i18n:@empty%</p> | ||||
| 	<div v-else> | ||||
| 		<vue-word-cloud | ||||
| 				:words="tags.slice(0, 20).map(x => [x.name, x.count])" | ||||
| @@ -74,7 +74,7 @@ export default Vue.extend({ | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> div | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| 	</label> | ||||
|  | ||||
| 	<details class="creator"> | ||||
| 		<summary>%fa:palette% %i18n:@create-a-theme%</summary> | ||||
| 		<summary><fa icon="palette"/> %i18n:@create-a-theme%</summary> | ||||
| 		<div> | ||||
| 			<span>%i18n:@base-theme%:</span> | ||||
| 			<ui-radio v-model="myThemeBase" value="light">%i18n:@base-theme-light%</ui-radio> | ||||
| @@ -51,23 +51,23 @@ | ||||
| 			<div style="padding-bottom:8px;">%i18n:@text-color%:</div> | ||||
| 			<color-picker v-model="myThemeText"/> | ||||
| 		</div> | ||||
| 		<ui-button @click="preview()">%fa:eye% %i18n:@preview-created-theme%</ui-button> | ||||
| 		<ui-button primary @click="gen()">%fa:save R% %i18n:@save-created-theme%</ui-button> | ||||
| 		<ui-button @click="preview()"><fa icon="eye"/> %i18n:@preview-created-theme%</ui-button> | ||||
| 		<ui-button primary @click="gen()"><fa :icon="['far', 'save']"/> %i18n:@save-created-theme%</ui-button> | ||||
| 	</details> | ||||
|  | ||||
| 	<details> | ||||
| 		<summary>%fa:download% %i18n:@install-a-theme%</summary> | ||||
| 		<ui-button @click="import_()">%fa:file-import% %i18n:@import%</ui-button> | ||||
| 		<summary><fa icon="download"/> %i18n:@install-a-theme%</summary> | ||||
| 		<ui-button @click="import_()"><fa icon="file-import"/> %i18n:@import%</ui-button> | ||||
| 		<input ref="file" type="file" accept=".misskeytheme" style="display:none;" @change="onUpdateImportFile"/> | ||||
| 		<p>%i18n:@import-by-code%:</p> | ||||
| 		<ui-textarea v-model="installThemeCode"> | ||||
| 			<span>%i18n:@theme-code%</span> | ||||
| 		</ui-textarea> | ||||
| 		<ui-button @click="() => install(this.installThemeCode)">%fa:check% %i18n:@install%</ui-button> | ||||
| 		<ui-button @click="() => install(this.installThemeCode)"><fa icon="check"/> %i18n:@install%</ui-button> | ||||
| 	</details> | ||||
|  | ||||
| 	<details> | ||||
| 		<summary>%fa:folder-open% %i18n:@manage-themes%</summary> | ||||
| 		<summary><fa icon="folder-open"/> %i18n:@manage-themes%</summary> | ||||
| 		<ui-select v-model="selectedThemeId" placeholder="%i18n:@select-theme%"> | ||||
| 			<optgroup label="%i18n:@builtin-themes%"> | ||||
| 				<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option> | ||||
| @@ -89,8 +89,8 @@ | ||||
| 			<ui-textarea readonly :value="selectedThemeCode"> | ||||
| 				<span>%i18n:@theme-code%</span> | ||||
| 			</ui-textarea> | ||||
| 			<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button> | ||||
| 			<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)">%fa:trash-alt R% %i18n:@uninstall%</ui-button> | ||||
| 			<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export"><fa icon="box"/> %i18n:@export%</ui-button> | ||||
| 			<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><fa :icon="['far', 'trash-alt']"/> %i18n:@uninstall%</ui-button> | ||||
| 		</template> | ||||
| 	</details> | ||||
| </div> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc"> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="stats.length == 0"><fa icon="exclamation-circle"/>%i18n:@empty%</p> | ||||
| 	<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||
| 	<transition-group v-else tag="div" name="chart"> | ||||
| 		<div v-for="stat in stats" :key="stat.tag"> | ||||
| @@ -58,7 +58,7 @@ export default Vue.extend({ | ||||
| 		color var(--text) | ||||
| 		opacity 0.7 | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> div | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| <template> | ||||
| <div class="pfzekjfwkwvadvlujpdnnxfggqgqjoze"> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({}); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .pfzekjfwkwvadvlujpdnnxfggqgqjoze | ||||
| 	display flex | ||||
|  | ||||
| 	> * | ||||
| 		flex 1 | ||||
|  | ||||
| 		&:not(:last-child) | ||||
| 			margin-right 16px | ||||
| </style> | ||||
| @@ -1,5 +1,10 @@ | ||||
| <template> | ||||
| <component class="dmtdnykelhudezerjlfpbhgovrgnqqgr" :is="link ? 'a' : 'button'" :class="[styl, { inline, primary }]" :type="type" @click="$emit('click')"> | ||||
| <component class="dmtdnykelhudezerjlfpbhgovrgnqqgr" | ||||
| 	:is="link ? 'a' : 'button'" | ||||
| 	:class="[styl, { inline, primary }]" | ||||
| 	:type="type" | ||||
| 	@click="$emit('click')" | ||||
| > | ||||
| 	<slot></slot> | ||||
| </component> | ||||
| </template> | ||||
| @@ -7,6 +12,11 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	inject: { | ||||
| 		horizonGrouped: { | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	props: { | ||||
| 		type: { | ||||
| 			type: String, | ||||
| @@ -20,7 +30,9 @@ export default Vue.extend({ | ||||
| 		inline: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 			default(): boolean { | ||||
| 				return this.horizonGrouped; | ||||
| 			} | ||||
| 		}, | ||||
| 		link: { | ||||
| 			type: Boolean, | ||||
|   | ||||
| @@ -48,6 +48,9 @@ export default Vue.extend({ | ||||
| 		&.fit-top | ||||
| 			padding-top 0 | ||||
|  | ||||
| 		&.fit-bottom | ||||
| 			padding-bottom 0 | ||||
|  | ||||
| 		> header | ||||
| 			margin-bottom 16px | ||||
| 			font-weight bold | ||||
|   | ||||
							
								
								
									
										35
									
								
								src/client/app/common/views/components/ui/horizon-group.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/client/app/common/views/components/ui/horizon-group.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| <template> | ||||
| <div class="pfzekjfwkwvadvlujpdnnxfggqgqjoze" :class="{ inputs }"> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	provide: { | ||||
| 		horizonGrouped: true | ||||
| 	}, | ||||
| 	props: { | ||||
| 		inputs: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .pfzekjfwkwvadvlujpdnnxfggqgqjoze | ||||
| 	display flex | ||||
|  | ||||
| 	&.inputs | ||||
| 		margin 32px 0 | ||||
|  | ||||
| 	> * | ||||
| 		flex 1 | ||||
|  | ||||
| 		&:not(:last-child) | ||||
| 			margin-right 16px | ||||
| </style> | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="ymxyweixqwsxauxldgpvecjepnwxbylu" :class="{ warn }"> | ||||
| 	<i v-if="warn">%fa:exclamation-triangle%</i> | ||||
| 	<i v-else>%fa:info-circle%</i> | ||||
| 	<i v-if="warn"><fa icon="exclamation-triangle"/></i> | ||||
| 	<i v-else><fa icon="info-circle"/></i> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="ui-input" :class="[{ focused, filled }, styl]"> | ||||
| <div class="ui-input" :class="[{ focused, filled, inline, disabled }, styl]"> | ||||
| 	<div class="icon" ref="icon"><slot name="icon"></slot></div> | ||||
| 	<div class="input"> | ||||
| 		<div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength"> | ||||
| @@ -11,6 +11,7 @@ | ||||
| 			<input ref="input" | ||||
| 					:type="type" | ||||
| 					v-model="v" | ||||
| 					:disabled="disabled" | ||||
| 					:required="required" | ||||
| 					:readonly="readonly" | ||||
| 					:pattern="pattern" | ||||
| @@ -32,7 +33,7 @@ | ||||
| 		</template> | ||||
| 		<div class="suffix" ref="suffix"><slot name="suffix"></slot></div> | ||||
| 	</div> | ||||
| 	<div class="text"><slot name="text"></slot></div> | ||||
| 	<div class="desc"><slot name="desc"></slot></div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -41,6 +42,11 @@ import Vue from 'vue'; | ||||
| const getPasswordStrength = require('syuilo-password-strength'); | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	inject: { | ||||
| 		horizonGrouped: { | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			required: false | ||||
| @@ -57,6 +63,10 @@ export default Vue.extend({ | ||||
| 			type: Boolean, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		disabled: { | ||||
| 			type: Boolean, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		pattern: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| @@ -72,6 +82,13 @@ export default Vue.extend({ | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		inline: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default(): boolean { | ||||
| 				return this.horizonGrouped; | ||||
| 			} | ||||
| 		}, | ||||
| 		styl: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| @@ -304,7 +321,7 @@ root(fill) | ||||
| 			if fill | ||||
| 				padding-right 12px | ||||
|  | ||||
| 	> .text | ||||
| 	> .desc | ||||
| 		margin 6px 0 | ||||
| 		font-size 13px | ||||
|  | ||||
| @@ -337,4 +354,14 @@ root(fill) | ||||
| 	&:not(.fill) | ||||
| 		root(false) | ||||
|  | ||||
| 	&.inline | ||||
| 		display inline-block | ||||
| 		margin 0 | ||||
|  | ||||
| 	&.disabled | ||||
| 		opacity 0.7 | ||||
|  | ||||
| 		&, * | ||||
| 			cursor not-allowed !important | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -129,5 +129,6 @@ export default Vue.extend({ | ||||
| 		> p | ||||
| 			margin 0 | ||||
| 			opacity 0.7 | ||||
| 			font-size 90% | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
| 			@blur="focused = false" | ||||
| 		></textarea> | ||||
| 	</div> | ||||
| 	<div class="text"><slot name="text"></slot></div> | ||||
| 	<div class="desc"><slot name="desc"></slot></div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -139,7 +139,7 @@ root(fill) | ||||
| 			outline none | ||||
| 			box-shadow none | ||||
|  | ||||
| 	> .text | ||||
| 	> .desc | ||||
| 		margin 6px 0 | ||||
| 		font-size 13px | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<ol v-if="uploads.length > 0"> | ||||
| 		<li v-for="ctx in uploads" :key="ctx.id"> | ||||
| 			<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> | ||||
| 			<p class="name">%fa:spinner .pulse%{{ ctx.name }}</p> | ||||
| 			<p class="name"><fa icon="spinner .pulse"/>{{ ctx.name }}</p> | ||||
| 			<p class="status"> | ||||
| 				<span class="initing" v-if="ctx.progress == undefined">%i18n:@waiting%<mk-ellipsis/></span> | ||||
| 				<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> | ||||
| @@ -155,7 +155,7 @@ export default Vue.extend({ | ||||
| 				text-overflow ellipsis | ||||
| 				overflow hidden | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 4px | ||||
|  | ||||
| 			> .status | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| 	<span class="pathname" v-if="pathname != ''">{{ pathname }}</span> | ||||
| 	<span class="query">{{ query }}</span> | ||||
| 	<span class="hash">{{ hash }}</span> | ||||
| 	%fa:external-link-square-alt% | ||||
| 	<fa icon="external-link-square-alt"/> | ||||
| </a> | ||||
| </template> | ||||
|  | ||||
| @@ -40,7 +40,7 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| .mk-url | ||||
| 	word-break break-all | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		padding-left 2px | ||||
| 		font-size .9em | ||||
| 		font-weight 400 | ||||
|   | ||||
| @@ -3,34 +3,34 @@ | ||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||
| 	<div class="popover" :class="{ compact }" ref="popover"> | ||||
| 		<div @click="choose('public')" :class="{ active: v == 'public' }"> | ||||
| 			<div>%fa:globe%</div> | ||||
| 			<div><fa icon="globe"/></div> | ||||
| 			<div> | ||||
| 				<span>%i18n:@public%</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('home')" :class="{ active: v == 'home' }"> | ||||
| 			<div>%fa:home%</div> | ||||
| 			<div><fa icon="home"/></div> | ||||
| 			<div> | ||||
| 				<span>%i18n:@home%</span> | ||||
| 				<span>%i18n:@home-desc%</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('followers')" :class="{ active: v == 'followers' }"> | ||||
| 			<div>%fa:unlock%</div> | ||||
| 			<div><fa icon="unlock"/></div> | ||||
| 			<div> | ||||
| 				<span>%i18n:@followers%</span> | ||||
| 				<span>%i18n:@followers-desc%</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('specified')" :class="{ active: v == 'specified' }"> | ||||
| 			<div>%fa:envelope%</div> | ||||
| 			<div><fa icon="envelope"/></div> | ||||
| 			<div> | ||||
| 				<span>%i18n:@specified%</span> | ||||
| 				<span>%i18n:@specified-desc%</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('private')" :class="{ active: v == 'private' }"> | ||||
| 			<div>%fa:lock%</div> | ||||
| 			<div><fa icon="lock"/></div> | ||||
| 			<div> | ||||
| 				<span>%i18n:@private%</span> | ||||
| 			</div> | ||||
|   | ||||
| @@ -109,7 +109,7 @@ class Autocomplete { | ||||
|  | ||||
| 		if (isEmoji && opened == false) { | ||||
| 			const emoji = text.substr(emojiIndex + 1); | ||||
| 			if (emoji != '' && emoji.match(/^[\+\-a-z0-9_]+$/)) { | ||||
| 			if (!emoji.includes(' ')) { | ||||
| 				this.open('emoji', emoji); | ||||
| 				opened = true; | ||||
| 			} | ||||
| @@ -145,6 +145,7 @@ class Autocomplete { | ||||
| 		} else { | ||||
| 			// サジェスト要素作成 | ||||
| 			this.suggestion = new MkAutocomplete({ | ||||
| 				parent: this.vm, | ||||
| 				propsData: { | ||||
| 					textarea: this.textarea, | ||||
| 					complete: this.complete, | ||||
| @@ -222,8 +223,6 @@ class Autocomplete { | ||||
| 			const trimmedBefore = before.substring(0, before.lastIndexOf(':')); | ||||
| 			const after = source.substr(caret); | ||||
|  | ||||
| 			if (value.startsWith(':')) value = value + ' '; | ||||
|  | ||||
| 			// 挿入 | ||||
| 			this.text = trimmedBefore + value + after; | ||||
|  | ||||
|   | ||||
| @@ -19,13 +19,13 @@ | ||||
| 			@click="onClick" | ||||
| 			:disabled="followWait"> | ||||
| 		<template v-if="!followWait"> | ||||
| 			<template v-if="user.hasPendingFollowRequestFromYou && user.isLocked">%fa:hourglass-half% %i18n:@request-pending%</template> | ||||
| 			<template v-else-if="user.hasPendingFollowRequestFromYou && !user.isLocked">%fa:hourglass-start% %i18n:@follow-processing%</template> | ||||
| 			<template v-else-if="user.isFollowing">%fa:minus% %i18n:@following%</template> | ||||
| 			<template v-else-if="!user.isFollowing && user.isLocked">%fa:plus% %i18n:@follow-request%</template> | ||||
| 			<template v-else-if="!user.isFollowing && !user.isLocked">%fa:plus% %i18n:@follow%</template> | ||||
| 			<template v-if="user.hasPendingFollowRequestFromYou && user.isLocked"><fa icon="hourglass-half"/> %i18n:@request-pending%</template> | ||||
| 			<template v-else-if="user.hasPendingFollowRequestFromYou && !user.isLocked"><fa icon="hourglass-start"/> %i18n:@follow-processing%</template> | ||||
| 			<template v-else-if="user.isFollowing"><fa icon="minus"/> %i18n:@following%</template> | ||||
| 			<template v-else-if="!user.isFollowing && user.isLocked"><fa icon="plus"/> %i18n:@follow-request%</template> | ||||
| 			<template v-else-if="!user.isFollowing && !user.isLocked"><fa icon="plus"/> %i18n:@follow%</template> | ||||
| 		</template> | ||||
| 		<template v-else>%fa:spinner .pulse .fw%</template> | ||||
| 		<template v-else><fa icon="spinner .pulse" fixed-width/></template> | ||||
| 	</button> | ||||
| </div> | ||||
| </template> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div> | ||||
| 	<mk-widget-container :show-header="false"> | ||||
| 		<article class="dolfvtibguprpxxhfndqaosjitixjohx"> | ||||
| 			<h1>%fa:heart%%i18n:@title%</h1> | ||||
| 			<h1><fa icon="heart"/>%i18n:@title%</h1> | ||||
| 			<p v-if="meta"> | ||||
| 				{{ '%i18n:@text%'.substr(0, '%i18n:@text%'.indexOf('{')) }} | ||||
| 				<a :href="meta.maintainer.url">{{ meta.maintainer.name }}</a> | ||||
| @@ -41,7 +41,7 @@ export default define({ | ||||
| 		margin 0 0 5px 0 | ||||
| 		font-size 1em | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 0.25em | ||||
|  | ||||
| 	> p | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="mkw-hashtags"> | ||||
| 	<mk-widget-container :show-header="!props.compact"> | ||||
| 		<template slot="header">%fa:hashtag%%i18n:@title%</template> | ||||
| 		<template slot="header"><fa icon="hashtag"/>%i18n:@title%</template> | ||||
|  | ||||
| 		<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'"> | ||||
| 			<mk-trends/> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="mkw-memo"> | ||||
| 	<mk-widget-container :show-header="!props.compact"> | ||||
| 		<template slot="header">%fa:R sticky-note%%i18n:@title%</template> | ||||
| 		<template slot="header"><fa :icon="['far', 'sticky-note']"/>%i18n:@title%</template> | ||||
|  | ||||
| 		<div class="mkw-memo--body"> | ||||
| 			<textarea v-model="text" placeholder="%i18n:@memo%" @input="onChange"></textarea> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <template> | ||||
| <div class="mkw-photo-stream" :class="$style.root" :data-melt="props.design == 2"> | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header">%fa:camera%%i18n:@title%</template> | ||||
| 		<template slot="header"><fa icon="camera"/>%i18n:@title%</template> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<div :class="$style.stream" v-if="!fetching && images.length > 0"> | ||||
| 			<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.thumbnailUrl || image.url})`"></div> | ||||
| 		</div> | ||||
| @@ -94,7 +94,7 @@ export default define({ | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
|  | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| <template> | ||||
| <div class="mkw-posts-monitor"> | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header">%fa:chart-line%%i18n:@title%</template> | ||||
| 		<button slot="func" @click="toggle" title="%i18n:@toggle%">%fa:sort%</button> | ||||
| 		<template slot="header"><fa icon="chart-line"/>%i18n:@title%</template> | ||||
| 		<button slot="func" @click="toggle" title="%i18n:@toggle%"><fa icon="sort"/></button> | ||||
|  | ||||
| 		<div class="qpdmibaztplkylerhdbllwcokyrfxeyj" :class="{ dual: props.view == 0 }"> | ||||
| 			<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" v-show="props.view != 2"> | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <template> | ||||
| <div class="mkw-rss"> | ||||
| 	<mk-widget-container :show-header="!props.compact"> | ||||
| 		<template slot="header">%fa:rss-square%RSS</template> | ||||
| 		<button slot="func" title="設定" @click="setting">%fa:cog%</button> | ||||
| 		<template slot="header"><fa icon="rss-square"/>RSS</template> | ||||
| 		<button slot="func" title="設定" @click="setting"><fa icon="cog"/></button> | ||||
|  | ||||
| 		<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> | ||||
| 			<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 			<div class="feed" v-else> | ||||
| 				<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> | ||||
| 			</div> | ||||
| @@ -85,7 +85,7 @@ export default define({ | ||||
| 			text-align center | ||||
| 			color #aaa | ||||
|  | ||||
| 			> [data-fa] | ||||
| 			> [data-icon] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		&[data-mobile] | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="cpu"> | ||||
| 	<x-pie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p>%fa:microchip%CPU</p> | ||||
| 		<p><fa icon="microchip"/>CPU</p> | ||||
| 		<p>{{ meta.cpu.cores }} Cores</p> | ||||
| 		<p>{{ meta.cpu.model }}</p> | ||||
| 	</div> | ||||
| @@ -57,7 +57,7 @@ export default Vue.extend({ | ||||
| 			&:first-child | ||||
| 				font-weight bold | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 4px | ||||
|  | ||||
| 	&:after | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="disk"> | ||||
| 	<x-pie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p>%fa:R hdd%Storage</p> | ||||
| 		<p><fa :icon="['far', 'hdd']"/>Storage</p> | ||||
| 		<p>Total: {{ total | bytes(1) }}</p> | ||||
| 		<p>Free: {{ available | bytes(1) }}</p> | ||||
| 		<p>Used: {{ used | bytes(1) }}</p> | ||||
| @@ -65,7 +65,7 @@ export default Vue.extend({ | ||||
| 			&:first-child | ||||
| 				font-weight bold | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 4px | ||||
|  | ||||
| 	&:after | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="memory"> | ||||
| 	<x-pie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p>%fa:flask%Memory</p> | ||||
| 		<p><fa icon="flask"/>Memory</p> | ||||
| 		<p>Total: {{ total | bytes(1) }}</p> | ||||
| 		<p>Used: {{ used | bytes(1) }}</p> | ||||
| 		<p>Free: {{ free | bytes(1) }}</p> | ||||
| @@ -65,7 +65,7 @@ export default Vue.extend({ | ||||
| 			&:first-child | ||||
| 				font-weight bold | ||||
|  | ||||
| 				> [data-fa] | ||||
| 				> [data-icon] | ||||
| 					margin-right 4px | ||||
|  | ||||
| 	&:after | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <template> | ||||
| <div class="mkw-server"> | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header">%fa:server%%i18n:@title%</template> | ||||
| 		<button slot="func" @click="toggle" title="%i18n:@toggle%">%fa:sort%</button> | ||||
| 		<template slot="header"><fa icon="server"/>%i18n:@title%</template> | ||||
| 		<button slot="func" @click="toggle" title="%i18n:@toggle%"><fa icon="sort"/></button> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<template v-if="!fetching"> | ||||
| 			<x-cpu-memory v-show="props.view == 0" :connection="connection"/> | ||||
| 			<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/> | ||||
| @@ -87,7 +87,7 @@ export default define({ | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
|  | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="mkw-tips"> | ||||
| 	<p ref="tip">%fa:R lightbulb%<span v-html="tip"></span></p> | ||||
| 	<p ref="tip"><fa :icon="['far', 'lightbulb']"/><span v-html="tip"></span></p> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -88,7 +88,7 @@ export default define({ | ||||
| 		font-size 0.7em | ||||
| 		color #999 | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 		kbd | ||||
|   | ||||
| @@ -23,7 +23,6 @@ import updateBanner from './api/update-banner'; | ||||
| import MkIndex from './views/pages/index.vue'; | ||||
| import MkHome from './views/pages/home.vue'; | ||||
| import MkDeck from './views/pages/deck/deck.vue'; | ||||
| import MkStats from './views/pages/stats/stats.vue'; | ||||
| import MkUser from './views/pages/user/user.vue'; | ||||
| import MkFavorites from './views/pages/favorites.vue'; | ||||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||
| @@ -56,7 +55,6 @@ init(async (launch) => { | ||||
| 			{ path: '/', name: 'index', component: MkIndex }, | ||||
| 			{ path: '/home', name: 'home', component: MkHome }, | ||||
| 			{ path: '/deck', name: 'deck', component: MkDeck }, | ||||
| 			{ path: '/stats', name: 'stats', component: MkStats }, | ||||
| 			{ path: '/i/customize-home', component: MkHomeCustomize }, | ||||
| 			{ path: '/i/favorites', component: MkFavorites }, | ||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <template> | ||||
| <div class="mk-activity"> | ||||
| 	<mk-widget-container :show-header="design == 0" :naked="design == 2"> | ||||
| 		<template slot="header">%fa:chart-bar%%i18n:@title%</template> | ||||
| 		<button slot="func" title="%i18n:@toggle%" @click="toggle">%fa:sort%</button> | ||||
| 		<template slot="header"><fa icon="chart-bar"/>%i18n:@title%</template> | ||||
| 		<button slot="func" title="%i18n:@toggle%" @click="toggle"><fa icon="sort"/></button> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<template v-else> | ||||
| 			<x-calendar v-show="view == 0" :data="[].concat(activity)"/> | ||||
| 			<x-chart v-show="view == 1" :data="[].concat(activity)"/> | ||||
| @@ -78,7 +78,7 @@ export default Vue.extend({ | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
|  | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <template> | ||||
| <div class="mk-calendar" :data-melt="design == 4 || design == 5"> | ||||
| 	<template v-if="design == 0 || design == 1"> | ||||
| 		<button @click="prev" title="%i18n:@prev%">%fa:chevron-circle-left%</button> | ||||
| 		<button @click="prev" title="%i18n:@prev%"><fa icon="chevron-circle-left"/></button> | ||||
| 		<p class="title">{{ '%i18n:@title%'.replace('{1}', year).replace('{2}', month) }}</p> | ||||
| 		<button @click="next" title="%i18n:@next%">%fa:chevron-circle-right%</button> | ||||
| 		<button @click="next" title="%i18n:@next%"><fa icon="chevron-circle-right"/></button> | ||||
| 	</template> | ||||
|  | ||||
| 	<div class="calendar"> | ||||
| @@ -151,7 +151,7 @@ export default Vue.extend({ | ||||
| 		background var(--faceHeader) | ||||
| 		box-shadow 0 1px rgba(#000, 0.07) | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> button | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| import Vue from 'vue'; | ||||
| import { Line } from 'vue-chartjs'; | ||||
| import * as mergeOptions from 'merge-options'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	extends: Line, | ||||
| 	props: { | ||||
| 		data: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		opts: { | ||||
| 			required: false | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		data() { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			this.renderChart(this.data, mergeOptions({ | ||||
| 				responsive: true, | ||||
| 				maintainAspectRatio: false, | ||||
| 				scales: { | ||||
| 					xAxes: [{ | ||||
| 						type: 'time', | ||||
| 						distribution: 'series' | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					intersect: false, | ||||
| 					mode: 'index', | ||||
| 					position: 'nearest' | ||||
| 				} | ||||
| 			}, this.opts || {})); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| @@ -1,723 +0,0 @@ | ||||
| <template> | ||||
| <div class="gkgckalzgidaygcxnugepioremxvxvpt"> | ||||
| 	<header> | ||||
| 		<b>%i18n:@title%:</b> | ||||
| 		<select v-model="chartType"> | ||||
| 			<optgroup label="%i18n:@federation%"> | ||||
| 				<option value="federation-instances">%i18n:@charts.federation-instances%</option> | ||||
| 				<option value="federation-instances-total">%i18n:@charts.federation-instances-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@users%"> | ||||
| 				<option value="users">%i18n:@charts.users%</option> | ||||
| 				<option value="users-total">%i18n:@charts.users-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@notes%"> | ||||
| 				<option value="notes">%i18n:@charts.notes%</option> | ||||
| 				<option value="local-notes">%i18n:@charts.local-notes%</option> | ||||
| 				<option value="remote-notes">%i18n:@charts.remote-notes%</option> | ||||
| 				<option value="notes-total">%i18n:@charts.notes-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@drive%"> | ||||
| 				<option value="drive-files">%i18n:@charts.drive-files%</option> | ||||
| 				<option value="drive-files-total">%i18n:@charts.drive-files-total%</option> | ||||
| 				<option value="drive">%i18n:@charts.drive%</option> | ||||
| 				<option value="drive-total">%i18n:@charts.drive-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@network%"> | ||||
| 				<option value="network-requests">%i18n:@charts.network-requests%</option> | ||||
| 				<option value="network-time">%i18n:@charts.network-time%</option> | ||||
| 				<option value="network-usage">%i18n:@charts.network-usage%</option> | ||||
| 			</optgroup> | ||||
| 		</select> | ||||
| 		<div> | ||||
| 			<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span> | ||||
| 		</div> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<x-chart v-if="chart" :data="data[0]" :opts="data[1]"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XChart from './charts.chart.ts'; | ||||
|  | ||||
| const colors = { | ||||
| 	local: 'rgb(246, 88, 79)', | ||||
| 	remote: 'rgb(65, 221, 222)', | ||||
|  | ||||
| 	localPlus: 'rgb(52, 178, 118)', | ||||
| 	remotePlus: 'rgb(158, 255, 209)', | ||||
| 	localMinus: 'rgb(255, 97, 74)', | ||||
| 	remoteMinus: 'rgb(255, 149, 134)', | ||||
|  | ||||
| 	incoming: 'rgb(52, 178, 118)', | ||||
| 	outgoing: 'rgb(255, 97, 74)', | ||||
| }; | ||||
|  | ||||
| const rgba = (color: string): string => { | ||||
| 	return color.replace('rgb', 'rgba').replace(')', ', 0.1)'); | ||||
| }; | ||||
|  | ||||
| const limit = 35; | ||||
|  | ||||
| const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); | ||||
| const negate = arr => arr.map(x => -x); | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			now: null, | ||||
| 			chart: null, | ||||
| 			chartType: 'notes', | ||||
| 			span: 'hour' | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		data(): any { | ||||
| 			if (this.chart == null) return null; | ||||
| 			switch (this.chartType) { | ||||
| 				case 'federation-instances': return this.federationInstancesChart(false); | ||||
| 				case 'federation-instances-total': return this.federationInstancesChart(true); | ||||
| 				case 'users': return this.usersChart(false); | ||||
| 				case 'users-total': return this.usersChart(true); | ||||
| 				case 'notes': return this.notesChart('combined'); | ||||
| 				case 'local-notes': return this.notesChart('local'); | ||||
| 				case 'remote-notes': return this.notesChart('remote'); | ||||
| 				case 'notes-total': return this.notesTotalChart(); | ||||
| 				case 'drive': return this.driveChart(); | ||||
| 				case 'drive-total': return this.driveTotalChart(); | ||||
| 				case 'drive-files': return this.driveFilesChart(); | ||||
| 				case 'drive-files-total': return this.driveFilesTotalChart(); | ||||
| 				case 'network-requests': return this.networkRequestsChart(); | ||||
| 				case 'network-time': return this.networkTimeChart(); | ||||
| 				case 'network-usage': return this.networkUsageChart(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		stats(): any[] { | ||||
| 			const stats = | ||||
| 				this.span == 'day' ? this.chart.perDay : | ||||
| 				this.span == 'hour' ? this.chart.perHour : | ||||
| 				null; | ||||
|  | ||||
| 			return stats; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	async created() { | ||||
| 		this.now = new Date(); | ||||
|  | ||||
| 		const [perHour, perDay] = await Promise.all([Promise.all([ | ||||
| 			(this as any).api('charts/federation', { limit: limit, span: 'hour' }), | ||||
| 			(this as any).api('charts/users', { limit: limit, span: 'hour' }), | ||||
| 			(this as any).api('charts/notes', { limit: limit, span: 'hour' }), | ||||
| 			(this as any).api('charts/drive', { limit: limit, span: 'hour' }), | ||||
| 			(this as any).api('charts/network', { limit: limit, span: 'hour' }) | ||||
| 		]), Promise.all([ | ||||
| 			(this as any).api('charts/federation', { limit: limit, span: 'day' }), | ||||
| 			(this as any).api('charts/users', { limit: limit, span: 'day' }), | ||||
| 			(this as any).api('charts/notes', { limit: limit, span: 'day' }), | ||||
| 			(this as any).api('charts/drive', { limit: limit, span: 'day' }), | ||||
| 			(this as any).api('charts/network', { limit: limit, span: 'day' }) | ||||
| 		])]); | ||||
|  | ||||
| 		const chart = { | ||||
| 			perHour: { | ||||
| 				federation: perHour[0], | ||||
| 				users: perHour[1], | ||||
| 				notes: perHour[2], | ||||
| 				drive: perHour[3], | ||||
| 				network: perHour[4] | ||||
| 			}, | ||||
| 			perDay: { | ||||
| 				federation: perDay[0], | ||||
| 				users: perDay[1], | ||||
| 				notes: perDay[2], | ||||
| 				drive: perDay[3], | ||||
| 				network: perDay[4] | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		this.chart = chart; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		getDate(i: number) { | ||||
| 			const y = this.now.getFullYear(); | ||||
| 			const m = this.now.getMonth(); | ||||
| 			const d = this.now.getDate(); | ||||
| 			const h = this.now.getHours(); | ||||
|  | ||||
| 			return ( | ||||
| 				this.span == 'day' ? new Date(y, m, d - i) : | ||||
| 				this.span == 'hour' ? new Date(y, m, d, h - i) : | ||||
| 				null | ||||
| 			); | ||||
| 		}, | ||||
|  | ||||
| 		format(arr) { | ||||
| 			return arr.map((v, i) => ({ t: this.getDate(i).getTime(), y: v })); | ||||
| 		}, | ||||
|  | ||||
| 		federationInstancesChart(total: boolean): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Instances', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(total | ||||
| 						? this.stats.federation.instance.total | ||||
| 						: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))) | ||||
| 				}] | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		notesChart(type: string): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(type == 'combined' | ||||
| 						? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec)) | ||||
| 						: sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec)) | ||||
| 					) | ||||
| 				}, { | ||||
| 					label: 'Renotes', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(161, 222, 65, 0.1)', | ||||
| 					borderColor: '#a1de41', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(type == 'combined' | ||||
| 						? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote) | ||||
| 						: this.stats.notes[type].diffs.renote | ||||
| 					) | ||||
| 				}, { | ||||
| 					label: 'Replies', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(247, 121, 108, 0.1)', | ||||
| 					borderColor: '#f7796c', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(type == 'combined' | ||||
| 						? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply) | ||||
| 						: this.stats.notes[type].diffs.reply | ||||
| 					) | ||||
| 				}, { | ||||
| 					label: 'Normal', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(65, 221, 222, 0.1)', | ||||
| 					borderColor: '#41ddde', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(type == 'combined' | ||||
| 						? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal) | ||||
| 						: this.stats.notes[type].diffs.normal | ||||
| 					) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		notesTotalChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total)) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.notes.local.total) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.notes.remote.total) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		usersChart(total: boolean): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(total | ||||
| 						? sum(this.stats.users.local.total, this.stats.users.remote.total) | ||||
| 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||
| 					) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(total | ||||
| 						? this.stats.users.local.total | ||||
| 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec)) | ||||
| 					) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(total | ||||
| 						? this.stats.users.remote.total | ||||
| 						: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||
| 					) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(sum(this.stats.drive.local.incSize, negate(this.stats.drive.local.decSize), this.stats.drive.remote.incSize, negate(this.stats.drive.remote.decSize))) | ||||
| 				}, { | ||||
| 					label: 'Local +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.local.incSize) | ||||
| 				}, { | ||||
| 					label: 'Local -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localMinus), | ||||
| 					borderColor: colors.localMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(negate(this.stats.drive.local.decSize)) | ||||
| 				}, { | ||||
| 					label: 'Remote +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remotePlus), | ||||
| 					borderColor: colors.remotePlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.remote.incSize) | ||||
| 				}, { | ||||
| 					label: 'Remote -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remoteMinus), | ||||
| 					borderColor: colors.remoteMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(negate(this.stats.drive.remote.decSize)) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('bytes')(value, 1); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveTotalChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize)) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.local.totalSize) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.remote.totalSize) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('bytes')(value, 1); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveFilesChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(sum(this.stats.drive.local.incCount, negate(this.stats.drive.local.decCount), this.stats.drive.remote.incCount, negate(this.stats.drive.remote.decCount))) | ||||
| 				}, { | ||||
| 					label: 'Local +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.local.incCount) | ||||
| 				}, { | ||||
| 					label: 'Local -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localMinus), | ||||
| 					borderColor: colors.localMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(negate(this.stats.drive.local.decCount)) | ||||
| 				}, { | ||||
| 					label: 'Remote +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remotePlus), | ||||
| 					borderColor: colors.remotePlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.remote.incCount) | ||||
| 				}, { | ||||
| 					label: 'Remote -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remoteMinus), | ||||
| 					borderColor: colors.remoteMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(negate(this.stats.drive.remote.decCount)) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveFilesTotalChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount)) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.local.totalCount) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.drive.remote.totalCount) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		networkRequestsChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Incoming', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.network.incomingRequests) | ||||
| 				}] | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		networkTimeChart(): any { | ||||
| 			const data = []; | ||||
|  | ||||
| 			for (let i = 0; i < limit; i++) { | ||||
| 				data.push(this.stats.network.incomingRequests[i] != 0 ? (this.stats.network.totalTime[i] / this.stats.network.incomingRequests[i]) : 0); | ||||
| 			} | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Avg time (ms)', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(data) | ||||
| 				}] | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		networkUsageChart(): any { | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Incoming', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.incoming), | ||||
| 					borderColor: colors.incoming, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.network.incomingBytes) | ||||
| 				}, { | ||||
| 					label: 'Outgoing', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.outgoing), | ||||
| 					borderColor: colors.outgoing, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: this.format(this.stats.network.outgoingBytes) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('bytes')(value, 1); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .gkgckalzgidaygcxnugepioremxvxvpt | ||||
| 	padding 32px | ||||
| 	background #fff | ||||
| 	box-shadow 0 2px 8px rgba(#000, 0.1) | ||||
|  | ||||
| 	* | ||||
| 		user-select none | ||||
|  | ||||
| 	> header | ||||
| 		display flex | ||||
| 		margin 0 0 1em 0 | ||||
| 		padding 0 0 8px 0 | ||||
| 		font-size 1em | ||||
| 		color #555 | ||||
| 		border-bottom solid 1px #eee | ||||
|  | ||||
| 		> b | ||||
| 			margin-right 8px | ||||
|  | ||||
| 		> *:last-child | ||||
| 			margin-left auto | ||||
|  | ||||
| 			* | ||||
| 				&:not(.active) | ||||
| 					color var(--primary) | ||||
| 					cursor pointer | ||||
|  | ||||
| 	> div | ||||
| 		> * | ||||
| 			display block | ||||
| 			height 350px | ||||
|  | ||||
| </style> | ||||
| @@ -13,7 +13,7 @@ | ||||
| 		@change-selection="onChangeSelection" | ||||
| 	/> | ||||
| 	<div :class="$style.footer"> | ||||
| 		<button :class="$style.upload" title="%i18n:@upload%" @click="upload">%fa:upload%</button> | ||||
| 		<button :class="$style.upload" title="%i18n:@upload%" @click="upload"><fa icon="upload"/></button> | ||||
| 		<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button> | ||||
| 		<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">%i18n:@ok%</button> | ||||
| 	</div> | ||||
| @@ -28,7 +28,7 @@ export default Vue.extend({ | ||||
| 			default: false | ||||
| 		}, | ||||
| 		title: { | ||||
| 			default: '%fa:R file%%i18n:@choose-prompt%' | ||||
| 			default: '<fa :icon="['far', 'file']"/>%i18n:@choose-prompt%' | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| @@ -62,7 +62,7 @@ export default Vue.extend({ | ||||
|  | ||||
|  | ||||
| .title | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| .count | ||||
|   | ||||
| @@ -21,7 +21,7 @@ import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		title: { | ||||
| 			default: '%fa:R folder%%i18n:@choose-prompt%' | ||||
| 			default: '<fa :icon="['far', 'folder']"/>%i18n:@choose-prompt%' | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| @@ -40,7 +40,7 @@ export default Vue.extend({ | ||||
|  | ||||
|  | ||||
| .title | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| .browser | ||||
|   | ||||
| @@ -3,13 +3,13 @@ | ||||
| 	<li v-for="(item, i) in menu" :class="item ? item.type : item === null ? 'divider' : null"> | ||||
| 		<template v-if="item"> | ||||
| 			<template v-if="item.type == null || item.type == 'item'"> | ||||
| 				<p @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</p> | ||||
| 				<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p> | ||||
| 			</template> | ||||
| 			<template v-else-if="item.type == 'link'"> | ||||
| 				<a :href="item.href" :target="item.target" @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</a> | ||||
| 				<a :href="item.href" :target="item.target" @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a> | ||||
| 			</template> | ||||
| 			<template v-else-if="item.type == 'nest'"> | ||||
| 				<p><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}...<span class="caret">%fa:caret-right%</span></p> | ||||
| 				<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p> | ||||
| 				<me-nu :menu="item.menu" @x="click"/> | ||||
| 			</template> | ||||
| 		</template> | ||||
| @@ -113,9 +113,9 @@ export default Vue.extend({ | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .icon | ||||
| 	> * | ||||
| 		width 28px | ||||
| 		margin-left -28px | ||||
| 		text-align center | ||||
| 	display inline-block | ||||
| 	width 28px | ||||
| 	margin-left -28px | ||||
| 	text-align center | ||||
| </style> | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| 	<mk-window ref="window" is-modal width="800px" :can-close="false"> | ||||
| 		<span slot="header">%fa:crop%{{ title }}</span> | ||||
| 		<span slot="header"><fa icon="crop"/>{{ title }}</span> | ||||
| 		<div class="body"> | ||||
| 			<vue-cropper ref="cropper" | ||||
| 				:src="image.url" | ||||
| @@ -64,7 +64,7 @@ export default Vue.extend({ | ||||
|  | ||||
|  | ||||
| .header | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| .img | ||||
|   | ||||
| @@ -91,8 +91,6 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
|  | ||||
|  | ||||
| .mk-dialog | ||||
| 	> .bg | ||||
| 		display block | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <mk-window ref="window" @closed="destroyDom" width="800px" height="500px" :popout-url="popout"> | ||||
| 	<template slot="header"> | ||||
| 		<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p> | ||||
| 		<span :class="$style.title">%fa:cloud%%i18n:common.drive%</span> | ||||
| 		<span :class="$style.title"><fa icon="cloud"/>%i18n:common.drive%</span> | ||||
| 	</template> | ||||
| 	<mk-drive :class="$style.browser" multiple :init-folder="folder" ref="browser"/> | ||||
| </mk-window> | ||||
| @@ -39,7 +39,7 @@ export default Vue.extend({ | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .title | ||||
| 	> [data-fa] | ||||
| 	> [data-icon] | ||||
| 		margin-right 4px | ||||
|  | ||||
| .info | ||||
|   | ||||
| @@ -71,27 +71,27 @@ export default Vue.extend({ | ||||
| 			contextmenu((this as any).os)(e, [{ | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.rename%', | ||||
| 				icon: '%fa:i-cursor%', | ||||
| 				icon: 'i-cursor', | ||||
| 				action: this.rename | ||||
| 			}, { | ||||
| 				type: 'item', | ||||
| 				text: this.file.isSensitive ? '%i18n:@contextmenu.unmark-as-sensitive%' : '%i18n:@contextmenu.mark-as-sensitive%', | ||||
| 				icon: this.file.isSensitive ? '%fa:R eye%' : '%fa:R eye-slash%', | ||||
| 				icon: this.file.isSensitive ? ['far', 'eye'] : ['far', 'eye-slash'], | ||||
| 				action: this.toggleSensitive | ||||
| 			}, null, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.copy-url%', | ||||
| 				icon: '%fa:link%', | ||||
| 				icon: 'link', | ||||
| 				action: this.copyUrl | ||||
| 			}, { | ||||
| 				type: 'link', | ||||
| 				href: `${this.file.url}?download`, | ||||
| 				text: '%i18n:@contextmenu.download%', | ||||
| 				icon: '%fa:download%', | ||||
| 				icon: 'download', | ||||
| 			}, null, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:common.delete%', | ||||
| 				icon: '%fa:R trash-alt%', | ||||
| 				icon: ['far', 'trash-alt'], | ||||
| 				action: this.deleteFile | ||||
| 			}, null, { | ||||
| 				type: 'nest', | ||||
| @@ -170,7 +170,7 @@ export default Vue.extend({ | ||||
| 		copyUrl() { | ||||
| 			copyToClipboard(this.file.url); | ||||
| 			(this as any).apis.dialog({ | ||||
| 				title: '%fa:check%%i18n:@contextmenu.copied%', | ||||
| 				title: '<fa icon="check"/>%i18n:@contextmenu.copied%', | ||||
| 				text: '%i18n:@contextmenu.copied-url-to-clipboard%', | ||||
| 				actions: [{ | ||||
| 					text: '%i18n:common.ok%' | ||||
|   | ||||
| @@ -16,8 +16,8 @@ | ||||
| 	:title="title" | ||||
| > | ||||
| 	<p class="name"> | ||||
| 		<template v-if="hover">%fa:R folder-open .fw%</template> | ||||
| 		<template v-if="!hover">%fa:R folder .fw%</template> | ||||
| 		<template v-if="hover"><fa :icon="['far', 'folder-open']" fixed-width/></template> | ||||
| 		<template v-if="!hover"><fa :icon="['far', 'folder']" fixed-width/></template> | ||||
| 		{{ folder.name }} | ||||
| 	</p> | ||||
| </div> | ||||
| @@ -55,22 +55,22 @@ export default Vue.extend({ | ||||
| 			contextmenu((this as any).os)(e, [{ | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.move-to-this-folder%', | ||||
| 				icon: '%fa:arrow-right%', | ||||
| 				icon: 'arrow-right', | ||||
| 				action: this.go | ||||
| 			}, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.show-in-new-window%', | ||||
| 				icon: '%fa:R window-restore%', | ||||
| 				icon: ['far', 'window-restore'], | ||||
| 				action: this.newWindow | ||||
| 			}, null, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.rename%', | ||||
| 				icon: '%fa:i-cursor%', | ||||
| 				icon: 'i-cursor', | ||||
| 				action: this.rename | ||||
| 			}, null, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:common.delete%', | ||||
| 				icon: '%fa:R trash-alt%', | ||||
| 				icon: ['far', 'trash-alt'], | ||||
| 				action: this.deleteFolder | ||||
| 			}], { | ||||
| 					closed: () => { | ||||
| @@ -155,7 +155,7 @@ export default Vue.extend({ | ||||
| 					switch (err) { | ||||
| 						case 'detected-circular-definition': | ||||
| 							(this as any).apis.dialog({ | ||||
| 								title: '%fa:exclamation-triangle%%i18n:@unable-to-process%', | ||||
| 								title: '<fa icon="exclamation-triangle"/>%i18n:@unable-to-process%', | ||||
| 								text: '%i18n:@circular-reference-detected%', | ||||
| 								actions: [{ | ||||
| 									text: '%i18n:common.ok%' | ||||
| @@ -255,7 +255,7 @@ export default Vue.extend({ | ||||
| 		font-size 0.9em | ||||
| 		color var(--desktopDriveFolderFg) | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
| 			margin-left 2px | ||||
| 			text-align left | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| 	@dragleave="onDragleave" | ||||
| 	@drop.stop="onDrop" | ||||
| > | ||||
| 	<template v-if="folder == null">%fa:cloud%</template> | ||||
| 	<i v-if="folder == null" class="cloud"><fa icon="cloud"/></i> | ||||
| 	<span>{{ folder == null ? '%i18n:common.drive%' : folder.name }}</span> | ||||
| </div> | ||||
| </template> | ||||
| @@ -110,7 +110,7 @@ export default Vue.extend({ | ||||
| 	&[data-draghover] | ||||
| 		background #eee | ||||
|  | ||||
| 	[data-fa].cloud | ||||
| 	i.cloud | ||||
| 		margin-right 4px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
| 		<div class="path" @contextmenu.prevent.stop="() => {}"> | ||||
| 			<x-nav-folder :class="{ current: folder == null }"/> | ||||
| 			<template v-for="folder in hierarchyFolders"> | ||||
| 				<span class="separator">%fa:angle-right%</span> | ||||
| 				<span class="separator"><fa icon="angle-right"/></span> | ||||
| 				<x-nav-folder :folder="folder" :key="folder.id"/> | ||||
| 			</template> | ||||
| 			<span class="separator" v-if="folder != null">%fa:angle-right%</span> | ||||
| 			<span class="separator" v-if="folder != null"><fa icon="angle-right"/></span> | ||||
| 			<span class="folder current" v-if="folder != null">{{ folder.name }}</span> | ||||
| 		</div> | ||||
| 		<!-- | ||||
| @@ -138,17 +138,17 @@ export default Vue.extend({ | ||||
| 			contextmenu((this as any).os)(e, [{ | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.create-folder%', | ||||
| 				icon: '%fa:R folder%', | ||||
| 				icon: ['far', 'folder'], | ||||
| 				action: this.createFolder | ||||
| 			}, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.upload%', | ||||
| 				icon: '%fa:upload%', | ||||
| 				icon: 'upload', | ||||
| 				action: this.selectLocalFile | ||||
| 			}, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:@contextmenu.url-upload%', | ||||
| 				icon: '%fa:cloud-upload-alt%', | ||||
| 				icon: 'cloud-upload-alt', | ||||
| 				action: this.urlUpload | ||||
| 			}]); | ||||
| 		}, | ||||
| @@ -313,7 +313,7 @@ export default Vue.extend({ | ||||
| 					switch (err) { | ||||
| 						case 'detected-circular-definition': | ||||
| 							(this as any).apis.dialog({ | ||||
| 								title: '%fa:exclamation-triangle%%i18n:@unable-to-process%', | ||||
| 								title: '<fa icon="exclamation-triangle"/>%i18n:@unable-to-process%', | ||||
| 								text: '%i18n:@circular-reference-detected%', | ||||
| 								actions: [{ | ||||
| 									text: '%i18n:common.ok%' | ||||
| @@ -343,7 +343,7 @@ export default Vue.extend({ | ||||
| 				}); | ||||
|  | ||||
| 				(this as any).apis.dialog({ | ||||
| 					title: '%fa:check%%i18n:@url-upload-requested%', | ||||
| 					title: '<fa icon="check"/>%i18n:@url-upload-requested%', | ||||
| 					text: '%i18n:@may-take-time%', | ||||
| 					actions: [{ | ||||
| 						text: '%i18n:common.ok%' | ||||
| @@ -613,9 +613,6 @@ export default Vue.extend({ | ||||
| 				line-height 38px | ||||
| 				cursor pointer | ||||
|  | ||||
| 				i | ||||
| 					margin-right 4px | ||||
|  | ||||
| 				* | ||||
| 					pointer-events none | ||||
|  | ||||
| @@ -635,7 +632,7 @@ export default Vue.extend({ | ||||
| 					opacity 0.5 | ||||
| 					cursor default | ||||
|  | ||||
| 					> [data-fa] | ||||
| 					> [data-icon] | ||||
| 						margin 0 | ||||
|  | ||||
| 		> .search | ||||
|   | ||||
| @@ -5,13 +5,13 @@ | ||||
| 	:disabled="wait" | ||||
| > | ||||
| 	<template v-if="!wait"> | ||||
| 		<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked">%fa:hourglass-half%<template v-if="size == 'big'"> %i18n:@request-pending%</template></template> | ||||
| 		<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked">%fa:hourglass-start%<template v-if="size == 'big'"> %i18n:@follow-processing%</template></template> | ||||
| 		<template v-else-if="u.isFollowing">%fa:minus%<template v-if="size == 'big'"> %i18n:@following%</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow-request%</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow%</template></template> | ||||
| 		<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked"><fa icon="hourglass-half"/><template v-if="size == 'big'"> %i18n:@request-pending%</template></template> | ||||
| 		<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked"><fa icon="hourglass-start"/><template v-if="size == 'big'"> %i18n:@follow-processing%</template></template> | ||||
| 		<template v-else-if="u.isFollowing"><fa icon="minus"/><template v-if="size == 'big'"> %i18n:@following%</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> %i18n:@follow-request%</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && !u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> %i18n:@follow%</template></template> | ||||
| 	</template> | ||||
| 	<template v-else>%fa:spinner .pulse .fw%</template> | ||||
| 	<template v-else><fa icon="spinner .pulse" fixed-width/></template> | ||||
| </button> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -11,9 +11,9 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:@fetching%<mk-ellipsis/></p> | ||||
| 	<a class="refresh" @click="refresh">%i18n:@refresh%</a> | ||||
| 	<button class="close" @click="destroyDom()" title="%i18n:@close%">%fa:times%</button> | ||||
| 	<button class="close" @click="destroyDom()" title="%i18n:@close%"><fa icon="times"/></button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -124,7 +124,7 @@ export default Vue.extend({ | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> .refresh | ||||
| @@ -155,7 +155,7 @@ export default Vue.extend({ | ||||
| 		&:active | ||||
| 			color #222 | ||||
|  | ||||
| 		> [data-fa] | ||||
| 		> [data-icon] | ||||
| 			padding 14px | ||||
|  | ||||
| </style> | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user