Merge branch 'develop' into feature/default-post-target-detect-from-path
This commit is contained in:
commit
f680469877
|
@ -54,7 +54,7 @@ jobs:
|
||||||
- name: Copy API.json
|
- name: Copy API.json
|
||||||
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
|
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: api-artifact
|
name: api-artifact
|
||||||
path: ${{ matrix.api-json-name }}
|
path: ${{ matrix.api-json-name }}
|
||||||
|
@ -67,7 +67,7 @@ jobs:
|
||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
run: |
|
run: |
|
||||||
echo "$PR_NUMBER" > ./pr_number
|
echo "$PR_NUMBER" > ./pr_number
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: api-artifact
|
name: api-artifact
|
||||||
path: pr_number
|
path: pr_number
|
||||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
- name: Echo full diff
|
- name: Echo full diff
|
||||||
run: cat ./api-full.json.diff
|
run: cat ./api-full.json.diff
|
||||||
- name: Upload full diff to Artifact
|
- name: Upload full diff to Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: api-artifact
|
name: api-artifact
|
||||||
path: |
|
path: |
|
||||||
|
|
|
@ -108,12 +108,12 @@ jobs:
|
||||||
wait-on: 'http://localhost:61812'
|
wait-on: 'http://localhost:61812'
|
||||||
headed: true
|
headed: true
|
||||||
browser: ${{ matrix.browser }}
|
browser: ${{ matrix.browser }}
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.browser }}-cypress-screenshots
|
name: ${{ matrix.browser }}-cypress-screenshots
|
||||||
path: cypress/screenshots
|
path: cypress/screenshots
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.browser }}-cypress-videos
|
name: ${{ matrix.browser }}-cypress-videos
|
||||||
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -16,6 +16,7 @@
|
||||||
## 2023.x.x (unreleased)
|
## 2023.x.x (unreleased)
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
|
- Node.js 20.10.0が最小要件になりました
|
||||||
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
|
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
|
||||||
|
|
||||||
**影響:**
|
**影響:**
|
||||||
|
@ -36,8 +37,10 @@
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
|
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
|
||||||
- Feat: データセーバーでコードハイライトの読み込みを削減できるように
|
- Feat: 画面に雪を降らせられるように
|
||||||
- Feat: MFMのアニメーション要素(`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加
|
- Enhance: MFMのアニメーション要素(`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加
|
||||||
|
- Enhance: センシティブと判断されたウェブサイトのサムネイルを非表示に
|
||||||
|
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
|
||||||
- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 #12560
|
- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 #12560
|
||||||
- Enhance: リアクション用ピン留め絵文字と投稿時の絵文字入力用ピン留め絵文字を分けて設定できるように #12560
|
- Enhance: リアクション用ピン留め絵文字と投稿時の絵文字入力用ピン留め絵文字を分けて設定できるように #12560
|
||||||
- Enhance: 絵文字のオートコンプリート機能強化 #12364
|
- Enhance: 絵文字のオートコンプリート機能強化 #12364
|
||||||
|
@ -48,16 +51,19 @@
|
||||||
- Enhance: Shareページで投稿を完了すると、親ウィンドウ(親フレーム)にpostMessageするように
|
- Enhance: Shareページで投稿を完了すると、親ウィンドウ(親フレーム)にpostMessageするように
|
||||||
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
|
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
|
||||||
- Enhance: ノートプレビューに「内容を隠す」が反映されるように
|
- Enhance: ノートプレビューに「内容を隠す」が反映されるように
|
||||||
|
- Enhance: データセーバーでコードハイライトの読み込みを削減できるように
|
||||||
- Enhance: データセーバーの適用範囲を個別で設定できるように
|
- Enhance: データセーバーの適用範囲を個別で設定できるように
|
||||||
- 従来のデータセーバーの設定はリセットされます
|
- 従来のデータセーバーの設定はリセットされます
|
||||||
- Enhance: タイムライン上のタブからリスト、アンテナ、チャンネルの管理ページにジャンプできるように
|
- Enhance: タイムライン上のタブからリスト、アンテナ、チャンネルの管理ページにジャンプできるように
|
||||||
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
|
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
|
||||||
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
|
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
|
||||||
- Feat: センシティブと判断されたウェブサイトのサムネイルをぼかすように
|
|
||||||
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
|
|
||||||
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
|
||||||
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
|
||||||
- Enhance: 絵文字の詳細ページに記載される情報を追加
|
- Enhance: 絵文字の詳細ページに記載される情報を追加
|
||||||
|
- Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように
|
||||||
|
- MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました
|
||||||
|
- 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります
|
||||||
|
(例: ` ```js ` → Javascript, ` ```ais ` → AiScript)
|
||||||
|
- Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
||||||
|
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
||||||
- Fix: コードエディタが正しく表示されない問題を修正
|
- Fix: コードエディタが正しく表示されない問題を修正
|
||||||
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
|
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
|
||||||
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
|
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
|
||||||
|
@ -67,6 +73,7 @@
|
||||||
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
|
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
|
||||||
- Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正
|
- Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正
|
||||||
- Fix: ノート中のリアクションの表示を微調整 #12650
|
- Fix: ノート中のリアクションの表示を微調整 #12650
|
||||||
|
- Fix: AiScriptの`readline`が不正な値を返すことがある問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
||||||
|
@ -85,6 +92,7 @@
|
||||||
- Fix: 「みつける」が年越し時に壊れる問題を修正
|
- Fix: 「みつける」が年越し時に壊れる問題を修正
|
||||||
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
|
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
|
||||||
- Fix: モデレーションログがモデレーターは閲覧できないように修正
|
- Fix: モデレーションログがモデレーターは閲覧できないように修正
|
||||||
|
- Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない
|
||||||
|
|
||||||
## 2023.11.1
|
## 2023.11.1
|
||||||
|
|
||||||
|
|
|
@ -128,8 +128,8 @@ export interface Locale {
|
||||||
"pinnedEmojisForReactionSettingDescription": string;
|
"pinnedEmojisForReactionSettingDescription": string;
|
||||||
"pinnedEmojisSettingDescription": string;
|
"pinnedEmojisSettingDescription": string;
|
||||||
"emojiPickerDisplay": string;
|
"emojiPickerDisplay": string;
|
||||||
"copyFromPinnedEmojisForReaction": string;
|
"overwriteFromPinnedEmojisForReaction": string;
|
||||||
"copyFromPinnedEmojis": string;
|
"overwriteFromPinnedEmojis": string;
|
||||||
"reactionSettingDescription2": string;
|
"reactionSettingDescription2": string;
|
||||||
"rememberNoteVisibility": string;
|
"rememberNoteVisibility": string;
|
||||||
"attachCancel": string;
|
"attachCancel": string;
|
||||||
|
@ -1182,6 +1182,8 @@ export interface Locale {
|
||||||
"reloadRequiredToApplySettings": string;
|
"reloadRequiredToApplySettings": string;
|
||||||
"remainingN": string;
|
"remainingN": string;
|
||||||
"overwriteContentConfirm": string;
|
"overwriteContentConfirm": string;
|
||||||
|
"seasonalScreenEffect": string;
|
||||||
|
"decorate": string;
|
||||||
"_announcement": {
|
"_announcement": {
|
||||||
"forExistingUsers": string;
|
"forExistingUsers": string;
|
||||||
"forExistingUsersDescription": string;
|
"forExistingUsersDescription": string;
|
||||||
|
|
|
@ -125,8 +125,8 @@ emojiPicker: "絵文字ピッカー"
|
||||||
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
|
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
|
||||||
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
|
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
|
||||||
emojiPickerDisplay: "ピッカーの表示"
|
emojiPickerDisplay: "ピッカーの表示"
|
||||||
copyFromPinnedEmojisForReaction: "リアクション設定からコピーする"
|
overwriteFromPinnedEmojisForReaction: "リアクション設定から上書きする"
|
||||||
copyFromPinnedEmojis: "絵文字設定からコピーする"
|
overwriteFromPinnedEmojis: "全般設定から上書きする"
|
||||||
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
||||||
rememberNoteVisibility: "公開範囲を記憶する"
|
rememberNoteVisibility: "公開範囲を記憶する"
|
||||||
attachCancel: "添付取り消し"
|
attachCancel: "添付取り消し"
|
||||||
|
@ -1179,6 +1179,8 @@ code: "コード"
|
||||||
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
|
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
|
||||||
remainingN: "残り: {n}"
|
remainingN: "残り: {n}"
|
||||||
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
|
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
|
||||||
|
seasonalScreenEffect: "季節に応じた画面の演出"
|
||||||
|
decorate: "デコる"
|
||||||
|
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "既存ユーザーのみ"
|
forExistingUsers: "既存ユーザーのみ"
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.12.0-beta.4",
|
"version": "2023.12.0-beta.5",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.10.5",
|
"packageManager": "pnpm@8.12.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
|
@ -46,10 +46,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"cssnano": "6.0.1",
|
"cssnano": "6.0.2",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.32",
|
"postcss": "8.4.32",
|
||||||
"terser": "5.24.0",
|
"terser": "5.26.0",
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
"@typescript-eslint/parser": "6.14.0",
|
"@typescript-eslint/parser": "6.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.6.1",
|
"cypress": "13.6.1",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"start-server-and-test": "2.0.3",
|
"start-server-and-test": "2.0.3",
|
||||||
"ncp": "2.0.0"
|
"ncp": "2.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.16.0"
|
"node": ">=20.10.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./built/boot/entry.js",
|
"start": "node ./built/boot/entry.js",
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"bullmq": "4.15.3",
|
"bullmq": "4.15.4",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.1",
|
"cbor": "9.0.1",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
"file-type": "18.7.0",
|
"file-type": "18.7.0",
|
||||||
"fluent-ffmpeg": "2.1.2",
|
"fluent-ffmpeg": "2.1.2",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"got": "13.0.0",
|
"got": "14.0.0",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"http-link-header": "1.1.1",
|
"http-link-header": "1.1.1",
|
||||||
|
@ -222,8 +222,8 @@
|
||||||
"@typescript-eslint/parser": "6.14.0",
|
"@typescript-eslint/parser": "6.14.0",
|
||||||
"aws-sdk-client-mock": "3.0.0",
|
"aws-sdk-client-mock": "3.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-plugin-import": "2.29.0",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
|
|
|
@ -74,6 +74,14 @@ export const packedUserLiteSchema = {
|
||||||
format: 'url',
|
format: 'url',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
|
offsetX: {
|
||||||
|
type: 'number',
|
||||||
|
nullable: false, optional: true,
|
||||||
|
},
|
||||||
|
offsetY: {
|
||||||
|
type: 'number',
|
||||||
|
nullable: false, optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -138,7 +138,7 @@ export class ActivityPubServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const algo = match[1];
|
const algo = match[1].toUpperCase();
|
||||||
const digestValue = match[2];
|
const digestValue = match[2];
|
||||||
|
|
||||||
if (algo !== 'SHA-256') {
|
if (algo !== 'SHA-256') {
|
||||||
|
|
|
@ -61,6 +61,9 @@ export class FileServerService {
|
||||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
reply.header('Access-Control-Allow-Origin', '*');
|
||||||
|
}
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
"@tabler/icons-webfont": "2.44.0",
|
"@tabler/icons-webfont": "2.44.0",
|
||||||
"@vitejs/plugin-vue": "4.5.2",
|
"@vitejs/plugin-vue": "4.5.2",
|
||||||
"@vue/compiler-sfc": "3.3.11",
|
"@vue/compiler-sfc": "3.3.11",
|
||||||
"astring": "1.8.6",
|
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
|
||||||
|
"astring": "1.8.6",
|
||||||
"broadcast-channel": "6.0.0",
|
"broadcast-channel": "6.0.0",
|
||||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
|
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"gsap": "3.12.3",
|
"gsap": "3.12.4",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"insert-text-at-cursor": "0.3.0",
|
"insert-text-at-cursor": "0.3.0",
|
||||||
"is-file-animated": "1.0.2",
|
"is-file-animated": "1.0.2",
|
||||||
|
@ -56,8 +56,8 @@
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.9.0",
|
"rollup": "4.9.0",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"shiki": "0.14.6",
|
|
||||||
"sass": "1.69.5",
|
"sass": "1.69.5",
|
||||||
|
"shiki": "0.14.7",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.159.0",
|
"three": "0.159.0",
|
||||||
|
@ -69,29 +69,29 @@
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.3.3",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"v-code-diff": "1.7.2",
|
"v-code-diff": "1.7.2",
|
||||||
"vite": "5.0.8",
|
"vite": "5.0.10",
|
||||||
"vue": "3.3.11",
|
"vue": "3.3.11",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-actions": "7.6.4",
|
"@storybook/addon-actions": "7.6.5",
|
||||||
"@storybook/addon-essentials": "7.6.4",
|
"@storybook/addon-essentials": "7.6.5",
|
||||||
"@storybook/addon-interactions": "7.6.4",
|
"@storybook/addon-interactions": "7.6.5",
|
||||||
"@storybook/addon-links": "7.6.4",
|
"@storybook/addon-links": "7.6.5",
|
||||||
"@storybook/addon-storysource": "7.6.4",
|
"@storybook/addon-storysource": "7.6.5",
|
||||||
"@storybook/addons": "7.6.4",
|
"@storybook/addons": "7.6.5",
|
||||||
"@storybook/blocks": "7.6.4",
|
"@storybook/blocks": "7.6.5",
|
||||||
"@storybook/core-events": "7.6.4",
|
"@storybook/core-events": "7.6.5",
|
||||||
"@storybook/jest": "0.2.3",
|
"@storybook/jest": "0.2.3",
|
||||||
"@storybook/manager-api": "7.6.4",
|
"@storybook/manager-api": "7.6.5",
|
||||||
"@storybook/preview-api": "7.6.4",
|
"@storybook/preview-api": "7.6.5",
|
||||||
"@storybook/react": "7.6.4",
|
"@storybook/react": "7.6.5",
|
||||||
"@storybook/react-vite": "7.6.4",
|
"@storybook/react-vite": "7.6.5",
|
||||||
"@storybook/testing-library": "0.2.2",
|
"@storybook/testing-library": "0.2.2",
|
||||||
"@storybook/theming": "7.6.4",
|
"@storybook/theming": "7.6.5",
|
||||||
"@storybook/types": "7.6.4",
|
"@storybook/types": "7.6.5",
|
||||||
"@storybook/vue3": "7.6.4",
|
"@storybook/vue3": "7.6.5",
|
||||||
"@storybook/vue3-vite": "7.6.4",
|
"@storybook/vue3-vite": "7.6.5",
|
||||||
"@testing-library/vue": "8.0.1",
|
"@testing-library/vue": "8.0.1",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
|
@ -111,11 +111,12 @@
|
||||||
"acorn": "8.11.2",
|
"acorn": "8.11.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.6.1",
|
"cypress": "13.6.1",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-plugin-import": "2.29.0",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-vue": "9.19.2",
|
"eslint-plugin-vue": "9.19.2",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.5",
|
"micromatch": "4.0.5",
|
||||||
"msw": "1.3.2",
|
"msw": "1.3.2",
|
||||||
"msw-storybook-addon": "1.10.0",
|
"msw-storybook-addon": "1.10.0",
|
||||||
|
@ -124,7 +125,7 @@
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"start-server-and-test": "2.0.3",
|
"start-server-and-test": "2.0.3",
|
||||||
"storybook": "7.6.4",
|
"storybook": "7.6.5",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"summaly": "github:misskey-dev/summaly",
|
"summaly": "github:misskey-dev/summaly",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
|
|
|
@ -12,19 +12,16 @@ import { version, ui, lang, updateLocale, locale } from '@/config.js';
|
||||||
import { applyTheme } from '@/scripts/theme.js';
|
import { applyTheme } from '@/scripts/theme.js';
|
||||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||||
import { i18n, updateI18n } from '@/i18n.js';
|
import { i18n, updateI18n } from '@/i18n.js';
|
||||||
import { confirm, alert, post, popup, toast } from '@/os.js';
|
|
||||||
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
|
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
|
||||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import { reloadChannel } from '@/scripts/unison-reload.js';
|
import { reloadChannel } from '@/scripts/unison-reload.js';
|
||||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
|
||||||
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
||||||
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
||||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||||
import { mainRouter } from '@/router.js';
|
|
||||||
|
|
||||||
export async function common(createVue: () => App<Element>) {
|
export async function common(createVue: () => App<Element>) {
|
||||||
console.info(`Misskey v${version}`);
|
console.info(`Misskey v${version}`);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { mainRouter } from '@/router.js';
|
||||||
import { initializeSw } from '@/scripts/initialize-sw.js';
|
import { initializeSw } from '@/scripts/initialize-sw.js';
|
||||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||||
|
import { SnowfallEffect } from '@/scripts/snowfall-effect.js';
|
||||||
|
|
||||||
export async function mainBoot() {
|
export async function mainBoot() {
|
||||||
const { isClientUpdated } = await common(() => createApp(
|
const { isClientUpdated } = await common(() => createApp(
|
||||||
|
@ -75,6 +76,13 @@ export async function mainBoot() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (defaultStore.state.enableSeasonalScreenEffect) {
|
||||||
|
const month = new Date().getMonth() + 1;
|
||||||
|
if (month === 12 || month === 1) {
|
||||||
|
new SnowfallEffect().render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($i) {
|
if ($i) {
|
||||||
// only add post shortcuts if logged in
|
// only add post shortcuts if logged in
|
||||||
hotkeys['p|n'] = post;
|
hotkeys['p|n'] = post;
|
||||||
|
|
|
@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkLoading v-if="!inline ?? true"/>
|
<MkLoading v-if="!inline ?? true"/>
|
||||||
</template>
|
</template>
|
||||||
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
|
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
|
||||||
<XCode v-else-if="show" :code="code" :lang="lang"/>
|
<XCode v-else-if="show && lang" :code="code" :lang="lang"/>
|
||||||
|
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
|
||||||
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
|
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
|
||||||
<div :class="$style.codePlaceholderContainer">
|
<div :class="$style.codePlaceholderContainer">
|
||||||
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
|
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
|
||||||
|
@ -47,6 +48,21 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codeBlockFallbackRoot {
|
||||||
|
display: block;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: #D4D4D4;
|
||||||
|
background: #1E1E1E;
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlockFallbackCode {
|
||||||
|
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
.codePlaceholderRoot {
|
.codePlaceholderRoot {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -4,30 +4,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
|
<div>
|
||||||
<div :class="$style.codeEditorScroller">
|
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||||
<textarea
|
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
|
||||||
ref="inputEl"
|
<div :class="$style.codeEditorScroller">
|
||||||
v-model="vModel"
|
<textarea
|
||||||
:class="[$style.textarea]"
|
ref="inputEl"
|
||||||
:disabled="disabled"
|
v-model="vModel"
|
||||||
:required="required"
|
:class="[$style.textarea]"
|
||||||
:readonly="readonly"
|
:disabled="disabled"
|
||||||
autocomplete="off"
|
:required="required"
|
||||||
wrap="off"
|
:readonly="readonly"
|
||||||
spellcheck="false"
|
autocomplete="off"
|
||||||
@focus="focused = true"
|
wrap="off"
|
||||||
@blur="focused = false"
|
spellcheck="false"
|
||||||
@keydown="onKeydown($event)"
|
@focus="focused = true"
|
||||||
@input="onInput"
|
@blur="focused = false"
|
||||||
></textarea>
|
@keydown="onKeydown($event)"
|
||||||
<XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/>
|
@input="onInput"
|
||||||
|
></textarea>
|
||||||
|
<XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div :class="$style.caption"><slot name="caption"></slot></div>
|
||||||
|
<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
|
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
|
||||||
|
import { debounce } from 'throttle-debounce';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
import XCode from '@/components/MkCode.core.vue';
|
import XCode from '@/components/MkCode.core.vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
@ -36,6 +44,8 @@ const props = withDefaults(defineProps<{
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
debounce?: boolean;
|
||||||
|
manualSave?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
lang: 'js',
|
lang: 'js',
|
||||||
});
|
});
|
||||||
|
@ -54,6 +64,8 @@ const focused = ref(false);
|
||||||
const changed = ref(false);
|
const changed = ref(false);
|
||||||
const inputEl = shallowRef<HTMLTextAreaElement>();
|
const inputEl = shallowRef<HTMLTextAreaElement>();
|
||||||
|
|
||||||
|
const focus = () => inputEl.value?.focus();
|
||||||
|
|
||||||
const onInput = (ev) => {
|
const onInput = (ev) => {
|
||||||
v.value = ev.target?.value ?? v.value;
|
v.value = ev.target?.value ?? v.value;
|
||||||
changed.value = true;
|
changed.value = true;
|
||||||
|
@ -100,16 +112,48 @@ const updated = () => {
|
||||||
emit('update:modelValue', v.value);
|
emit('update:modelValue', v.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedUpdated = debounce(1000, updated);
|
||||||
|
|
||||||
watch(modelValue, newValue => {
|
watch(modelValue, newValue => {
|
||||||
v.value = newValue ?? '';
|
v.value = newValue ?? '';
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(v, () => {
|
watch(v, newValue => {
|
||||||
updated();
|
if (!props.manualSave) {
|
||||||
|
if (props.debounce) {
|
||||||
|
debouncedUpdated();
|
||||||
|
} else {
|
||||||
|
updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
.label {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 0 0 8px 0;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 8px 0 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.codeEditorRoot {
|
.codeEditorRoot {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -117,6 +161,7 @@ watch(v, () => {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
border-radius: 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
border: solid 1px var(--panel);
|
border: solid 1px var(--panel);
|
||||||
|
@ -157,9 +202,10 @@ watch(v, () => {
|
||||||
caret-color: rgb(225, 228, 232);
|
caret-color: rgb(225, 228, 232);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
min-width: calc(100% - 24px);
|
min-width: calc(100% - 24px);
|
||||||
height: calc(100% - 24px);
|
height: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
|
|
@ -67,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
||||||
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
||||||
|
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
||||||
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||||
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1038,6 +1039,16 @@ defineExpose({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colorBar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 12px;
|
||||||
|
width: 5px;
|
||||||
|
height: 100% ;
|
||||||
|
border-radius: 999px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.submitInner {
|
.submitInner {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
|
@ -1296,5 +1307,6 @@ defineExpose({
|
||||||
.headerRight {
|
.headerRight {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,16 +12,17 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>misskey</title>
|
<title>[DEV] Loading...</title>
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self';
|
content="default-src 'self';
|
||||||
worker-src 'self';
|
worker-src 'self';
|
||||||
script-src 'self';
|
script-src 'self' 'unsafe-eval';
|
||||||
style-src 'self' 'unsafe-inline';
|
style-src 'self' 'unsafe-inline';
|
||||||
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
|
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||||
|
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
|
||||||
/>
|
/>
|
||||||
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
|
@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts._play.summary }}</template>
|
<template #label>{{ i18n.ts._play.summary }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
<MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ti ti-chevron-down"></i></MkButton>
|
<MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ti ti-chevron-down"></i></MkButton>
|
||||||
<MkTextarea v-model="script" code tall spellcheck="false">
|
<MkCodeEditor v-model="script" lang="is">
|
||||||
<template #label>{{ i18n.ts._play.script }}</template>
|
<template #label>{{ i18n.ts._play.script }}</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
|
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
|
||||||
|
@ -40,6 +40,7 @@ import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
|
|
@ -165,12 +165,8 @@ async function run() {
|
||||||
return new Promise(ok => {
|
return new Promise(ok => {
|
||||||
os.inputText({
|
os.inputText({
|
||||||
title: q,
|
title: q,
|
||||||
}).then(({ canceled, result: a }) => {
|
}).then(({ result: a }) => {
|
||||||
if (canceled) {
|
ok(a ?? '');
|
||||||
ok('');
|
|
||||||
} else {
|
|
||||||
ok(a);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,9 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
|
|
||||||
<MkTextarea v-model="valueForEditor" tall code>
|
<MkCodeEditor v-model="valueForEditor" lang="json5">
|
||||||
<template #label>{{ i18n.ts.value }} (JSON)</template>
|
<template #label>{{ i18n.ts.value }} (JSON)</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
|
|
||||||
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import FormInfo from '@/components/MkInfo.vue';
|
import FormInfo from '@/components/MkInfo.vue';
|
||||||
|
|
||||||
|
|
|
@ -4,51 +4,56 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!loading" class="_gaps">
|
<div>
|
||||||
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
<div v-if="!loading" class="_gaps">
|
||||||
|
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||||
|
|
||||||
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
||||||
<div>{{ i18n.ts.inUse }}</div>
|
|
||||||
|
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
||||||
|
<div>{{ i18n.ts.inUse }}</div>
|
||||||
|
|
||||||
|
<div :class="$style.decorations">
|
||||||
|
<XDecoration
|
||||||
|
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||||
|
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||||
|
:angle="avatarDecoration.angle"
|
||||||
|
:flipH="avatarDecoration.flipH"
|
||||||
|
:offsetX="avatarDecoration.offsetX"
|
||||||
|
:offsetY="avatarDecoration.offsetY"
|
||||||
|
:active="true"
|
||||||
|
@click="openDecoration(avatarDecoration, i)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div :class="$style.decorations">
|
<div :class="$style.decorations">
|
||||||
<XDecoration
|
<XDecoration
|
||||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
v-for="avatarDecoration in avatarDecorations"
|
||||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
:key="avatarDecoration.id"
|
||||||
:angle="avatarDecoration.angle"
|
:decoration="avatarDecoration"
|
||||||
:flipH="avatarDecoration.flipH"
|
@click="openDecoration(avatarDecoration)"
|
||||||
:offsetX="avatarDecoration.offsetX"
|
|
||||||
:offsetY="avatarDecoration.offsetY"
|
|
||||||
:active="true"
|
|
||||||
@click="openDecoration(avatarDecoration, i)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
<div :class="$style.decorations">
|
<MkLoading/>
|
||||||
<XDecoration
|
|
||||||
v-for="avatarDecoration in avatarDecorations"
|
|
||||||
:key="avatarDecoration.id"
|
|
||||||
:decoration="avatarDecoration"
|
|
||||||
@click="openDecoration(avatarDecoration)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
|
||||||
<MkLoading/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, defineAsyncComponent } from 'vue';
|
import { ref, defineAsyncComponent, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XDecoration from './profile.avatar-decoration.decoration.vue';
|
import XDecoration from './avatar-decoration.decoration.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
|
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
|
||||||
|
@ -59,7 +64,7 @@ os.api('get-avatar-decorations').then(_avatarDecorations => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function openDecoration(avatarDecoration, index?: number) {
|
function openDecoration(avatarDecoration, index?: number) {
|
||||||
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration.dialog.vue')), {
|
os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {
|
||||||
decoration: avatarDecoration,
|
decoration: avatarDecoration,
|
||||||
usingIndex: index,
|
usingIndex: index,
|
||||||
}, {
|
}, {
|
||||||
|
@ -115,9 +120,25 @@ function detachAllDecorations() {
|
||||||
$i.avatarDecorations = [];
|
$i.avatarDecorations = [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts.avatarDecorations,
|
||||||
|
icon: 'ti ti-sparkles',
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
.avatar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
|
||||||
.current {
|
.current {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
|
@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
|
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
|
||||||
|
|
||||||
<MkTextarea v-model="localCustomCss" manualSave tall code style="tab-size: 2;">
|
<MkCodeEditor v-model="localCustomCss" manualSave lang="css">
|
||||||
<template #label>CSS</template>
|
<template #label>CSS</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed } from 'vue';
|
import { ref, watch, computed } from 'vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import FormInfo from '@/components/MkInfo.vue';
|
import FormInfo from '@/components/MkInfo.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
|
|
@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
<MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
<MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||||
<MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
<MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||||
<MkButton inline danger @click="copyFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojis }}</MkButton>
|
<MkButton inline danger @click="overwriteFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojis }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
<MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
<MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||||
<MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
<MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||||
<MkButton inline danger @click="copyFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojisForReaction }}</MkButton>
|
<MkButton inline danger @click="overwriteFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojisForReaction }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
@ -164,7 +164,7 @@ function previewEmoji(ev: MouseEvent) {
|
||||||
emojiPicker.show(getHTMLElement(ev));
|
emojiPicker.show(getHTMLElement(ev));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyFromPinnedEmojis() {
|
async function overwriteFromPinnedEmojis() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts.overwriteContentConfirm,
|
text: i18n.ts.overwriteContentConfirm,
|
||||||
|
@ -177,7 +177,7 @@ async function copyFromPinnedEmojis() {
|
||||||
pinnedEmojisForReaction.value = [...pinnedEmojis.value];
|
pinnedEmojisForReaction.value = [...pinnedEmojis.value];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyFromPinnedEmojisForReaction() {
|
async function overwriteFromPinnedEmojisForReaction() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts.overwriteContentConfirm,
|
text: i18n.ts.overwriteContentConfirm,
|
||||||
|
|
|
@ -122,6 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
|
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
|
||||||
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
|
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
|
||||||
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
|
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<MkRadios v-model="emojiStyle">
|
<MkRadios v-model="emojiStyle">
|
||||||
|
@ -289,6 +290,7 @@ const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificati
|
||||||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||||
|
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
miLocalStorage.setItem('lang', lang.value as string);
|
miLocalStorage.setItem('lang', lang.value as string);
|
||||||
|
@ -328,6 +330,7 @@ watch([
|
||||||
highlightSensitiveMedia,
|
highlightSensitiveMedia,
|
||||||
keepScreenOn,
|
keepScreenOn,
|
||||||
disableStreamingTimeline,
|
disableStreamingTimeline,
|
||||||
|
enableSeasonalScreenEffect,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk();
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<FormInfo warn>{{ i18n.ts._plugin.installWarn }}</FormInfo>
|
<FormInfo warn>{{ i18n.ts._plugin.installWarn }}</FormInfo>
|
||||||
|
|
||||||
<MkTextarea v-model="code" tall code>
|
<MkCodeEditor v-model="code" lang="is">
|
||||||
<template #label>{{ i18n.ts.code }}</template>
|
<template #label>{{ i18n.ts.code }}</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
<MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
||||||
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, ref, computed } from 'vue';
|
import { nextTick, ref, computed } from 'vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormInfo from '@/components/MkInfo.vue';
|
import FormInfo from '@/components/MkInfo.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -5,12 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
<div class="_panel">
|
||||||
|
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||||
|
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||||
|
</div>
|
||||||
<div :class="$style.avatarContainer">
|
<div :class="$style.avatarContainer">
|
||||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
|
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
|
||||||
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||||
|
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
|
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
|
||||||
|
@ -83,13 +88,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
|
|
||||||
<MkFolder>
|
|
||||||
<template #icon><i class="ti ti-sparkles"></i></template>
|
|
||||||
<template #label>{{ i18n.ts.avatarDecorations }}</template>
|
|
||||||
|
|
||||||
<XAvatarDecoration/>
|
|
||||||
</MkFolder>
|
|
||||||
|
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||||
|
|
||||||
|
@ -264,19 +262,19 @@ definePageMetadata({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.avatarAndBanner {
|
.banner {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 130px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
border: solid 1px var(--divider);
|
border-bottom: solid 1px var(--divider);
|
||||||
border-radius: 10px;
|
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatarContainer {
|
.avatarContainer {
|
||||||
display: inline-block;
|
margin-top: -50px;
|
||||||
|
padding-bottom: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
|
|
|
@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkTextarea v-model="installThemeCode" code>
|
<MkCodeEditor v-model="installThemeCode" lang="json5">
|
||||||
<template #label>{{ i18n.ts._theme.code }}</template>
|
<template #label>{{ i18n.ts._theme.code }}</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
|
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
<MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
<MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||||
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
|
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -51,9 +51,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.editCode }}</template>
|
<template #label>{{ i18n.ts.editCode }}</template>
|
||||||
|
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkTextarea v-model="themeCode" tall code>
|
<MkCodeEditor v-model="themeCode" lang="json5">
|
||||||
<template #label>{{ i18n.ts._theme.code }}</template>
|
<template #label>{{ i18n.ts._theme.code }}</template>
|
||||||
</MkTextarea>
|
</MkCodeEditor>
|
||||||
<MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton>
|
<MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
@ -80,6 +80,7 @@ import { v4 as uuid } from 'uuid';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
|
|
|
@ -136,9 +136,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkLazy>
|
</MkLazy>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="!disableNotes">
|
<div v-if="!disableNotes">
|
||||||
<div style="margin-bottom: 8px;">{{ i18n.ts.featured }}</div>
|
|
||||||
<MkLazy>
|
<MkLazy>
|
||||||
<MkNotes :class="$style.tl" :noGap="true" :pagination="pagination"/>
|
<XTimeline :user="user"/>
|
||||||
</MkLazy>
|
</MkLazy>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -193,6 +192,7 @@ function calcAge(birthdate: string): number {
|
||||||
|
|
||||||
const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
|
const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
|
||||||
const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
|
const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
|
||||||
|
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
|
@ -219,14 +219,6 @@ watch(moderationNote, async () => {
|
||||||
await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
|
await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: 'users/featured-notes' as const,
|
|
||||||
limit: 10,
|
|
||||||
params: computed(() => ({
|
|
||||||
userId: props.user.id,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
const style = computed(() => {
|
const style = computed(() => {
|
||||||
if (props.user.bannerUrl == null) return {};
|
if (props.user.bannerUrl == null) return {};
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,18 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="800" style="padding-top: 0">
|
<MkStickyContainer>
|
||||||
<MkStickyContainer>
|
<template #header>
|
||||||
<template #header>
|
<MkTab v-model="tab" :class="$style.tab">
|
||||||
<MkTab v-model="include" :class="$style.tab">
|
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
<option :value="null">{{ i18n.ts.notes }}</option>
|
||||||
<option value="all">{{ i18n.ts.all }}</option>
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
</template>
|
</template>
|
||||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</MkSpacer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -29,24 +28,29 @@ const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const include = ref<string | null>('all');
|
const tab = ref<string | null>('all');
|
||||||
|
|
||||||
const pagination = {
|
const pagination = computed(() => tab.value === 'featured' ? {
|
||||||
|
endpoint: 'users/featured-notes' as const,
|
||||||
|
limit: 10,
|
||||||
|
params: {
|
||||||
|
userId: props.user.id,
|
||||||
|
},
|
||||||
|
} : {
|
||||||
endpoint: 'users/notes' as const,
|
endpoint: 'users/notes' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
params: {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withRenotes: include.value === 'all',
|
withRenotes: tab.value === 'all',
|
||||||
withReplies: include.value === 'all',
|
withReplies: tab.value === 'all',
|
||||||
withChannelNotes: include.value === 'all',
|
withChannelNotes: tab.value === 'all',
|
||||||
withFiles: include.value === 'files',
|
withFiles: tab.value === 'files',
|
||||||
})),
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.tab {
|
.tab {
|
||||||
margin: calc(var(--margin) / 2) 0;
|
|
||||||
padding: calc(var(--margin) / 2) 0;
|
padding: calc(var(--margin) / 2) 0;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>
|
<div>
|
||||||
<div v-if="user">
|
<div v-if="user">
|
||||||
<XHome v-if="tab === 'home'" :user="user"/>
|
<XHome v-if="tab === 'home'" :user="user"/>
|
||||||
<XTimeline v-else-if="tab === 'notes'" :user="user"/>
|
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
|
||||||
|
<XTimeline :user="user"/>
|
||||||
|
</MkSpacer>
|
||||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
||||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
||||||
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
||||||
|
|
|
@ -54,6 +54,10 @@ export const routes = [{
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
component: page(() => import('./pages/settings/profile.vue')),
|
component: page(() => import('./pages/settings/profile.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decoration',
|
||||||
|
name: 'avatarDecoration',
|
||||||
|
component: page(() => import('./pages/settings/avatar-decoration.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/roles',
|
path: '/roles',
|
||||||
name: 'roles',
|
name: 'roles',
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -412,6 +412,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
code: false,
|
code: false,
|
||||||
} as Record<string, boolean>,
|
} as Record<string, boolean>,
|
||||||
},
|
},
|
||||||
|
enableSeasonalScreenEffect: {
|
||||||
|
where: 'device',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, test, assert, afterEach } from 'vitest';
|
import { afterEach, assert, describe, test } from 'vitest';
|
||||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
import { cleanup, render, type RenderResult } from '@testing-library/vue';
|
||||||
import './init';
|
import './init';
|
||||||
import type * as Misskey from 'misskey-js';
|
import type * as Misskey from 'misskey-js';
|
||||||
import { directives } from '@/directives/index.js';
|
import { directives } from '@/directives/index.js';
|
||||||
import { components } from '@/components/index.js';
|
import { components } from '@/components/index.js';
|
||||||
import XHome from '@/pages/user/home.vue';
|
import XHome from '@/pages/user/home.vue';
|
||||||
|
import 'intersection-observer';
|
||||||
|
|
||||||
describe('XHome', () => {
|
describe('XHome', () => {
|
||||||
const renderHome = (user: Partial<Misskey.entities.UserDetailed>): RenderResult => {
|
const renderHome = (user: Partial<Misskey.entities.UserDetailed>): RenderResult => {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"@types/node": "20.10.4",
|
"@types/node": "20.10.4",
|
||||||
"@typescript-eslint/eslint-plugin": "6.14.0",
|
"@typescript-eslint/eslint-plugin": "6.14.0",
|
||||||
"@typescript-eslint/parser": "6.14.0",
|
"@typescript-eslint/parser": "6.14.0",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
"jest-websocket-mock": "2.5.0",
|
"jest-websocket-mock": "2.5.0",
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "6.14.0",
|
"@typescript-eslint/parser": "6.14.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-plugin-import": "2.29.0",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"nodemon": "3.0.2",
|
"nodemon": "3.0.2",
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3"
|
||||||
},
|
},
|
||||||
|
|
1219
pnpm-lock.yaml
1219
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue