Compare commits

...

87 Commits

Author SHA1 Message Date
938fcb3e5e Merge branch 'develop' 2021-04-18 00:07:33 +09:00
3553f3be4e 12.77.0 2021-04-18 00:07:20 +09:00
8c4662e6e5 Improve tooltip behaviour 2021-04-17 23:53:05 +09:00
e7610b2467 display online status 2021-04-17 23:52:54 +09:00
79e60afd42 Update Dockerfile 2021-04-17 18:37:10 +09:00
3da93e7bf9 Create .yarnrc 2021-04-17 17:35:44 +09:00
1700154f00 Update package.json 2021-04-17 15:34:19 +09:00
68571d8f57 Implement user online status
Resolve #7422
Fix #7424
2021-04-17 15:30:26 +09:00
61461b7f59 Improve client 2021-04-17 14:06:32 +09:00
c27c3817a9 Tweak UI 2021-04-17 11:40:47 +09:00
08e1db45a9 Improve client 2021-04-17 11:29:44 +09:00
27c373ddf4 Tweak UI 2021-04-17 00:12:50 +09:00
e62d7bc1ba Tweak UI 2021-04-16 23:26:01 +09:00
0d680b060e Tweak UI 2021-04-16 23:19:39 +09:00
0f054aed88 Tweak UI 2021-04-16 23:04:25 +09:00
b4308ecb91 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2021-04-16 21:47:15 +09:00
e68ff28649 Improve client 2021-04-16 21:47:12 +09:00
cb946772b4 Update package.json 2021-04-16 18:34:56 +09:00
f169585a55 Tweak style 2021-04-16 17:52:45 +09:00
e5fbc68e0e 詳細ユーザー情報ページなど 2021-04-16 17:34:06 +09:00
f32cad2667 Tweak UI 2021-04-16 15:44:17 +09:00
503f23ad3b Tweak UI 2021-04-16 12:17:22 +09:00
481b46ac9e Tweak UI 2021-04-16 12:06:54 +09:00
9c34ef7d74 Improve UI 2021-04-16 09:41:56 +09:00
18fbaee9df fix 2021-04-16 09:05:32 +09:00
94b59e8b6f update commander 2021-04-16 09:03:17 +09:00
e9c5cd543e update dependencies 2021-04-16 08:51:38 +09:00
5e1d17dff2 Merge branch 'develop' 2021-04-16 00:21:56 +09:00
526838c77c 12.76.1 2021-04-16 00:21:46 +09:00
ef6b370d0e Improve usability 2021-04-16 00:13:20 +09:00
38896205c8 fix bug 2021-04-15 23:49:05 +09:00
7221684337 Improve usability 2021-04-15 23:38:34 +09:00
99e936bf2b Tweak style 2021-04-15 23:34:12 +09:00
138fee9b52 fix theme 2021-04-15 23:27:33 +09:00
a19c1535e8 タイトルバー非表示オプション削除 2021-04-15 23:27:28 +09:00
05ba1d0fd4 fix style 2021-04-15 20:55:54 +09:00
c2b3436770 better list rendering 2021-04-15 20:26:02 +09:00
a0fd3aef98 update mfm-js 2021-04-15 20:25:32 +09:00
b112341d91 🎨 2021-04-15 18:02:55 +09:00
0dba5607a8 refactor 2021-04-15 17:36:09 +09:00
7378c4a9d8 fix #7444 2021-04-15 12:37:58 +09:00
89e7ef36dc Update yarn.lock 2021-04-15 12:37:32 +09:00
39ed02bc53 apply hotfix of MFM parser (#7446) 2021-04-15 12:10:16 +09:00
449dc17df8 Merge branch 'develop' 2021-04-14 16:39:53 +09:00
5cb3d86a1b 12.76.0 2021-04-14 16:39:38 +09:00
a3687dd653 theme description 2021-04-14 16:24:07 +09:00
fbc5b8ceb7 fix bug 2021-04-14 15:49:48 +09:00
4c545fbba5 refactor 2021-04-14 15:35:04 +09:00
7fda726bbe Tweak UI 2021-04-14 14:12:29 +09:00
e404e5e2de Tweak UI 2021-04-14 03:34:56 +09:00
930127348a Tweak UI 2021-04-14 03:23:29 +09:00
3db37e1d52 Tweak UI 2021-04-14 02:12:46 +09:00
b9c4a582ac Tweak UI 2021-04-14 01:41:49 +09:00
a085d9fbd7 Revert "Improve performance" (#7441)
This reverts commit 1dbce5e3e2.
2021-04-13 16:00:40 +09:00
7f2356ba4b Tweak UI 2021-04-13 12:43:19 +09:00
7a7f668879 fix bug 2021-04-13 01:59:18 +09:00
69a05aa5de Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2021-04-12 23:14:00 +09:00
c0c64a2d5d Tweak UI 2021-04-12 23:13:58 +09:00
3c3b4eb2af Fix undefined user page title (#7436) 2021-04-12 23:07:12 +09:00
1019e0bcfd Add prop validations in MkAcct (#7439) 2021-04-12 23:04:10 +09:00
faf2c007aa fix bug 2021-04-12 22:07:32 +09:00
43678ac801 Tweak UI 2021-04-12 14:16:30 +09:00
f22d5aa46d Tweak UI 2021-04-12 13:20:04 +09:00
d599c1e740 Tweak UI 2021-04-12 13:06:00 +09:00
0cc4519fc0 Tweak UI 2021-04-12 00:14:27 +09:00
0265c85ba3 full view 2021-04-12 00:05:13 +09:00
7ec0670748 Tweak UI 2021-04-11 23:49:13 +09:00
d06517265f Tweak UI 2021-04-11 21:31:38 +09:00
a88e486468 Tweak UI 2021-04-11 21:09:35 +09:00
c22ff4c556 Resolve #7425 2021-04-11 12:42:48 +09:00
d4c4e30fe4 Tweak UI 2021-04-11 12:31:24 +09:00
1ec3338d2e update mfm.js (#7435)
* use mfm.js 0.14.0

* use mfm.extract

* use mfm.extract

* use mfm.extract
2021-04-11 00:18:29 +09:00
bffcfd92da Tweak UI 2021-04-11 00:03:31 +09:00
f44517f4af Tweak UI 2021-04-10 23:52:45 +09:00
5087a5af21 Tweak UI 2021-04-10 18:17:42 +09:00
3a6331693a refactor mfm extract (#7434)
* refactor extractCustomEmojisFromMfm()

* refactor extract-hashtags

* refactor extract-mentions

* refactor extract-hashtags

* refactor extract-url-from-mfm

* refactor extract-mentions
2021-04-10 17:50:18 +09:00
56a28923ca Tweak UI 2021-04-10 14:37:29 +09:00
fb8ed718ce Improve UI 2021-04-10 13:38:24 +09:00
45e5d89353 refactor(client): Use symbol 2021-04-10 12:54:12 +09:00
d3fe02fb3e Default UI redesign (#7429)
* wip

* wip

* wip

* wip

* Update default.sidebar.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update sticky-sidebar.ts

* wip

* wip

* Update messaging-room.form.vue

* Update timeline.vue
2021-04-10 12:40:50 +09:00
7d2126e2b2 Fix punycode deprecation warning (#7426) 2021-04-04 13:00:39 +09:00
1f4ae2f63a Use mfm-js for MFM parsing (#7415)
* wip

* Update mfm.ts

* wip

* update mfmjs

* refactor

* nanka

* Update mfm.ts

* Update to-html.ts

* Update to-html.ts

* wip

* fix test

* fix test
2021-04-02 10:36:11 +09:00
b378066ebf tweak avatar generation 2021-04-02 10:00:05 +09:00
2210d1053a Update vue 2021-03-27 11:08:15 +09:00
bb6cd258f3 syuilo/misskey --> misskey-dev/misskey 2021-03-24 12:23:05 +09:00
b5adb02eb8 Update 01_bug-report.md 2021-03-24 12:01:37 +09:00
8b702bcfa0 Update README.md 2021-03-24 11:57:12 +09:00
309 changed files with 5690 additions and 4178 deletions

View File

@ -7,6 +7,11 @@ assignees: ''
---
<!--
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
-->
## 💡 Summary
<!-- Tell us what the bug is -->

1
.yarnrc Normal file
View File

@ -0,0 +1 @@
network-timeout 600000

View File

@ -1 +1 @@
see [releases](https://github.com/syuilo/misskey/releases)
see [releases](https://github.com/misskey-dev/misskey/releases)

View File

@ -8,7 +8,7 @@
- 温度感高めで見てほしいものは責付いてください。
## Issues
Feature suggestions and bug reports are filed in https://github.com/syuilo/misskey/issues .
Feature suggestions and bug reports are filed in https://github.com/misskey-dev/misskey/issues .
* Please search existing issues to avoid duplication. If your issue is already filed, please add your reaction or comment to the existing one.
* If you have multiple independent issues, please submit them separately.

View File

@ -22,7 +22,7 @@ RUN apk add --no-cache \
vips-dev \
vips
COPY package.json yarn.lock ./
COPY package.json yarn.lock .yarnrc ./
RUN yarn install
COPY . ./
RUN yarn build

View File

@ -4,8 +4,8 @@
<div align="center">
[![CircleCI](https://img.shields.io/circleci/project/github/syuilo/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/syuilo/misskey)
[![Dependencies](https://img.shields.io/david/syuilo/misskey.svg?style=for-the-badge&logo=npm)](https://david-dm.org/syuilo/misskey)
[![CircleCI](https://img.shields.io/circleci/project/github/misskey-dev/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/misskey-dev/misskey)
[![Dependencies](https://img.shields.io/david/misskey-dev/misskey.svg?style=for-the-badge&logo=npm)](https://david-dm.org/misskey-dev/misskey)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=github)](http://makeapullrequest.com)
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
@ -22,11 +22,16 @@ Why don't you take a short break from the hustle and bustle of the city, and div
---
Do you have a question? Or are you experiencing trouble?
Visit [our forum](https://forum.misskey.io/)!
---
![](https://ja.mstdn.wiki/images/e/ed/Deck.jpg)
:sparkles: Features
----------------------------------------------------------------
<a href="https://xn--931a.moe/"><img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/></a>
<a href="https://xn--931a.moe/"><img src="https://github.com/misskey-dev/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/></a>
<h3>Posting</h3>
<p>

View File

@ -12,13 +12,13 @@ This guide describes how to install and setup Misskey with Docker.
----------------------------------------------------------------
1. Clone Misskey repository's master branch.
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
2. Move to misskey directory.
`cd misskey`
3. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag.
3. Checkout to the [latest release](https://github.com/misskey-dev/misskey/releases/latest) tag.
`git checkout master`

View File

@ -13,13 +13,13 @@ Ce guide explique comment installer et configurer Misskey avec Docker.
----------------------------------------------------------------
1. Clone le dépôt de Misskey sur la branche master.
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
2. Naviguez dans le dossier du dépôt.
`cd misskey`
3. Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest).
3. Checkout sur le tag de la [dernière version](https://github.com/misskey-dev/misskey/releases/latest).
`git checkout master`

View File

@ -12,13 +12,13 @@ Dockerを使ったMisskey構築方法
----------------------------------------------------------------
1. masterブランチからMisskeyレポジトリをクローン
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
2. misskeyディレクトリに移動
`cd misskey`
3. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
3. [最新のリリース](https://github.com/misskey-dev/misskey/releases/latest)を確認
`git checkout master`

View File

@ -12,13 +12,13 @@ Docker 部署指南
----------------------------------------------------------------
1. 克隆 Misskey 项目的 master 分支。
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
2. 进入 misskey 文件夹。
`cd misskey`
3. 检查 [最新发布版](https://github.com/syuilo/misskey/releases/latest) 标签。
3. 检查 [最新发布版](https://github.com/misskey-dev/misskey/releases/latest) 标签。
`git checkout master`

View File

@ -40,13 +40,13 @@ Please install and setup these softwares:
2. Clone the misskey repo from master branch.
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
3. Navigate to misskey directory
`cd misskey`
4. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
4. Checkout to the [latest release](https://github.com/misskey-dev/misskey/releases/latest)
`git checkout master`

View File

@ -41,13 +41,13 @@ Installez les paquets suivants :
2. Clonez la branche master du dépôt misskey.
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
3. Accédez au dossier misskey.
`cd misskey`
4. Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
4. Checkout sur le tag de la [version la plus récente](https://github.com/misskey-dev/misskey/releases/latest)
`git checkout master`

View File

@ -42,13 +42,13 @@ adduser --disabled-password --disabled-login misskey
2. masterブランチからMisskeyレポジトリをクローン
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
3. misskeyディレクトリに移動
`cd misskey`
4. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
4. [最新のリリース](https://github.com/misskey-dev/misskey/releases/latest)を確認
`git checkout master`

View File

@ -40,13 +40,13 @@ adduser --disabled-password --disabled-login misskey
2. 克隆 Misskey 项目的 master 分支。
`git clone -b master git://github.com/syuilo/misskey.git`
`git clone -b master git://github.com/misskey-dev/misskey.git`
3. 进入 misskey 文件夹。
`cd misskey`
4. 检查 [最新发布版](https://github.com/syuilo/misskey/releases/latest) 标签。
4. 检查 [最新发布版](https://github.com/misskey-dev/misskey/releases/latest) 标签。
`git checkout master`

View File

@ -1,6 +1,6 @@
# **DO NOT edit locale files** except `ja-JP.yml`.
When you add text to the ja-JP file (of syuilo/misskey), it will automatically be applied to other language files.
When you add text to the ja-JP file (of misskey-dev/misskey), it will automatically be applied to other language files.
Translations added in ja-JP file should contain the original Japanese strings.
Please see [Contribution guide](../CONTRIBUTING.md) for more information.

View File

@ -687,6 +687,7 @@ textColor: "文字"
saveAs: "名前を付けて保存"
advanced: "高度"
value: "値"
createdAt: "作成日時"
updatedAt: "更新日時"
saveConfirm: "保存しますか?"
deleteConfirm: "削除しますか?"
@ -710,6 +711,23 @@ typingUsers: "{users}が入力中"
jumpToSpecifiedDate: "特定の日付にジャンプ"
showingPastTimeline: "過去のタイムラインを表示しています"
clear: "クリア"
markAllAsRead: "全て既読にする"
goBack: "戻る"
unlikeConfirm: "いいね解除しますか?"
fullView: "フルビュー"
quitFullView: "フルビュー解除"
addDescription: "説明を追加"
userPagePinTip: "個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。"
notSpecifiedMentionWarning: "宛先に含まれていないメンションがあります"
info: "情報"
userInfo: "ユーザー情報"
unknown: "不明"
onlineStatus: "オンライン状態"
hideOnlineStatus: "オンライン状態を隠す"
hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。"
online: "オンライン"
active: "アクティブ"
offline: "オフライン"
_email:
_follow:
@ -878,6 +896,7 @@ _theme:
install: "テーマのインストール"
manage: "テーマの管理"
code: "テーマコード"
description: "説明"
installed: "{name}をインストールしました"
installedThemes: "インストールされたテーマ"
builtinThemes: "標準のテーマ"

View File

@ -4,8 +4,8 @@ export class AddSomeUrls1557761316509 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://github.com/syuilo/misskey'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://github.com/syuilo/misskey/issues/new'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://github.com/misskey-dev/misskey'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://github.com/misskey-dev/misskey/issues/new'`);
}
public async down(queryRunner: QueryRunner): Promise<any> {

View File

@ -0,0 +1,16 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userLastActiveDate1618637372000 implements MigrationInterface {
name = 'userLastActiveDate1618637372000'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "lastActiveDate" TIMESTAMP WITH TIME ZONE DEFAULT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_seoignmeoprigmkpodgrjmkpormg" ON "user" ("lastActiveDate") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_seoignmeoprigmkpodgrjmkpormg"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "lastActiveDate"`);
}
}

View File

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userHideOnlineStatus1618639857000 implements MigrationInterface {
name = 'userHideOnlineStatus1618639857000'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "hideOnlineStatus" boolean NOT NULL DEFAULT false`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "hideOnlineStatus"`);
}
}

View File

@ -1,11 +1,11 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.75.1",
"version": "12.77.0",
"codename": "indigo",
"repository": {
"type": "git",
"url": "https://github.com/syuilo/misskey.git"
"url": "https://github.com/misskey-dev/misskey.git"
},
"main": "./index.js",
"private": true,
@ -37,7 +37,7 @@
"lodash": "^4.17.20"
},
"dependencies": {
"@babel/plugin-transform-runtime": "7.13.10",
"@babel/plugin-transform-runtime": "7.13.15",
"@elastic/elasticsearch": "7.11.0",
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-brands-svg-icons": "5.15.3",
@ -49,7 +49,7 @@
"@koa/router": "9.0.1",
"@sentry/browser": "5.29.2",
"@sentry/tracing": "5.29.2",
"@sinonjs/fake-timers": "7.0.2",
"@sinonjs/fake-timers": "7.0.5",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.0",
@ -62,7 +62,7 @@
"@types/gulp-replace": "0.0.31",
"@types/is-url": "1.2.28",
"@types/js-yaml": "4.0.0",
"@types/jsdom": "16.2.7",
"@types/jsdom": "16.2.10",
"@types/jsonld": "1.5.5",
"@types/katex": "0.11.0",
"@types/koa": "2.13.1",
@ -77,10 +77,10 @@
"@types/koa__multer": "2.0.2",
"@types/koa__router": "8.0.4",
"@types/markdown-it": "12.0.1",
"@types/matter-js": "0.14.10",
"@types/mocha": "8.2.1",
"@types/node": "14.14.35",
"@types/node-fetch": "2.5.8",
"@types/matter-js": "0.14.11",
"@types/mocha": "8.2.2",
"@types/node": "14.14.41",
"@types/node-fetch": "2.5.10",
"@types/nodemailer": "6.4.1",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
@ -88,6 +88,7 @@
"@types/parsimmon": "1.10.6",
"@types/portscanner": "2.1.0",
"@types/pug": "2.0.4",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.4.0",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.1",
@ -96,7 +97,7 @@
"@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.0",
"@types/seedrandom": "2.4.28",
"@types/sharp": "0.27.1",
"@types/sharp": "0.28.0",
"@types/sinonjs__fake-timers": "6.0.2",
"@types/speakeasy": "2.0.5",
"@types/throttle-debounce": "2.1.0",
@ -104,39 +105,39 @@
"@types/tmp": "0.2.0",
"@types/uuid": "8.3.0",
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.26",
"@types/webpack-stream": "3.2.11",
"@types/webpack": "5.28.0",
"@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.2",
"@types/ws": "7.4.0",
"@typescript-eslint/parser": "4.18.0",
"@vue/compiler-sfc": "3.0.7",
"@types/ws": "7.4.1",
"@typescript-eslint/parser": "4.22.0",
"@vue/compiler-sfc": "3.0.11",
"abort-controller": "3.0.0",
"apexcharts": "3.26.0",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.867.0",
"aws-sdk": "2.887.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.3",
"broadcast-channel": "3.5.3",
"bull": "3.21.1",
"bull": "3.22.0",
"cafy": "15.2.1",
"cbor": "7.0.4",
"cbor": "7.0.5",
"chalk": "4.1.0",
"chart.js": "2.9.4",
"cli-highlight": "2.1.10",
"commander": "4.1.1",
"concurrently": "6.0.0",
"cli-highlight": "2.1.11",
"commander": "7.2.0",
"concurrently": "6.0.2",
"content-disposition": "0.5.3",
"core-js": "3.9.1",
"core-js": "3.10.1",
"crc-32": "1.2.0",
"css-loader": "5.1.3",
"cssnano": "4.1.10",
"css-loader": "5.2.1",
"cssnano": "5.0.1",
"dateformat": "4.5.1",
"diskusage": "1.1.3",
"escape-regexp": "0.0.1",
"eslint": "7.22.0",
"eslint-plugin-vue": "7.7.0",
"eslint": "7.24.0",
"eslint-plugin-vue": "7.9.0",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"fibers": "5.0.0",
@ -155,17 +156,17 @@
"http-proxy-agent": "4.0.1",
"http-signature": "1.3.5",
"https-proxy-agent": "5.0.0",
"idb-keyval": "5.0.4",
"idb-keyval": "5.0.5",
"insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.3.1",
"js-yaml": "4.0.0",
"jsdom": "16.5.1",
"js-yaml": "4.1.0",
"jsdom": "16.5.3",
"json5": "2.2.0",
"json5-loader": "4.0.1",
"jsonld": "4.0.1",
"jsrsasign": "8.0.20",
"katex": "0.13.0",
"katex": "0.13.2",
"koa": "2.13.1",
"koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0",
@ -177,9 +178,10 @@
"koa-views": "7.0.1",
"langmap": "0.0.16",
"lookup-dns-cache": "2.1.0",
"markdown-it": "12.0.4",
"markdown-it": "12.0.5",
"markdown-it-anchor": "7.1.0",
"matter-js": "0.16.1",
"matter-js": "0.17.1",
"mfm-js": "0.15.0",
"mocha": "8.3.2",
"moji": "0.5.1",
"ms": "2.1.3",
@ -190,24 +192,23 @@
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
"parse5": "6.0.1",
"parsimmon": "1.16.0",
"pg": "8.5.1",
"pg": "8.6.0",
"portscanner": "2.2.0",
"postcss": "8.2.8",
"postcss": "8.2.10",
"postcss-loader": "5.2.0",
"prismjs": "1.23.0",
"probe-image-size": "7.0.1",
"probe-image-size": "7.1.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "3.0.2",
"punycode": "2.1.1",
"pureimage": "0.2.7",
"pureimage": "0.3.2",
"qrcode": "1.4.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.15.9",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
"redis": "3.1.1",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"regenerator-runtime": "0.13.7",
@ -220,43 +221,44 @@
"sass": "1.32.8",
"sass-loader": "11.0.1",
"seedrandom": "3.0.5",
"sharp": "0.27.2",
"sharp": "0.28.1",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.6.7",
"systeminformation": "5.6.12",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.18",
"ts-loader": "8.1.0",
"ts-node": "9.1.1",
"tsc-alias": "1.2.8",
"tsc-alias": "1.2.9",
"tsconfig-paths": "3.9.0",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.31",
"typescript": "4.2.3",
"typeorm": "0.2.32",
"typescript": "4.2.4",
"ulid": "2.3.0",
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.0",
"vue": "3.0.7",
"vue": "3.0.11",
"vue-color": "2.8.1",
"vue-json-pretty": "1.7.1",
"vue-loader": "16.1.2",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.5",
"vue-style-loader": "4.1.3",
"vue-svg-loader": "0.17.0-beta.2",
"vuedraggable": "4.0.1",
"web-push": "3.4.4",
"webpack": "5.27.2",
"webpack-cli": "4.5.0",
"websocket": "1.0.33",
"webpack": "5.33.2",
"webpack-cli": "4.6.0",
"websocket": "1.0.34",
"ws": "7.4.4",
"xev": "2.0.1"
},

View File

@ -1,6 +1,8 @@
import * as program from 'commander';
import { Command } from 'commander';
import config from '@/config';
const program = new Command();
program
.version(config.version)
.option('--no-daemons', 'Disable daemon processes (for debbuging)')

View File

@ -8,19 +8,15 @@
</template>
</I18n>
</template>
<div class="dpvffvvy">
<div class="dpvffvvy _monolithic_">
<div class="_section">
<div class="_content">
<MkTextarea v-model:value="comment">
<span>{{ $ts.details }}</span>
<template #desc>{{ $ts.fillAbuseReportDescription }}</template>
</MkTextarea>
</div>
<MkTextarea v-model:value="comment">
<span>{{ $ts.details }}</span>
<template #desc>{{ $ts.fillAbuseReportDescription }}</template>
</MkTextarea>
</div>
<div class="_section">
<div class="_content">
<MkButton @click="send" primary full :disabled="comment.length === 0">{{ $ts.send }}</MkButton>
</div>
<MkButton @click="send" primary full :disabled="comment.length === 0">{{ $ts.send }}</MkButton>
</div>
</div>
</XWindow>
@ -80,6 +76,6 @@ export default defineComponent({
<style lang="scss" scoped>
.dpvffvvy {
--section-padding: 16px;
--root-margin: 16px;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div>
<div v-for="user in us" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
<MkAvatar :user="user" style="width:32px;height:32px;"/>
<MkAvatar :user="user" style="width:32px;height:32px;" :show-indicator="true"/>
</div>
</div>
</template>

View File

@ -123,7 +123,7 @@ export default defineComponent({
> footer {
padding: 12px 16px;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
> span {
opacity: 0.7;

View File

@ -18,17 +18,20 @@ export default defineComponent({
type: Boolean,
required: false,
default: false
}
},
noGap: {
type: Boolean,
required: false,
default: false
},
},
methods: {
focus() {
this.$slots.default[0].elm.focus();
}
},
},
render() {
const getDateText = (time: string) => {
getDateText(time: string) {
const date = new Date(time).getDate();
const month = new Date(time).getMonth() + 1;
return this.$t('monthAndDay', {
@ -36,15 +39,19 @@ export default defineComponent({
day: date.toString()
});
}
},
render() {
if (this.items.length === 0) return;
return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? {
class: 'sqadhkmv _list_',
class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
name: 'list',
tag: 'div',
'data-direction': this.direction,
'data-reversed': this.reversed ? 'true' : 'false',
} : {
class: 'sqadhkmv _list_',
class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
}, this.items.map((item, i) => {
const el = this.$slots.default({
item: item
@ -70,10 +77,10 @@ export default defineComponent({
class: 'icon',
icon: faAngleUp,
}),
getDateText(item.createdAt)
this.getDateText(item.createdAt)
]),
h('span', [
getDateText(this.items[i + 1].createdAt),
this.getDateText(this.items[i + 1].createdAt),
h(FontAwesomeIcon, {
class: 'icon',
icon: faAngleDown,
@ -117,11 +124,7 @@ export default defineComponent({
transform: translateY(-64px);
}
}
}
</style>
<style lang="scss">
.sqadhkmv {
> .separator {
text-align: center;
@ -154,5 +157,26 @@ export default defineComponent({
}
}
}
&.noGap {
> * {
margin: 0 !important;
border: none;
border-radius: 0;
box-shadow: none;
&:not(:last-child) {
border-bottom: solid 0.5px var(--divider);
}
}
}
}
._inContainer_ .sqadhkmv > * {
margin: 0 !important;
border: none;
border-bottom: solid 0.5px var(--divider);
border-radius: 0;
box-shadow: none;
}
</style>

View File

@ -26,7 +26,7 @@ import {
faFileArchive,
faFilm
} from '@fortawesome/free-solid-svg-icons';
import ImgWithBlurhash from './img-with-blurhash.vue';
import ImgWithBlurhash from '@client/components/img-with-blurhash.vue';
import { ColdDeviceStorage } from '@client/store';
export default defineComponent({

View File

@ -330,8 +330,8 @@ export default defineComponent({
}
> .thumbnail {
width: 128px;
height: 128px;
width: 110px;
height: 110px;
margin: auto;
}

View File

@ -11,7 +11,7 @@
<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
</div>
</nav>
<div class="main _section" :class="{ uploading: uploadings.length > 0, fetching }"
<div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
ref="main"
@dragover.prevent.stop="onDragover"
@dragenter="onDragenter"
@ -704,6 +704,7 @@ export default defineComponent({
> .main {
flex: 1;
overflow: auto;
padding: var(--margin);
&, * {
user-select: none;
@ -735,7 +736,7 @@ export default defineComponent({
> .folder,
> .file {
flex-grow: 1;
width: 144px;
width: 128px;
margin: 4px;
box-sizing: border-box;
}
@ -743,7 +744,7 @@ export default defineComponent({
> .padding {
flex-grow: 1;
pointer-events: none;
width: 144px + 8px;
width: 128px + 8px;
}
}

View File

@ -123,7 +123,7 @@ export default defineComponent({
> .index {
min-height: var(--height);
position: relative;
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
> .arrow {
position: absolute;
@ -181,7 +181,7 @@ export default defineComponent({
}
&.result {
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
&:empty {
display: none;

View File

@ -119,7 +119,7 @@ export default defineComponent({
> .index {
min-height: var(--height);
position: relative;
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
> .arrow {
position: absolute;
@ -177,7 +177,7 @@ export default defineComponent({
}
&.result {
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
&:empty {
display: none;

View File

@ -402,7 +402,7 @@ export default defineComponent({
> .tab {
flex: 1;
height: 38px;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
&.active {
border-top: solid 1px var(--accent);
@ -425,7 +425,7 @@ export default defineComponent({
> div {
&:not(.index) {
padding: 4px 0 8px 0;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
}
> header {
@ -492,7 +492,7 @@ export default defineComponent({
}
&.result {
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
&:empty {
display: none;

View File

@ -20,12 +20,19 @@ export default defineComponent({
<style lang="scss" scoped>
.rbusrurv {
line-height: 1.4em;
// 他のCSSからも参照されるので消さないように
--formXPadding: 32px;
--formYPadding: 32px;
font-size: 95%;
line-height: 1.3em;
background: var(--bg);
padding: 32px;
padding: var(--formYPadding) var(--formXPadding);
max-width: 750px;
margin: 0 auto;
&:not(.wide).max-width_400px {
padding: 32px 0;
--formXPadding: 0px;
> ::v-deep(*) {
._formPanel {
@ -36,16 +43,16 @@ export default defineComponent({
}
._form_group {
> * {
&:not(:first-child) {
> *:not(._formNoConcat) {
&:not(:last-child):not(._formNoConcatPrev) {
&._formPanel, ._formPanel {
border-top: none;
border-bottom: solid 0.5px var(--divider);
}
}
&:not(:last-child) {
&:not(:first-child):not(._formNoConcatNext) {
&._formPanel, ._formPanel {
border-bottom: solid 0.5px var(--divider);
border-top: none;
}
}
}

View File

@ -1,32 +1,48 @@
._formPanel {
background: var(--panel);
border-radius: var(--radius);
transition: background 0.2s ease;
&._formClickable {
&:hover {
//background: var(--panelHighlight);
}
&:active {
background: var(--panelHighlight);
transition: background 0s;
}
}
}
._formLabel,
._formCaption {
font-size: 80%;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
._formLabel {
font-size: 80%;
padding: 0 16px 8px 16px;
opacity: 0.8;
position: sticky;
top: var(--stickyTop, 0px);
z-index: 2;
margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1);
padding: 8px calc(16px + var(--formXPadding)) 8px calc(16px + var(--formXPadding));
background: var(--X17);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
&:empty {
display: none;
}
._themeChanging_ ._formLabel {
transition: none !important;
background: transparent;
}
._formCaption {
font-size: 80%;
padding: 8px 16px 0 16px;
opacity: 0.8;
&:empty {
display: none;
}
}
._formItem {

View File

@ -1,7 +1,7 @@
<template>
<div class="vrtktovg _formItem" v-size="{ max: [500] }">
<div class="vrtktovg _formItem _formNoConcat" v-size="{ max: [500] }" v-sticky-container>
<div class="_formLabel"><slot name="label"></slot></div>
<div class="main _form_group">
<div class="main _form_group" ref="child">
<slot></slot>
</div>
<div class="_formCaption"><slot name="caption"></slot></div>
@ -9,33 +9,69 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, onMounted, ref } from 'vue';
export default defineComponent({
setup(props, context) {
const child = ref<HTMLElement | null>(null);
const scanChild = () => {
if (child.value == null) return;
const els = Array.from(child.value.children);
for (let i = 0; i < els.length; i++) {
const el = els[i];
if (el.classList.contains('_formNoConcat')) {
if (els[i - 1]) els[i - 1].classList.add('_formNoConcatPrev');
if (els[i + 1]) els[i + 1].classList.add('_formNoConcatNext');
}
}
};
onMounted(() => {
scanChild();
const observer = new MutationObserver(records => {
scanChild();
});
observer.observe(child.value, {
childList: true,
subtree: false,
attributes: false,
characterData: false,
});
});
return {
child
};
}
});
</script>
<style lang="scss" scoped>
.vrtktovg {
> .main {
> ::v-deep(*) {
margin: 0;
&:not(:first-child) {
&._formPanel, ._formPanel {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
> ::v-deep(*):not(._formNoConcat) {
&:not(._formNoConcatNext) {
margin: 0;
}
&:not(:last-child) {
&:not(:last-child):not(._formNoConcatPrev) {
&._formPanel, ._formPanel {
border-bottom: solid 0.5px var(--divider);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
&:not(:first-child):not(._formNoConcatNext) {
&._formPanel, ._formPanel {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
}
}
}

View File

@ -0,0 +1,49 @@
<template>
<div class="fzenkabp _formItem">
<div class="_formPanel" :class="{ warn }">
<i v-if="warn"><Fa :icon="faExclamationTriangle"/></i>
<i v-else><Fa :icon="faInfoCircle"/></i>
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faInfoCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
export default defineComponent({
props: {
warn: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {
faInfoCircle, faExclamationTriangle
};
}
});
</script>
<style lang="scss" scoped>
.fzenkabp {
> div {
padding: 14px 16px;
font-size: 90%;
background: var(--infoBg);
color: var(--infoFg);
&.warn {
background: var(--infoWarnBg);
color: var(--infoWarnFg);
}
> i {
margin-right: 4px;
}
}
}
</style>

View File

@ -215,7 +215,7 @@ export default defineComponent({
}
> .input {
$height: 52px;
$height: 48px;
position: relative;
> input {

View File

@ -22,9 +22,17 @@ export default defineComponent({
align-items: center;
padding: 14px 16px;
> .key {
margin-right: 12px;
white-space: nowrap;
}
> .value {
margin-left: auto;
opacity: 0.7;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
</style>

View File

@ -66,6 +66,7 @@ export default defineComponent({
&.active {
color: var(--accent);
background: var(--panelHighlight);
}
> .icon {

View File

@ -0,0 +1,102 @@
<template>
<FormGroup class="_formItem">
<template #label><slot></slot></template>
<div class="drooglns _formItem" :class="{ tall }">
<div class="input _formPanel">
<textarea class="_monospace"
v-model="v"
readonly
:spellcheck="false"
></textarea>
</div>
</div>
<template #caption><slot name="desc"></slot></template>
</FormGroup>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs, watch } from 'vue';
import * as JSON5 from 'json5';
import './form.scss';
import FormGroup from './group.vue';
export default defineComponent({
components: {
FormGroup,
},
props: {
value: {
required: false
},
tall: {
type: Boolean,
required: false,
default: false
},
pre: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
setup(props, context) {
const { value } = toRefs(props);
const v = ref('');
watch(() => value, newValue => {
v.value = JSON5.stringify(newValue.value, null, '\t');
}, {
immediate: true
});
return {
v,
};
}
});
</script>
<style lang="scss" scoped>
.drooglns {
position: relative;
> .input {
position: relative;
> textarea {
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 130px;
margin: 0;
padding: 16px;
box-sizing: border-box;
font: inherit;
font-weight: normal;
font-size: 1em;
background: transparent;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
color: var(--fg);
tab-size: 2;
white-space: pre;
}
}
&.tall {
> .input {
> textarea {
min-height: 200px;
}
}
}
}
</style>

View File

@ -69,8 +69,8 @@ export default defineComponent({
display: inline-block;
vertical-align: bottom;
position: relative;
width: 20px;
height: 20px;
width: 16px;
height: 16px;
margin-right: 8px;
background: none;
border: 2px solid var(--inputBorder);

View File

@ -69,7 +69,7 @@ export default defineComponent({
position: relative;
> .main {
padding: 24px 16px;
padding: 22px 16px;
> input {
display: block;

View File

@ -97,7 +97,7 @@ export default defineComponent({
font: inherit;
font-weight: normal;
font-size: 1em;
height: 52px;
height: 48px;
background: none;
border: none;
border-radius: 0;

View File

@ -0,0 +1,92 @@
<template>
<transition name="fade" mode="out-in">
<div class="_formItem" v-if="pending">
<div class="_formPanel">
<MkLoading/>
</div>
</div>
<FormGroup v-else-if="resolved" class="_formItem">
<slot :result="result"></slot>
</FormGroup>
<div class="_formItem" v-else>
<div class="_formPanel">
error!
<button @click="retry">retry</button>
</div>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch } from 'vue';
import './form.scss';
import FormGroup from './group.vue';
export default defineComponent({
components: {
FormGroup,
},
props: {
p: {
type: Function as PropType<() => Promise<any>>,
required: true,
}
},
setup(props, context) {
const pending = ref(true);
const resolved = ref(false);
const rejected = ref(false);
const result = ref(null);
const process = () => {
if (props.p == null) {
return;
}
const promise = props.p();
pending.value = true;
resolved.value = false;
rejected.value = false;
promise.then((_result) => {
pending.value = false;
resolved.value = true;
result.value = _result;
});
promise.catch(() => {
pending.value = false;
rejected.value = true;
});
};
watch(() => props.p, () => {
process();
}, {
immediate: true
});
const retry = () => {
process();
};
return {
pending,
resolved,
rejected,
result,
retry,
};
}
});
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -57,7 +57,7 @@ export default defineComponent({
> .main {
position: relative;
display: flex;
padding: 16px;
padding: 14px 16px;
cursor: pointer;
> * {

View File

@ -93,6 +93,10 @@ export default defineComponent({
os.pageWindow(this.to);
},
modalWindow() {
os.modalPageWindow(this.to);
},
popout() {
popout(this.to);
},
@ -111,6 +115,8 @@ export default defineComponent({
if (this.behavior) {
if (this.behavior === 'window') {
return this.window();
} else if (this.behavior === 'modalWindow') {
return this.modalWindow();
}
}

View File

@ -1,5 +1,5 @@
<template>
<span class="mk-acct" v-once>
<span class="mk-acct">
<span class="name">@{{ user.username }}</span>
<span class="host" v-if="user.host || detail || $store.state.showFullAcct">@{{ user.host || host }}</span>
</span>
@ -7,11 +7,20 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { toUnicode } from 'punycode';
import { toUnicode } from 'punycode/';
import { host } from '@client/config';
export default defineComponent({
props: ['user', 'detail'],
props: {
user: {
type: Object,
required: true
},
detail: {
type: Boolean,
default: false
},
},
data() {
return {
host: toUnicode(host),

View File

@ -1,9 +1,11 @@
<template>
<span class="eiwwqkts _noSelect" :class="{ cat }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick">
<img class="inner" :src="url" decoding="async"/>
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
</span>
<MkA class="eiwwqkts _noSelect" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
<img class="inner" :src="url" decoding="async"/>
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
</MkA>
</template>
@ -12,8 +14,12 @@ import { defineComponent } from 'vue';
import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
import { extractAvgColorFromBlurhash } from '@client/scripts/extract-avg-color-from-blurhash';
import { acct, userPage } from '@client/filters/user';
import MkUserOnlineIndicator from '@client/components/user-online-indicator.vue';
export default defineComponent({
components: {
MkUserOnlineIndicator
},
props: {
user: {
type: Object,
@ -30,6 +36,10 @@ export default defineComponent({
disablePreview: {
required: false,
default: false
},
showIndicator: {
required: false,
default: false
}
},
emits: ['click'],
@ -93,7 +103,7 @@ export default defineComponent({
}
}
.inner {
> .inner {
position: absolute;
bottom: 0;
left: 0;
@ -106,5 +116,14 @@ export default defineComponent({
width: 100%;
height: 100%;
}
> .indicator {
position: absolute;
z-index: 1;
bottom: 0;
left: 0;
width: 20%;
height: 20%;
}
}
</style>

View File

@ -1,12 +1,11 @@
<template>
<div class="yxspomdl" :class="{ inline }">
<div class="yxspomdl" :class="{ inline, colored }">
<div class="ring"></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@client/os';
export default defineComponent({
props: {
@ -14,6 +13,11 @@ export default defineComponent({
type: Boolean,
required: false,
default: false
},
colored: {
type: Boolean,
required: false,
default: true
}
}
});
@ -32,6 +36,11 @@ export default defineComponent({
.yxspomdl {
padding: 32px;
text-align: center;
cursor: wait;
&.colored {
color: var(--accent);
}
&.inline {
display: inline;
@ -41,24 +50,43 @@ export default defineComponent({
width: 32px;
height: 32px;
}
> .ring {
&:before,
&:after {
width: 32px;
height: 32px;
}
}
}
> .ring {
position: relative;
display: inline-block;
opacity: 0.7;
vertical-align: middle;
}
> .ring:after {
content: " ";
display: block;
box-sizing: border-box;
width: 48px;
height: 48px;
border-radius: 50%;
border: solid 4px;
border-color: currentColor transparent transparent transparent;
animation: ring 0.5s linear infinite;
&:before,
&:after {
content: " ";
display: block;
box-sizing: border-box;
width: 48px;
height: 48px;
border-radius: 50%;
border: solid 4px;
}
&:before {
border-color: currentColor;
opacity: 0.3;
}
&:after {
position: absolute;
top: 0;
border-color: currentColor transparent transparent transparent;
animation: ring 0.5s linear infinite;
}
}
}
</style>

View File

@ -22,7 +22,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
import { toUnicode as decodePunycode } from 'punycode';
import { toUnicode as decodePunycode } from 'punycode/';
import { url as local } from '@client/config';
import { isDeviceTouch } from '@client/scripts/is-device-touch';
import * as os from '@client/os';

View File

@ -71,6 +71,7 @@ export default defineComponent({
<style lang="scss" scoped>
.xubzgfgb {
position: relative;
width: 100%;
height: 100%;
@ -82,6 +83,7 @@ export default defineComponent({
}
> canvas {
position: absolute;
object-fit: cover;
}

View File

@ -146,7 +146,7 @@ export default defineComponent({
> .sub {
margin-top: 8px;
padding-top: 8px;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
}
}
</style>

View File

@ -27,7 +27,7 @@ import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-i
import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
import { extractAvgColorFromBlurhash } from '@client/scripts/extract-avg-color-from-blurhash';
import ImageViewer from './image-viewer.vue';
import ImgWithBlurhash from './img-with-blurhash.vue';
import ImgWithBlurhash from '@client/components/img-with-blurhash.vue';
import * as os from '@client/os';
export default defineComponent({
@ -123,7 +123,7 @@ export default defineComponent({
.gqnyydlz {
position: relative;
border: solid 1px var(--divider);
border: solid 0.5px var(--divider);
> i {
display: block;

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { toUnicode } from 'punycode';
import { toUnicode } from 'punycode/';
import { host as localHost } from '@client/config';
import { wellKnownServices } from '../../well-known-services';
import * as os from '@client/os';

View File

@ -1,6 +1,5 @@
import { VNode, defineComponent, h } from 'vue';
import { MfmForest } from '@client/../mfm/prelude';
import { parse, parsePlain } from '@client/../mfm/parse';
import * as mfm from 'mfm-js';
import MkUrl from '@client/components/global/url.vue';
import MkLink from '@client/components/link.vue';
import MkMention from '@client/components/mention.vue';
@ -46,23 +45,26 @@ export default defineComponent({
render() {
if (this.text == null || this.text == '') return;
const ast = (this.plain ? parsePlain : parse)(this.text);
const ast = (this.plain ? mfm.parsePlain : mfm.parse)(this.text);
const validTime = (t: string | null | undefined) => {
if (t == null) return null;
return t.match(/^[0-9.]+s$/) ? t : null;
};
const genEl = (ast: MfmForest) => concat(ast.map((token): VNode[] => {
switch (token.node.type) {
const genEl = (ast: mfm.MfmNode[]) => concat(ast.map((token): VNode[] => {
switch (token.type) {
case 'text': {
const text = token.node.props.text.replace(/(\r\n|\n|\r)/g, '\n');
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
if (!this.plain) {
const x = text.split('\n')
.map(t => t == '' ? [h('br')] : [t, h('br')]);
x[x.length - 1].pop();
return x;
const res = [];
for (const t of text.split('\n')) {
res.push(h('br'));
res.push(t);
}
res.shift();
return res;
} else {
return [text.replace(/\n/g, ' ')];
}
@ -83,38 +85,38 @@ export default defineComponent({
}
case 'fn': {
// TODO: CSSを文字列で組み立てていくと token.node.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
let style;
switch (token.node.props.name) {
switch (token.props.name) {
case 'tada': {
style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : '');
break;
}
case 'jelly': {
const speed = validTime(token.node.props.args.speed) || '1s';
const speed = validTime(token.props.args.speed) || '1s';
style = (this.$store.state.animatedMfm ? `animation: mfm-rubberBand ${speed} linear infinite both;` : '');
break;
}
case 'twitch': {
const speed = validTime(token.node.props.args.speed) || '0.5s';
const speed = validTime(token.props.args.speed) || '0.5s';
style = this.$store.state.animatedMfm ? `animation: mfm-twitch ${speed} ease infinite;` : '';
break;
}
case 'shake': {
const speed = validTime(token.node.props.args.speed) || '0.5s';
const speed = validTime(token.props.args.speed) || '0.5s';
style = this.$store.state.animatedMfm ? `animation: mfm-shake ${speed} ease infinite;` : '';
break;
}
case 'spin': {
const direction =
token.node.props.args.left ? 'reverse' :
token.node.props.args.alternate ? 'alternate' :
token.props.args.left ? 'reverse' :
token.props.args.alternate ? 'alternate' :
'normal';
const anime =
token.node.props.args.x ? 'mfm-spinX' :
token.node.props.args.y ? 'mfm-spinY' :
token.props.args.x ? 'mfm-spinX' :
token.props.args.y ? 'mfm-spinY' :
'mfm-spin';
const speed = validTime(token.node.props.args.speed) || '1.5s';
const speed = validTime(token.props.args.speed) || '1.5s';
style = this.$store.state.animatedMfm ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : '';
break;
}
@ -128,8 +130,8 @@ export default defineComponent({
}
case 'flip': {
const transform =
(token.node.props.args.h && token.node.props.args.v) ? 'scale(-1, -1)' :
token.node.props.args.v ? 'scaleY(-1)' :
(token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' :
token.props.args.v ? 'scaleY(-1)' :
'scaleX(-1)';
style = `transform: ${transform};`;
break;
@ -148,12 +150,12 @@ export default defineComponent({
}
case 'font': {
const family =
token.node.props.args.serif ? 'serif' :
token.node.props.args.monospace ? 'monospace' :
token.node.props.args.cursive ? 'cursive' :
token.node.props.args.fantasy ? 'fantasy' :
token.node.props.args.emoji ? 'emoji' :
token.node.props.args.math ? 'math' :
token.props.args.serif ? 'serif' :
token.props.args.monospace ? 'monospace' :
token.props.args.cursive ? 'cursive' :
token.props.args.fantasy ? 'fantasy' :
token.props.args.emoji ? 'emoji' :
token.props.args.math ? 'math' :
null;
if (family) style = `font-family: ${family};`;
break;
@ -165,7 +167,7 @@ export default defineComponent({
}
}
if (style == null) {
return h('span', {}, ['[', token.node.props.name, ...genEl(token.children), ']']);
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);
} else {
return h('span', {
style: 'display: inline-block;' + style,
@ -188,7 +190,7 @@ export default defineComponent({
case 'url': {
return [h(MkUrl, {
key: Math.random(),
url: token.node.props.url,
url: token.props.url,
rel: 'nofollow noopener',
})];
}
@ -196,7 +198,7 @@ export default defineComponent({
case 'link': {
return [h(MkLink, {
key: Math.random(),
url: token.node.props.url,
url: token.props.url,
rel: 'nofollow noopener',
}, genEl(token.children))];
}
@ -204,32 +206,31 @@ export default defineComponent({
case 'mention': {
return [h(MkMention, {
key: Math.random(),
host: (token.node.props.host == null && this.author && this.author.host != null ? this.author.host : token.node.props.host) || host,
username: token.node.props.username
host: (token.props.host == null && this.author && this.author.host != null ? this.author.host : token.props.host) || host,
username: token.props.username
})];
}
case 'hashtag': {
return [h(MkA, {
key: Math.random(),
to: this.isNote ? `/tags/${encodeURIComponent(token.node.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.node.props.hashtag)}`,
to: this.isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.props.hashtag)}`,
style: 'color:var(--hashtag);'
}, `#${token.node.props.hashtag}`)];
}, `#${token.props.hashtag}`)];
}
case 'blockCode': {
return [h(MkCode, {
key: Math.random(),
code: token.node.props.code,
lang: token.node.props.lang,
code: token.props.code,
lang: token.props.lang,
})];
}
case 'inlineCode': {
return [h(MkCode, {
key: Math.random(),
code: token.node.props.code,
lang: token.node.props.lang,
code: token.props.code,
inline: true
})];
}
@ -246,10 +247,19 @@ export default defineComponent({
}
}
case 'emoji': {
case 'emojiCode': {
return [h(MkEmoji, {
key: Math.random(),
emoji: token.node.props.name ? `:${token.node.props.name}:` : token.node.props.emoji,
emoji: `:${token.props.name}:`,
customEmojis: this.customEmojis,
normal: this.plain
})];
}
case 'unicodeEmoji': {
return [h(MkEmoji, {
key: Math.random(),
emoji: token.props.emoji,
customEmojis: this.customEmojis,
normal: this.plain
})];
@ -258,7 +268,7 @@ export default defineComponent({
case 'mathInline': {
return [h(MkFormula, {
key: Math.random(),
formula: token.node.props.formula,
formula: token.props.formula,
block: false
})];
}
@ -266,7 +276,7 @@ export default defineComponent({
case 'mathBlock': {
return [h(MkFormula, {
key: Math.random(),
formula: token.node.props.formula,
formula: token.props.formula,
block: true
})];
}
@ -274,12 +284,12 @@ export default defineComponent({
case 'search': {
return [h(MkGoogle, {
key: Math.random(),
q: token.node.props.query
q: token.props.query
})];
}
default: {
console.error('unrecognized ast type:', token.node.type);
console.error('unrecognized ast type:', token.type);
return [];
}

View File

@ -0,0 +1,213 @@
<template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header">
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
<span class="title">
<XHeader :info="pageInfo" :with-back="false"/>
</span>
<button class="_button" @click="$refs.modal.close()"><Fa :icon="faTimes"/></button>
</div>
<div class="body _flat_">
<keep-alive>
<component :is="component" v-bind="props" :ref="changePage"/>
</keep-alive>
</div>
</div>
</MkModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft, faColumns, faTimes } from '@fortawesome/free-solid-svg-icons';
import MkModal from '@client/components/ui/modal.vue';
import XHeader from '@client/ui/_common_/header.vue';
import { popout } from '@client/scripts/popout';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
import { resolve } from '@client/router';
import { url } from '@client/config';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
MkModal,
XHeader,
},
inject: {
sideViewHook: {
default: null
}
},
provide() {
return {
navHook: (path) => {
this.navigate(path);
}
};
},
props: {
initialPath: {
type: String,
required: true,
},
initialComponent: {
type: Object,
required: true,
},
initialProps: {
type: Object,
required: false,
default: () => {},
},
},
emits: ['closed'],
data() {
return {
width: 860,
height: 660,
pageInfo: null,
path: this.initialPath,
component: this.initialComponent,
props: this.initialProps,
history: [],
faChevronLeft, faTimes,
};
},
computed: {
url(): string {
return url + this.path;
},
contextmenu() {
return [{
type: 'label',
text: this.path,
}, {
icon: faExpandAlt,
text: this.$ts.showInPage,
action: this.expand
}, this.sideViewHook ? {
icon: faColumns,
text: this.$ts.openInSideView,
action: () => {
this.sideViewHook(this.path);
this.$refs.window.close();
}
} : undefined, {
icon: faExternalLinkAlt,
text: this.$ts.popout,
action: this.popout
}, null, {
icon: faExternalLinkAlt,
text: this.$ts.openInNewTab,
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: faLink,
text: this.$ts.copyLink,
action: () => {
copyToClipboard(this.url);
}
}];
},
},
methods: {
changePage(page) {
if (page == null) return;
if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO];
}
},
navigate(path, record = true) {
if (record) this.history.push(this.path);
this.path = path;
const { component, props } = resolve(path);
this.component = component;
this.props = props;
},
back() {
this.navigate(this.history.pop(), false);
},
expand() {
this.$router.push(this.path);
this.$refs.window.close();
},
popout() {
popout(this.path, this.$el);
this.$refs.window.close();
},
},
});
</script>
<style lang="scss" scoped>
.hrmcaedk {
overflow: hidden;
display: flex;
flex-direction: column;
contain: content;
--root-margin: 24px;
@media (max-width: 500px) {
--root-margin: 16px;
}
> .header {
$height: 52px;
$height-narrow: 42px;
display: flex;
flex-shrink: 0;
box-shadow: 0px 1px var(--divider);
> button {
height: $height;
width: $height;
@media (max-width: 500px) {
height: $height-narrow;
width: $height-narrow;
}
}
> .title {
flex: 1;
line-height: $height;
padding-left: 32px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
@media (max-width: 500px) {
line-height: $height-narrow;
padding-left: 16px;
}
}
> button + .title {
padding-left: 0;
}
}
> .body {
overflow: auto;
background: var(--bg);
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div
class="note _panel"
class="note _block"
v-if="!muted"
v-show="!isDeleted"
:tabindex="!isDeleted ? '-1' : null"
@ -35,7 +35,7 @@
</div>
<article class="article" @contextmenu.stop="onContextmenu">
<header class="header">
<MkAvatar class="avatar" :user="appearNote.user"/>
<MkAvatar class="avatar" :user="appearNote.user" :show-indicator="true"/>
<div class="body">
<div class="top">
<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.user.id">
@ -120,11 +120,11 @@
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle, faPaperclip } from '@fortawesome/free-solid-svg-icons';
import { defineAsyncComponent, defineComponent, markRaw } from 'vue';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle, faPaperclip, faShareAlt } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
import * as mfm from 'mfm-js';
import { sum } from '../../prelude/array';
import XSub from './note.sub.vue';
import XNoteHeader from './note-header.vue';
import XNotePreview from './note-preview.vue';
@ -141,6 +141,7 @@ import { userPage } from '@client/filters/user';
import * as os from '@client/os';
import { noteActions, noteViewInterruptors } from '@client/store';
import { reactionPicker } from '@client/scripts/reaction-picker';
import { extractUrlFromMfm } from '@/misc/extract-url-from-mfm';
function markRawAll(...xs) {
for (const x of xs) {
@ -252,21 +253,7 @@ export default defineComponent({
urls(): string[] {
if (this.appearNote.text) {
const ast = parse(this.appearNote.text);
// TODO: 再帰的にURL要素がないか調べる
const urls = unique(ast
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
.map(t => t.node.props.url));
// unique without hash
// [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ]
const removeHash = x => x.replace(/#[^#]*$/, '');
return urls.reduce((array, url) => {
const removed = removeHash(url);
if (!array.map(x => removeHash(x)).includes(removed)) array.push(url);
return array;
}, []);
return extractUrlFromMfm(mfm.parse(this.appearNote.text));
} else {
return null;
}
@ -638,6 +625,11 @@ export default defineComponent({
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
}
} : undefined,
{
icon: faShareAlt,
text: this.$ts.share,
action: this.share
},
null,
statePromise.then(state => state.isFavorited ? {
icon: faStar,
@ -863,6 +855,14 @@ export default defineComponent({
});
},
share() {
navigator.share({
title: this.$t('noteOf', { user: this.appearNote.user.name }),
text: this.appearNote.text,
url: `${url}/notes/${this.appearNote.id}`
});
},
focus() {
this.$el.focus();
},
@ -1020,7 +1020,7 @@ export default defineComponent({
margin: 0 0.5em;
padding: 4px 6px;
font-size: 80%;
border: solid 1px var(--divider);
border: solid 0.5px var(--divider);
border-radius: 4px;
}
@ -1123,7 +1123,7 @@ export default defineComponent({
}
> .reply {
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
}
&.max-width_500px {

View File

@ -78,7 +78,7 @@ export default defineComponent({
margin: 0 .5em 0 0;
padding: 1px 6px;
font-size: 80%;
border: solid 1px var(--divider);
border: solid 0.5px var(--divider);
border-radius: 3px;
}

View File

@ -139,7 +139,7 @@ export default defineComponent({
}
> .reply {
border-left: solid 1px var(--divider);
border-left: solid 0.5px var(--divider);
margin-top: 10px;
}
}

View File

@ -1,6 +1,6 @@
<template>
<div
class="tkcbzcuz _panel"
class="tkcbzcuz"
v-if="!muted"
v-show="!isDeleted"
:tabindex="!isDeleted ? '-1' : null"
@ -90,7 +90,7 @@
</div>
</article>
</div>
<div v-else class="_panel muted" @click="muted = false">
<div v-else class="muted" @click="muted = false">
<I18n :src="$ts.userSaysSomething" tag="small">
<template #name>
<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
@ -102,11 +102,11 @@
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle, faPaperclip } from '@fortawesome/free-solid-svg-icons';
import { defineAsyncComponent, defineComponent, markRaw } from 'vue';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle, faPaperclip, faShareAlt } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
import * as mfm from 'mfm-js';
import { sum } from '../../prelude/array';
import XSub from './note.sub.vue';
import XNoteHeader from './note-header.vue';
import XNotePreview from './note-preview.vue';
@ -123,6 +123,7 @@ import { userPage } from '@client/filters/user';
import * as os from '@client/os';
import { noteActions, noteViewInterruptors } from '@client/store';
import { reactionPicker } from '@client/scripts/reaction-picker';
import { extractUrlFromMfm } from '@/misc/extract-url-from-mfm';
function markRawAll(...xs) {
for (const x of xs) {
@ -238,21 +239,7 @@ export default defineComponent({
urls(): string[] {
if (this.appearNote.text) {
const ast = parse(this.appearNote.text);
// TODO: 再帰的にURL要素がないか調べる
const urls = unique(ast
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
.map(t => t.node.props.url));
// unique without hash
// [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ]
const removeHash = x => x.replace(/#[^#]*$/, '');
return urls.reduce((array, url) => {
const removed = removeHash(url);
if (!array.map(x => removeHash(x)).includes(removed)) array.push(url);
return array;
}, []);
return extractUrlFromMfm(mfm.parse(this.appearNote.text));
} else {
return null;
}
@ -613,6 +600,11 @@ export default defineComponent({
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
}
} : undefined,
{
icon: faShareAlt,
text: this.$ts.share,
action: this.share
},
null,
statePromise.then(state => state.isFavorited ? {
icon: faStar,
@ -838,6 +830,14 @@ export default defineComponent({
});
},
share() {
navigator.share({
title: this.$t('noteOf', { user: this.appearNote.user.name }),
text: this.appearNote.text,
url: `${url}/notes/${this.appearNote.id}`
});
},
focus() {
this.$el.focus();
},
@ -863,7 +863,7 @@ export default defineComponent({
.tkcbzcuz {
position: relative;
transition: box-shadow 0.1s ease;
overflow: hidden;
overflow: clip;
contain: content;
// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
@ -994,11 +994,12 @@ export default defineComponent({
> .avatar {
flex-shrink: 0;
display: block;
//position: sticky;
//top: 72px;
margin: 0 14px 8px 0;
width: 58px;
height: 58px;
position: sticky;
top: calc(22px + var(--stickyTop, 0px));
left: 0;
}
> .main {
@ -1119,7 +1120,7 @@ export default defineComponent({
}
> .reply {
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
}
&.max-width_500px {
@ -1142,6 +1143,7 @@ export default defineComponent({
margin: 0 10px 8px 0;
width: 50px;
height: 50px;
top: calc(14px + var(--stickyTop, 0px));
}
}
}

View File

@ -1,30 +1,34 @@
<template>
<div class="_list_">
<div class="_fullinfo" v-if="empty">
<transition name="fade" mode="out-in">
<MkLoading v-if="fetching"/>
<MkError v-else-if="error" @retry="init()"/>
<div class="_fullinfo" v-else-if="empty">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ $ts.noNotes }}</div>
</div>
<MkError v-if="error" @retry="init()"/>
<div v-else>
<div v-show="more && reversed" style="margin-bottom: var(--margin);">
<MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</MkButton>
</div>
<div v-show="more && reversed" style="margin-bottom: var(--margin);">
<MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</MkButton>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap">
<XNote :note="note" class="_block" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
</XList>
<div v-show="more && !reversed" style="margin-top: var(--margin);">
<MkButton style="margin: 0 auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</MkButton>
</div>
</div>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
<XNote :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
</XList>
<div v-show="more && !reversed" style="margin-top: var(--margin);">
<MkButton style="margin: 0 auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</MkButton>
</div>
</div>
</transition>
</template>
<script lang="ts">
@ -55,11 +59,15 @@ export default defineComponent({
pagination: {
required: true
},
prop: {
type: String,
required: false
}
},
noGap: {
type: Boolean,
required: false,
default: false
},
},
emits: ['before', 'after'],
@ -90,3 +98,14 @@ export default defineComponent({
}
});
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -9,17 +9,19 @@
@closed="$emit('closed')"
>
<template #header>{{ $ts.notificationSetting }}</template>
<div v-if="showGlobalToggle" class="_section">
<MkSwitch v-model:value="useGlobalSetting">
{{ $ts.useGlobalSetting }}
<template #desc>{{ $ts.useGlobalSettingDesc }}</template>
</MkSwitch>
</div>
<div v-if="!useGlobalSetting" class="_section">
<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo>
<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
<MkSwitch v-for="type in notificationTypes" :key="type" v-model:value="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch>
<div class="_monolithic_">
<div v-if="showGlobalToggle" class="_section">
<MkSwitch v-model:value="useGlobalSetting">
{{ $ts.useGlobalSetting }}
<template #desc>{{ $ts.useGlobalSettingDesc }}</template>
</MkSwitch>
</div>
<div v-if="!useGlobalSetting" class="_section">
<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo>
<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
<MkSwitch v-for="type in notificationTypes" :key="type" v-model:value="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch>
</div>
</div>
</XModalWindow>
</template>

View File

@ -1,19 +1,23 @@
<template>
<div class="mfcuwfyp _noGap_">
<XList class="notifications" :items="items" v-slot="{ item: notification }">
<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" @update:note="noteUpdated(notification.note, $event)" :key="notification.id"/>
<XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
</XList>
<transition name="fade" mode="out-in">
<MkLoading v-if="fetching"/>
<button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</button>
<MkError v-else-if="error" @retry="init()"/>
<p class="empty" v-if="empty">{{ $ts.noNotifications }}</p>
<p class="mfcuwfyp" v-else-if="empty">{{ $ts.noNotifications }}</p>
<MkError v-if="error" @retry="init()"/>
</div>
<div v-else class="_magnetParent">
<XList class="notifications _magnetChild" :items="items" v-slot="{ item: notification }" :no-gap="true">
<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" @update:note="noteUpdated(notification.note, $event)" :key="notification.id"/>
<XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
</XList>
<button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</button>
</div>
</transition>
</template>
<script lang="ts">
@ -120,17 +124,19 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.mfcuwfyp {
> .empty {
margin: 0;
padding: 16px;
text-align: center;
color: var(--fg);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
> .placeholder {
padding: 32px;
opacity: 0.3;
}
.mfcuwfyp {
margin: 0;
padding: 16px;
text-align: center;
color: var(--fg);
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj _panel" tabindex="-1">
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj _block _isolated" tabindex="-1">
<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<article>
<header>
@ -35,7 +35,6 @@ export default defineComponent({
<style lang="scss" scoped>
.vhpxefrj {
display: block;
width: 100%;
&:hover {
text-decoration: none;

View File

@ -14,7 +14,7 @@
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
</template>
<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);">
<div class="yrolvcoq _flat_">
<component :is="component" v-bind="props" :ref="changePage"/>
</div>
</XWindow>
@ -29,6 +29,7 @@ import { popout } from '@client/scripts/popout';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
import { resolve } from '@client/router';
import { url } from '@client/config';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -123,8 +124,8 @@ export default defineComponent({
methods: {
changePage(page) {
if (page == null) return;
if (page.INFO) {
this.pageInfo = page.INFO;
if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO];
}
},
@ -155,6 +156,7 @@ export default defineComponent({
<style lang="scss" scoped>
.yrolvcoq {
--section-padding: 16px;
min-height: 100%;
background: var(--bg);
}
</style>

View File

@ -9,8 +9,8 @@
import { TextBlock } from '@client/scripts/hpml/block';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { defineAsyncComponent, defineComponent, PropType } from 'vue';
import { parse } from '../../../mfm/parse';
import { unique } from '../../../prelude/array';
import * as mfm from 'mfm-js';
import { extractUrlFromMfm } from '@/misc/extract-url-from-mfm';
export default defineComponent({
components: {
@ -34,11 +34,7 @@ export default defineComponent({
computed: {
urls(): string[] {
if (this.text) {
const ast = parse(this.text);
// TODO: 再帰的にURL要素がないか調べる
return unique(ast
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
.map(t => t.node.props.url));
return extractUrlFromMfm(mfm.parse(this.text));
} else {
return [];
}

View File

@ -110,7 +110,7 @@ export default defineComponent({
position: relative;
margin: 4px 0;
padding: 4px 8px;
border: solid 1px var(--divider);
border: solid 0.5px var(--divider);
border-radius: 4px;
overflow: hidden;
cursor: pointer;

View File

@ -34,6 +34,7 @@
<button @click="addVisibleUser" class="_buttonPrimary"><Fa :icon="faPlus" fixed-width/></button>
</div>
</div>
<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown">
<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" />
<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
@ -56,12 +57,12 @@ import { faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlob
import { faEyeSlash, faLaughSquint } from '@fortawesome/free-regular-svg-icons';
import insertTextAtCursor from 'insert-text-at-cursor';
import { length } from 'stringz';
import { toASCII } from 'punycode';
import { toASCII } from 'punycode/';
import XNotePreview from './note-preview.vue';
import { parse } from '../../mfm/parse';
import * as mfm from 'mfm-js';
import { host, url } from '@client/config';
import { erase, unique } from '../../prelude/array';
import extractMentions from '@/misc/extract-mentions';
import { extractMentions } from '@/misc/extract-mentions';
import getAcct from '@/misc/acct/render';
import { formatTimeString } from '@/misc/format-time-string';
import { Autocomplete } from '@client/scripts/autocomplete';
@ -71,12 +72,14 @@ import { selectFile } from '@client/scripts/select-file';
import { notePostInterruptors, postFormActions } from '@client/store';
import { isMobile } from '@client/scripts/is-mobile';
import { throttle } from 'throttle-debounce';
import MkInfo from '@client/components/ui/info.vue';
export default defineComponent({
components: {
XNotePreview,
XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')),
XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue'))
XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')),
MkInfo,
},
inject: ['modal'],
@ -143,6 +146,7 @@ export default defineComponent({
autocomplete: null,
draghover: false,
quoteId: null,
hasNotSpecifiedMentions: false,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
imeText: '',
typing: throttle(3000, () => {
@ -214,6 +218,18 @@ export default defineComponent({
}
},
watch: {
text() {
this.checkMissingMention();
},
visibleUsers: {
handler() {
this.checkMissingMention();
},
deep: true
}
},
mounted() {
if (this.initialText) {
this.text = this.initialText;
@ -229,7 +245,7 @@ export default defineComponent({
}
if (this.reply && this.reply.text != null) {
const ast = parse(this.reply.text);
const ast = mfm.parse(this.reply.text);
for (const x of extractMentions(ast)) {
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
@ -338,6 +354,32 @@ export default defineComponent({
this.$watch('localOnly', () => this.saveDraft());
},
checkMissingMention() {
if (this.visibility === 'specified') {
const ast = mfm.parse(this.text);
for (const x of extractMentions(ast)) {
if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
this.hasNotSpecifiedMentions = true;
return;
}
}
this.hasNotSpecifiedMentions = false;
}
},
addMissingMention() {
const ast = mfm.parse(this.text);
for (const x of extractMentions(ast)) {
if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
os.api('users/show', { username: x.username, host: x.host }).then(user => {
this.visibleUsers.push(user);
});
}
}
},
togglePoll() {
if (this.poll) {
this.poll = null;
@ -580,7 +622,7 @@ export default defineComponent({
this.deleteDraft();
this.$emit('posted');
if (this.text && this.text != '') {
const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag);
const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
}
@ -741,6 +783,10 @@ export default defineComponent({
}
}
> .hasNotSpecifiedMentions {
margin: 0 20px 16px 20px;
}
> .cw,
> .text {
display: block;
@ -767,7 +813,7 @@ export default defineComponent({
> .cw {
z-index: 1;
padding-bottom: 8px;
border-bottom: solid 1px var(--divider);
border-bottom: solid 0.5px var(--divider);
}
> .text {

View File

@ -1,5 +1,5 @@
<template>
<div class="jmgmzlwq _panel"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $ts.remoteUserCaution }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $ts.showOnRemote }}</a></div>
<div class="jmgmzlwq _block"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $ts.remoteUserCaution }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $ts.showOnRemote }}</a></div>
</template>
<script lang="ts">

View File

@ -1,5 +1,5 @@
<template>
<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="auth _section">
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
<div class="normal-signin" v-if="!totpLogin">
@ -39,16 +39,16 @@
</div>
</div>
<div class="social _section">
<a class="_borderButton _vMargin" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
<a class="_borderButton _vMargin" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
<a class="_borderButton _vMargin" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
<a class="_borderButton _gap" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
<a class="_borderButton _gap" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
<a class="_borderButton _gap" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
</div>
</form>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { toUnicode } from 'punycode';
import { toUnicode } from 'punycode/';
import { faLock, faGavel } from '@fortawesome/free-solid-svg-icons';
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
import MkButton from './ui/button.vue';

View File

@ -7,8 +7,10 @@
>
<template #header>{{ $ts.signup }}</template>
<div class="_section">
<XSignup :auto-set="autoSet" @signup="onSignup"/>
<div class="_monolithic_">
<div class="_section">
<XSignup :auto-set="autoSet" @signup="onSignup"/>
</div>
</div>
</XModalWindow>
</template>

View File

@ -55,7 +55,7 @@
import { defineComponent, defineAsyncComponent } from 'vue';
import { faLock, faExclamationTriangle, faSpinner, faCheck, faKey } from '@fortawesome/free-solid-svg-icons';
const getPasswordStrength = require('syuilo-password-strength');
import { toUnicode } from 'punycode';
import { toUnicode } from 'punycode/';
import { host, url } from '@client/config';
import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue';

View File

@ -9,7 +9,7 @@
<template #header>Req Viewer</template>
<div class="rlkneywz">
<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
<option value="req">Request</option>
<option value="res">Response</option>
</MkTab>

View File

@ -4,7 +4,7 @@
<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager
</template>
<div class="qljqmnzj _monospace">
<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
<option value="windows">Windows</option>
<option value="stream">Stream</option>
<option value="streamPool">Stream (Pool)</option>
@ -215,7 +215,7 @@ export default defineComponent({
width: 100%;
padding: 8px 16px;
box-sizing: border-box;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
font-size: 0.9em;
> div {

View File

@ -1,5 +1,5 @@
<template>
<XNotes :class="{ _noGap_: !$store.state.showGapBetweenNotesInTimeline }" ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
<XNotes :no-gap="!$store.state.showGapBetweenNotesInTimeline" ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
</template>
<script lang="ts">
@ -56,6 +56,7 @@ export default defineComponent({
includeLocalRenotes: this.$store.state.showLocalRenotes
},
query: {},
date: null
};
},
@ -157,7 +158,7 @@ export default defineComponent({
endpoint: endpoint,
limit: 10,
params: init => ({
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
untilDate: this.date?.getTime(),
...this.baseQuery, ...this.query
})
};
@ -171,6 +172,11 @@ export default defineComponent({
methods: {
focus() {
this.$refs.tl.focus();
},
timetravel(date?: Date) {
this.date = date;
this.$refs.tl.reload();
}
}
});

View File

@ -1,10 +1,10 @@
<template>
<div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }">
<div class="ukygtjoj _block" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }">
<header v-if="showHeader" ref="header">
<div class="title"><slot name="header"></slot></div>
<div class="sub">
<slot name="func"></slot>
<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody">
<button class="_button" v-if="foldable" @click="() => showBody = !showBody">
<template v-if="showBody"><Fa :icon="faAngleUp"/></template>
<template v-else><Fa :icon="faAngleDown"/></template>
</button>
@ -16,8 +16,11 @@
@leave="leave"
@after-leave="afterLeave"
>
<div v-show="showBody">
<div v-show="showBody" class="content" :class="{ omitted }" ref="content">
<slot></slot>
<button v-if="omitted" class="fade _button" @click="() => { ignoreOmit = true; omitted = false; }">
<span>{{ $ts.showMore }}</span>
</button>
</div>
</transition>
</div>
@ -39,7 +42,7 @@ export default defineComponent({
required: false,
default: false
},
bodyTogglable: {
foldable: {
type: Boolean,
required: false,
default: false
@ -54,10 +57,17 @@ export default defineComponent({
required: false,
default: false
},
maxHeight: {
type: Number,
required: false,
default: null
},
},
data() {
return {
showBody: this.expanded,
omitted: null,
ignoreOmit: false,
faAngleUp, faAngleDown
};
},
@ -73,10 +83,23 @@ export default defineComponent({
}, {
immediate: true
});
this.$el.style.setProperty('--maxHeight', this.maxHeight + 'px');
const calcOmit = () => {
if (this.omitted || this.ignoreOmit || this.maxHeight == null) return;
const height = this.$refs.content.offsetHeight;
this.omitted = height > this.maxHeight;
};
calcOmit();
new ResizeObserver((entries, observer) => {
calcOmit();
}).observe(this.$refs.content);
},
methods: {
toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
if (!this.foldable) return;
this.showBody = show;
},
@ -116,7 +139,7 @@ export default defineComponent({
.ukygtjoj {
position: relative;
overflow: hidden;
overflow: clip;
&.naked {
background: transparent !important;
@ -127,16 +150,18 @@ export default defineComponent({
display: flex;
flex-direction: column;
> div {
> .content {
overflow: auto;
}
}
> header {
position: relative;
position: sticky;
top: var(--stickyTop, 0px);
left: 0;
color: var(--panelHeaderFg);
background: var(--panelHeaderBg);
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
border-bottom: solid 0.5px var(--panelHeaderDivider);
z-index: 2;
line-height: 1.4em;
@ -167,12 +192,35 @@ export default defineComponent({
}
}
> div {
> ::v-deep(._content) {
padding: 24px;
> .content {
&.omitted {
position: relative;
max-height: var(--maxHeight);
overflow: hidden;
& + ._content {
border-top: solid 1px var(--divider);
> .fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 64px;
background: linear-gradient(0deg, var(--panel), var(--X15));
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
}
}
@ -185,10 +233,7 @@ export default defineComponent({
}
}
> div {
> ::v-deep(._content) {
padding: 16px;
}
> .content {
}
}
}

View File

@ -98,11 +98,12 @@ export default defineComponent({
> header {
display: flex;
position: relative;
z-index: 2;
// TODO
// position: sticky;
// top: var(--stickyTopOffset);
// backdrop-filter: blur(20px);
z-index: 10;
position: sticky;
top: var(--stickyTop, 0px);
background: var(--X17);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(20px);
> .title {
margin: 0;
@ -137,4 +138,10 @@ export default defineComponent({
}
}
}
._flat_ .ssazuxis {
> header {
padding: 0 16px;
}
}
</style>

View File

@ -53,4 +53,8 @@ export default defineComponent({
margin-right: 4px;
}
}
._flat_ .fpezltsf {
border-radius: 0;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: height ? `${height}px` : null }">
<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header">
<button class="_button" v-if="withOkButton" @click="$emit('close')"><Fa :icon="faTimes"/></button>
<span class="title">
@ -61,6 +61,11 @@ export default defineComponent({
required: false,
default: true,
},
scroll: {
type: Boolean,
required: false,
default: true,
},
},
emits: ['click', 'close', 'closed', 'ok'],
@ -94,10 +99,10 @@ export default defineComponent({
flex-direction: column;
contain: content;
--section-padding: 24px;
--root-margin: 24px;
@media (max-width: 500px) {
--section-padding: 16px;
--root-margin: 16px;
}
> .header {

View File

@ -1,5 +1,5 @@
<template>
<transition :name="$store.state.animation ? popup ? 'modal-popup' : 'modal' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
<transition :name="$store.state.animation ? popup ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? popup ? 500 : 300 : 0" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
<div v-show="manualShowing != null ? manualShowing : showing" class="mk-modal" v-hotkey.global="keymap" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<div class="bg _modalBg" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
<div class="content" :class="{ popup, fixed, top: position === 'top' }" @click.self="onBgClick" ref="content">
@ -183,9 +183,6 @@ export default defineComponent({
<style lang="scss" scoped>
.modal-enter-active, .modal-leave-active {
// CSS的には無意味だけどこれが無いとVueが認識しない
transition: opacity 0.3s, transform 0.3s !important;
> .bg {
transition: opacity 0.3s !important;
}
@ -207,9 +204,6 @@ export default defineComponent({
}
.modal-popup-enter-active, .modal-popup-leave-active {
// CSS的には無意味だけどこれが無いとVueが認識しない
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) !important;
> .bg {
transition: opacity 0.3s !important;
}

View File

@ -39,7 +39,7 @@ export default defineComponent({
const contentHeight = this.$refs.content.offsetHeight;
let left = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
let top = rect.top + window.pageYOffset + this.source.offsetHeight;
let top = rect.top + window.pageYOffset - contentHeight;
left -= (this.$el.offsetWidth / 2);
@ -47,9 +47,9 @@ export default defineComponent({
left = window.innerWidth - contentWidth + window.pageXOffset - 1;
}
if (top + contentHeight - window.pageYOffset > window.innerHeight) {
top = rect.top + window.pageYOffset - contentHeight;
this.$refs.content.style.transformOrigin = 'center bottom';
if (top - window.pageYOffset < 0) {
top = rect.top + window.pageYOffset + this.source.offsetHeight;
this.$refs.content.style.transformOrigin = 'center top';
}
this.$el.style.left = left + 'px';
@ -81,6 +81,6 @@ export default defineComponent({
text-align: center;
border-radius: 4px;
pointer-events: none;
transform-origin: center top;
transform-origin: center bottom;
}
</style>

View File

@ -395,7 +395,7 @@ export default defineComponent({
position: fixed;
top: 0;
left: 0;
z-index: 5000;
z-index: 10000; // mk-modalのと同じでなければならない
&.front {
z-index: 11000; // front指定の時は、mk-modalのよりも大きくなければならない

View File

@ -1,7 +1,7 @@
<template>
<div class="_panel vjnjpkug">
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<div class="title">
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>
@ -104,7 +104,7 @@ export default defineComponent({
> .description {
padding: 16px;
font-size: 0.8em;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
> .mfm {
display: -webkit-box;
@ -116,7 +116,7 @@ export default defineComponent({
> .status {
padding: 10px 16px;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
> div {
display: inline-block;

View File

@ -1,7 +1,7 @@
<template>
<MkError v-if="error" @retry="init()"/>
<div v-else class="efvhhmdq">
<div v-else class="efvhhmdq _isolated">
<div class="no-users" v-if="empty">
<p>{{ $ts.noUsers }}</p>
</div>

View File

@ -0,0 +1,50 @@
<template>
<div class="fzgwjkgc" :class="user.onlineStatus" v-tooltip="text"></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
user: {
type: Object,
required: true
},
},
computed: {
text(): string {
switch (this.user.onlineStatus) {
case 'online': return this.$ts.online;
case 'active': return this.$ts.active;
case 'offline': return this.$ts.offline;
case 'unknown': return this.$ts.unknown;
}
}
}
});
</script>
<style lang="scss" scoped>
.fzgwjkgc {
box-shadow: 0 0 0 3px var(--panel);
border-radius: 100%;
&.online {
background: #58d4c9;
}
&.active {
background: #e4bc48;
}
&.offline {
background: #ea5353;
}
&.unknown {
background: #888;
}
}
</style>

View File

@ -3,7 +3,7 @@
<div v-if="showing" class="fxxzrfni _popup _shadow" :style="{ top: top + 'px', left: left + 'px' }" @mouseover="() => { $emit('mouseover'); }" @mouseleave="() => { $emit('mouseleave'); }">
<div v-if="fetched" class="info">
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<div class="title">
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>

View File

@ -17,7 +17,7 @@
<div class="tbhwbxda _section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }">
<div class="users" v-if="users.length > 0">
<div class="user" v-for="user in users" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar"/>
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
@ -31,7 +31,7 @@
<div class="tbhwbxda _section recent" v-if="username == '' && host == ''">
<div class="users">
<div class="user" v-for="user in recentUsers" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar"/>
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
@ -153,7 +153,7 @@ export default defineComponent({
> .user {
display: flex;
align-items: center;
padding: 8px var(--section-padding);
padding: 8px var(--root-margin);
font-size: 14px;
&:hover {

View File

@ -7,7 +7,7 @@
<div class="users">
<MkA v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)">
<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true" :show-indicator="true"/>
<div class="body">
<MkUserName :user="extract ? extract(item) : item" class="name"/>
<MkAcct :user="extract ? extract(item) : item" class="acct"/>

View File

@ -97,7 +97,7 @@ export default defineComponent({
> .divider {
margin: 8px 0;
border-top: solid 1px var(--divider);
border-top: solid 0.5px var(--divider);
}
> button {

View File

@ -7,6 +7,7 @@ import tooltip from './tooltip';
import hotkey from './hotkey';
import appear from './appear';
import anim from './anim';
import stickyContainer from './sticky-container';
export default function(app: App) {
app.directive('userPreview', userPreview);
@ -17,4 +18,5 @@ export default function(app: App) {
app.directive('hotkey', hotkey);
app.directive('appear', appear);
app.directive('anim', anim);
app.directive('sticky-container', stickyContainer);
}

View File

@ -0,0 +1,15 @@
import { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
//const query = binding.value;
const header = src.children[0];
const currentStickyTop = getComputedStyle(src).getPropertyValue('--stickyTop') || '0px';
src.style.setProperty('--stickyTop', `${parseInt(currentStickyTop) + header.offsetHeight}px`);
header.style.setProperty('--stickyTop', currentStickyTop);
header.style.position = 'sticky';
header.style.top = 'var(--stickyTop)';
header.style.zIndex = '1';
},
} as Directive;

View File

@ -6,7 +6,7 @@ import '@client/style.scss';
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';
import { createApp, watch } from 'vue';
import { computed, createApp, watch } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import widgets from '@client/widgets';
@ -25,7 +25,6 @@ import { fetchInstance, instance } from '@client/instance';
import { makeHotkey } from '@client/scripts/hotkey';
import { search } from '@client/scripts/search';
import { isMobile } from '@client/scripts/is-mobile';
import { getThemes } from '@client/theme-store';
import { initializeSw } from '@client/scripts/initialize-sw';
import { reloadChannel } from '@client/scripts/unison-reload';
import { reactionPicker } from '@client/scripts/reaction-picker';
@ -36,6 +35,13 @@ console.info(`Misskey v${version}`);
window.onerror = null;
window.onunhandledrejection = null;
// 後方互換性のため。
// TODO: そのうち消す
if ((typeof ColdDeviceStorage.get('lightTheme') === 'string') || (typeof ColdDeviceStorage.get('darkTheme') === 'string')) {
ColdDeviceStorage.set('lightTheme', require('@client/themes/l-light.json5'));
ColdDeviceStorage.set('darkTheme', require('@client/themes/d-dark.json5'));
}
if (_DEV_) {
console.warn('Development mode!!!');
@ -161,6 +167,7 @@ const app = createApp(await (
ui === 'deck' ? import('@client/ui/deck.vue') :
ui === 'desktop' ? import('@client/ui/desktop.vue') :
ui === 'chat' ? import('@client/ui/chat/index.vue') :
ui === 'pope' ? import('@client/ui/universal.vue') :
import('@client/ui/default.vue')
).then(x => x.default));
@ -204,12 +211,24 @@ if (splash) {
}
watch(defaultStore.reactiveState.darkMode, (darkMode) => {
import('@client/scripts/theme').then(({ builtinThemes }) => {
const themes = builtinThemes.concat(getThemes());
applyTheme(themes.find(x => x.id === (darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'))));
});
applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
}, { immediate: localStorage.theme == null });
const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
watch(darkTheme, (theme) => {
if (defaultStore.state.darkMode) {
applyTheme(theme);
}
});
watch(lightTheme, (theme) => {
if (!defaultStore.state.darkMode) {
applyTheme(theme);
}
});
//#region Sync dark mode
if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
defaultStore.set('darkMode', isDeviceDarkmode());

View File

@ -203,6 +203,15 @@ export function pageWindow(path: string) {
}, {}, 'closed');
}
export function modalPageWindow(path: string) {
const { component, props } = resolve(path);
popup(import('@client/components/modal-page-window.vue'), {
initialPath: path,
initialComponent: markRaw(component),
initialProps: props,
}, {}, 'closed');
}
export function dialog(props: Record<string, any>) {
return new Promise((resolve, reject) => {
popup(import('@client/components/dialog.vue'), props, {

View File

@ -14,6 +14,7 @@
import { defineComponent } from 'vue';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@client/components/ui/button.vue';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -21,7 +22,7 @@ export default defineComponent({
},
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: this.$ts.error,
icon: faExclamationTriangle
},

View File

@ -1,5 +1,5 @@
<template>
<div style="overflow: hidden;">
<div style="overflow: clip;">
<FormBase class="znqjceqz">
<div id="debug"></div>
<section class="_formItem about">
@ -14,7 +14,7 @@
{{ $ts._aboutMisskey.about }}
</section>
<FormGroup>
<FormLink to="https://github.com/syuilo/misskey" external>
<FormLink to="https://github.com/misskey-dev/misskey" external>
<template #icon><Fa :icon="faCode"/></template>
{{ $ts._aboutMisskey.source }}
<template #suffix>GitHub</template>
@ -40,7 +40,8 @@
<FormLink to="https://github.com/rinsuki" external>@rinsuki</FormLink>
<FormLink to="https://github.com/Xeltica" external>@Xeltica</FormLink>
<FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink>
<template #caption><MkLink url="https://github.com/syuilo/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template>
<FormLink to="https://github.com/marihachi" external>@marihachi</FormLink>
<template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template>
</FormGroup>
<FormGroup>
<template #label><Mfm text="[jelly ❤]"/> {{ $ts._aboutMisskey.patrons }}</template>
@ -63,6 +64,7 @@ import FormKeyValueView from '@client/components/form/key-value-view.vue';
import MkLink from '@client/components/link.vue';
import { physics } from '@client/scripts/physics.ts';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
const patrons = [
'Satsuki Yanagi',
@ -114,7 +116,7 @@ export default defineComponent({
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: this.$ts.aboutMisskey,
icon: null
},

View File

@ -48,6 +48,7 @@ import FormGroup from '@client/components/form/group.vue';
import FormKeyValueView from '@client/components/form/key-value-view.vue';
import * as os from '@client/os';
import number from '@client/filters/number';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -59,7 +60,7 @@ export default defineComponent({
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: this.$ts.instanceInfo,
icon: faInfoCircle
},

View File

@ -12,7 +12,7 @@
</div>
</section>
<section class="_section">
<div class="_content _card _vMargin">
<div class="_content _card _gap">
<div class="_content">
<MkInput v-model:value="name" required><span>{{ $ts.name }}</span></MkInput>
<MkInput v-model:value="author" required><span>{{ $ts.author }}</span></MkInput>
@ -24,7 +24,7 @@
</div>
</div>
</div>
<div class="_content _card _vMargin">
<div class="_content _card _gap">
<div class="list-view _content">
<div class="item" v-for="([ k, v ], i) in theme" :key="k">
<div class="_inputs">
@ -94,7 +94,7 @@
import { defineComponent } from 'vue';
import { faPalette, faChevronDown, faKeyboard } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import { toUnicode } from 'punycode';
import { toUnicode } from 'punycode/';
import MkRadio from '@client/components/ui/radio.vue';
import MkButton from '@client/components/ui/button.vue';
@ -109,6 +109,7 @@ import { host } from '@client/config';
import * as os from '@client/os';
import { ColdDeviceStorage } from '@client/store';
import { addTheme } from '@client/theme-store';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -122,7 +123,7 @@ export default defineComponent({
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: this.$ts.themeEditor,
icon: faPalette,
},

View File

@ -1,7 +1,7 @@
<template>
<div class="_section">
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content">
<section class="_card announcement _vMargin" v-for="(announcement, i) in items" :key="announcement.id">
<section class="_card announcement _gap" v-for="(announcement, i) in items" :key="announcement.id">
<div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content">
<Mfm :text="announcement.text"/>
@ -21,6 +21,7 @@ import { faCheck, faBroadcastTower } from '@fortawesome/free-solid-svg-icons';
import MkPagination from '@client/components/ui/pagination.vue';
import MkButton from '@client/components/ui/button.vue';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -30,7 +31,7 @@ export default defineComponent({
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: this.$ts.announcements,
icon: faBroadcastTower
},

View File

@ -1,25 +1,25 @@
<template>
<div>
<section class="_section">
<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()">
<span>Endpoint</span>
</MkInput>
<MkTextarea v-model:value="body" code>
<span>Params (JSON or JSON5)</span>
</MkTextarea>
<MkSwitch v-model:value="withCredential">
With credential
</MkSwitch>
<MkButton primary full @click="send" :disabled="sending">
<template v-if="sending"><MkEllipsis/></template>
<template v-else><Fa :icon="faPaperPlane"/> Send</template>
</MkButton>
</section>
<section class="_section" v-if="res">
<MkTextarea v-model:value="res" code readonly tall>
<span>Response</span>
</MkTextarea>
</section>
<div class="_root">
<div class="_block" style="padding: 24px;">
<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()">
<span>Endpoint</span>
</MkInput>
<MkTextarea v-model:value="body" code>
<span>Params (JSON or JSON5)</span>
</MkTextarea>
<MkSwitch v-model:value="withCredential">
With credential
</MkSwitch>
<MkButton primary full @click="send" :disabled="sending">
<template v-if="sending"><MkEllipsis/></template>
<template v-else><Fa :icon="faPaperPlane"/> Send</template>
</MkButton>
</div>
<div v-if="res" class="_block" style="padding: 24px;">
<MkTextarea v-model:value="res" code readonly tall>
<span>Response</span>
</MkTextarea>
</div>
</div>
</template>
@ -32,6 +32,7 @@ import MkInput from '@client/components/ui/input.vue';
import MkTextarea from '@client/components/ui/textarea.vue';
import MkSwitch from '@client/components/ui/switch.vue';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -40,7 +41,7 @@ export default defineComponent({
data() {
return {
INFO: {
[symbols.PAGE_INFO]: {
title: 'API console',
icon: faTerminal
},

View File

@ -30,6 +30,7 @@ import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import { selectFile } from '@client/scripts/select-file';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -45,7 +46,7 @@ export default defineComponent({
data() {
return {
INFO: computed(() => this.channelId ? {
[symbols.PAGE_INFO]: computed(() => this.channelId ? {
title: this.$ts._channel.edit,
icon: faSatelliteDish,
} : {

View File

@ -1,6 +1,6 @@
<template>
<div v-if="channel" class="_section">
<div class="wpgynlbz _content _panel _vMargin" :class="{ hide: !showBanner }">
<div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }">
<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
<button class="_button toggle" @click="() => showBanner = !showBanner">
<template v-if="showBanner"><Fa :icon="faAngleUp"/></template>
@ -20,9 +20,9 @@
</div>
</div>
<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="$i"/>
<XPostForm :channel="channel" class="post-form _content _panel _gap" fixed v-if="$i"/>
<XTimeline class="_content _vMargin _noGap_" src="channel" :key="channelId" :channel="channelId" @before="before" @after="after"/>
<XTimeline class="_content _gap" src="channel" :key="channelId" :channel="channelId" @before="before" @after="after"/>
</div>
</template>
@ -35,6 +35,7 @@ import XPostForm from '@client/components/post-form.vue';
import XTimeline from '@client/components/timeline.vue';
import XChannelFollowButton from '@client/components/channel-follow-button.vue';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
@ -53,7 +54,7 @@ export default defineComponent({
data() {
return {
INFO: computed(() => this.channel ? {
[symbols.PAGE_INFO]: computed(() => this.channel ? {
title: this.channel.name,
icon: faSatelliteDish,
} : null),

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