Compare commits
11 Commits
2025.3.2-a
...
refine-piz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2402754dcc | ||
|
|
2493592bd0 | ||
|
|
eec4ab841a | ||
|
|
d0b8ffe629 | ||
|
|
cef7575b76 | ||
|
|
9842eb2eeb | ||
|
|
05078e9c14 | ||
|
|
db5c6fa3c2 | ||
|
|
8a4e2659ed | ||
|
|
d19c094a9b | ||
|
|
a7f7ff33e7 |
@@ -6,16 +6,12 @@
|
|||||||
### Client
|
### Client
|
||||||
- Feat: 設定の管理が強化されました
|
- Feat: 設定の管理が強化されました
|
||||||
- 自動でバックアップされるように
|
- 自動でバックアップされるように
|
||||||
- 任意の設定項目をデバイス間で同期できるように(実験的)
|
|
||||||
- Enhance: プラグインの管理が強化されました
|
- Enhance: プラグインの管理が強化されました
|
||||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
|
||||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
|
||||||
- Enhance: テーマ設定画面のデザインを改善
|
|
||||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
|
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
|
||||||
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
|
|
||||||
|
|
||||||
## 2025.3.1
|
## 2025.3.1
|
||||||
|
|
||||||
|
|||||||
BIN
assets/about/drive.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
assets/about/post.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
assets/about/reaction.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/about/ui.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
assets/ss/explore.jpg
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
assets/ss/user.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
102
locales/index.d.ts
vendored
@@ -5310,96 +5310,6 @@ export interface Locale extends ILocale {
|
|||||||
* 復元
|
* 復元
|
||||||
*/
|
*/
|
||||||
"restore": string;
|
"restore": string;
|
||||||
/**
|
|
||||||
* デバイス間で同期
|
|
||||||
*/
|
|
||||||
"syncBetweenDevices": string;
|
|
||||||
/**
|
|
||||||
* サーバーに設定値が存在します
|
|
||||||
*/
|
|
||||||
"preferenceSyncConflictTitle": string;
|
|
||||||
/**
|
|
||||||
* 同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?
|
|
||||||
*/
|
|
||||||
"preferenceSyncConflictText": string;
|
|
||||||
/**
|
|
||||||
* サーバーの設定値
|
|
||||||
*/
|
|
||||||
"preferenceSyncConflictChoiceServer": string;
|
|
||||||
/**
|
|
||||||
* デバイスの設定値
|
|
||||||
*/
|
|
||||||
"preferenceSyncConflictChoiceDevice": string;
|
|
||||||
/**
|
|
||||||
* 同期の有効化をキャンセル
|
|
||||||
*/
|
|
||||||
"preferenceSyncConflictChoiceCancel": string;
|
|
||||||
"_settings": {
|
|
||||||
/**
|
|
||||||
* ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。
|
|
||||||
*/
|
|
||||||
"driveBanner": string;
|
|
||||||
/**
|
|
||||||
* プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。
|
|
||||||
*/
|
|
||||||
"pluginBanner": string;
|
|
||||||
/**
|
|
||||||
* サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。
|
|
||||||
*/
|
|
||||||
"notificationsBanner": string;
|
|
||||||
/**
|
|
||||||
* API
|
|
||||||
*/
|
|
||||||
"api": string;
|
|
||||||
/**
|
|
||||||
* Webhook
|
|
||||||
*/
|
|
||||||
"webhook": string;
|
|
||||||
/**
|
|
||||||
* サービス連携
|
|
||||||
*/
|
|
||||||
"serviceConnection": string;
|
|
||||||
/**
|
|
||||||
* 外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。
|
|
||||||
*/
|
|
||||||
"serviceConnectionBanner": string;
|
|
||||||
/**
|
|
||||||
* アカウントのデータ
|
|
||||||
*/
|
|
||||||
"accountData": string;
|
|
||||||
/**
|
|
||||||
* アカウントのデータをエクスポート/インポートして管理できます。
|
|
||||||
*/
|
|
||||||
"accountDataBanner": string;
|
|
||||||
/**
|
|
||||||
* 非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。
|
|
||||||
*/
|
|
||||||
"muteAndBlockBanner": string;
|
|
||||||
/**
|
|
||||||
* クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。
|
|
||||||
*/
|
|
||||||
"accessibilityBanner": string;
|
|
||||||
/**
|
|
||||||
* コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。
|
|
||||||
*/
|
|
||||||
"privacyBanner": string;
|
|
||||||
/**
|
|
||||||
* パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。
|
|
||||||
*/
|
|
||||||
"securityBanner": string;
|
|
||||||
/**
|
|
||||||
* 好みに応じた、クライアントの全体的な動作の設定が行えます。
|
|
||||||
*/
|
|
||||||
"preferencesBanner": string;
|
|
||||||
/**
|
|
||||||
* 好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。
|
|
||||||
*/
|
|
||||||
"appearanceBanner": string;
|
|
||||||
/**
|
|
||||||
* クライアントで再生するサウンドの設定が行えます。
|
|
||||||
*/
|
|
||||||
"soundsBanner": string;
|
|
||||||
};
|
|
||||||
"_preferencesProfile": {
|
"_preferencesProfile": {
|
||||||
/**
|
/**
|
||||||
* プロファイル名
|
* プロファイル名
|
||||||
@@ -5485,10 +5395,6 @@ export interface Locale extends ILocale {
|
|||||||
* リモートサーバーに連合されたノートには効果が及ばない場合があります。
|
* リモートサーバーに連合されたノートには効果が及ばない場合があります。
|
||||||
*/
|
*/
|
||||||
"mayNotEffectForFederatedNotes": string;
|
"mayNotEffectForFederatedNotes": string;
|
||||||
/**
|
|
||||||
* これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。
|
|
||||||
*/
|
|
||||||
"mayNotEffectSomeSituations": string;
|
|
||||||
/**
|
/**
|
||||||
* 指定した時間を経過しているノート
|
* 指定した時間を経過しているノート
|
||||||
*/
|
*/
|
||||||
@@ -7836,10 +7742,6 @@ export interface Locale extends ILocale {
|
|||||||
* 標準のテーマ
|
* 標準のテーマ
|
||||||
*/
|
*/
|
||||||
"builtinThemes": string;
|
"builtinThemes": string;
|
||||||
/**
|
|
||||||
* サーバーのテーマ
|
|
||||||
*/
|
|
||||||
"instanceTheme": string;
|
|
||||||
/**
|
/**
|
||||||
* そのテーマは既にインストールされています
|
* そのテーマは既にインストールされています
|
||||||
*/
|
*/
|
||||||
@@ -9848,10 +9750,6 @@ export interface Locale extends ILocale {
|
|||||||
* 幅を自動調整
|
* 幅を自動調整
|
||||||
*/
|
*/
|
||||||
"flexible": string;
|
"flexible": string;
|
||||||
/**
|
|
||||||
* プロファイル情報のデバイス間同期を有効にする
|
|
||||||
*/
|
|
||||||
"enableSyncBetweenDevicesForProfiles": string;
|
|
||||||
"_columns": {
|
"_columns": {
|
||||||
/**
|
/**
|
||||||
* メイン
|
* メイン
|
||||||
|
|||||||
@@ -1323,30 +1323,6 @@ untitled: "無題"
|
|||||||
noName: "名前はありません"
|
noName: "名前はありません"
|
||||||
skip: "スキップ"
|
skip: "スキップ"
|
||||||
restore: "復元"
|
restore: "復元"
|
||||||
syncBetweenDevices: "デバイス間で同期"
|
|
||||||
preferenceSyncConflictTitle: "サーバーに設定値が存在します"
|
|
||||||
preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?"
|
|
||||||
preferenceSyncConflictChoiceServer: "サーバーの設定値"
|
|
||||||
preferenceSyncConflictChoiceDevice: "デバイスの設定値"
|
|
||||||
preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
|
|
||||||
|
|
||||||
_settings:
|
|
||||||
driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。"
|
|
||||||
pluginBanner: "プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。"
|
|
||||||
notificationsBanner: "サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。"
|
|
||||||
api: "API"
|
|
||||||
webhook: "Webhook"
|
|
||||||
serviceConnection: "サービス連携"
|
|
||||||
serviceConnectionBanner: "外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。"
|
|
||||||
accountData: "アカウントのデータ"
|
|
||||||
accountDataBanner: "アカウントのデータをエクスポート/インポートして管理できます。"
|
|
||||||
muteAndBlockBanner: "非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。"
|
|
||||||
accessibilityBanner: "クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。"
|
|
||||||
privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。"
|
|
||||||
securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。"
|
|
||||||
preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定が行えます。"
|
|
||||||
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
|
||||||
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
|
||||||
|
|
||||||
_preferencesProfile:
|
_preferencesProfile:
|
||||||
profileName: "プロファイル名"
|
profileName: "プロファイル名"
|
||||||
@@ -1373,7 +1349,6 @@ _accountSettings:
|
|||||||
makeNotesHiddenBefore: "過去のノートを非公開化する"
|
makeNotesHiddenBefore: "過去のノートを非公開化する"
|
||||||
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
|
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
|
||||||
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
|
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
|
||||||
mayNotEffectSomeSituations: "これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。"
|
|
||||||
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
|
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
|
||||||
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
|
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
|
||||||
|
|
||||||
@@ -2055,7 +2030,6 @@ _theme:
|
|||||||
installed: "{name}をインストールしました"
|
installed: "{name}をインストールしました"
|
||||||
installedThemes: "インストールされたテーマ"
|
installedThemes: "インストールされたテーマ"
|
||||||
builtinThemes: "標準のテーマ"
|
builtinThemes: "標準のテーマ"
|
||||||
instanceTheme: "サーバーのテーマ"
|
|
||||||
alreadyInstalled: "そのテーマは既にインストールされています"
|
alreadyInstalled: "そのテーマは既にインストールされています"
|
||||||
invalid: "テーマの形式が間違っています"
|
invalid: "テーマの形式が間違っています"
|
||||||
make: "テーマを作る"
|
make: "テーマを作る"
|
||||||
@@ -2603,7 +2577,6 @@ _deck:
|
|||||||
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
|
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
|
||||||
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
|
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
|
||||||
flexible: "幅を自動調整"
|
flexible: "幅を自動調整"
|
||||||
enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする"
|
|
||||||
|
|
||||||
_columns:
|
_columns:
|
||||||
main: "メイン"
|
main: "メイン"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.3.2-alpha.9",
|
"version": "2025.3.2-alpha.4",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"build-frontend-search-index": "pnpm --filter frontend build-search-index",
|
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
@@ -66,12 +65,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||||
"@types/node": "22.13.10",
|
"@types/node": "22.13.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.26.0",
|
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||||
"@typescript-eslint/parser": "8.26.0",
|
"@typescript-eslint/parser": "8.26.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "14.1.0",
|
"cypress": "14.1.0",
|
||||||
"eslint": "9.22.0",
|
"eslint": "9.21.0",
|
||||||
"globals": "16.0.0",
|
"globals": "16.0.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"pnpm": "10.6.1",
|
"pnpm": "10.6.1",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { Config } from '@/config.js';
|
|||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
import { assertActivityMatchesUrl, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import type { IObject } from '@/core/activitypub/type.js';
|
import type { IObject } from '@/core/activitypub/type.js';
|
||||||
import type { Response } from 'node-fetch';
|
import type { Response } from 'node-fetch';
|
||||||
import type { URL } from 'node:url';
|
import type { URL } from 'node:url';
|
||||||
@@ -265,7 +265,7 @@ export class HttpRequestService {
|
|||||||
const finalUrl = res.url; // redirects may have been involved
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
const activity = await res.json() as IObject;
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
assertActivityMatchesUrl(url, activity, finalUrl, allowSoftfail);
|
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
import { assertActivityMatchesUrl, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import type { IObject } from './type.js';
|
import type { IObject } from './type.js';
|
||||||
|
|
||||||
type Request = {
|
type Request = {
|
||||||
@@ -258,7 +258,7 @@ export class ApRequestService {
|
|||||||
const finalUrl = res.url; // redirects may have been involved
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
const activity = await res.json() as IObject;
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
assertActivityMatchesUrl(url, activity, finalUrl, allowSoftfail);
|
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ function normalizeSynonymousSubdomain(url: URL | string): URL {
|
|||||||
return new URL(urlParsed.toString().replace(host, normalizedHost));
|
return new URL(urlParsed.toString().replace(host, normalizedHost));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertActivityMatchesUrl(requestUrl: string | URL, activity: IObject, finalUrl: string | URL, allowSoftfail: FetchAllowSoftFailMask): FetchAllowSoftFailMask {
|
export function assertActivityMatchesUrls(requestUrl: string | URL, activity: IObject, candidateUrls: (string | URL)[], allowSoftfail: FetchAllowSoftFailMask): FetchAllowSoftFailMask {
|
||||||
// must have a unique identifier to verify authority
|
// must have a unique identifier to verify authority
|
||||||
if (!activity.id) {
|
if (!activity.id) {
|
||||||
throw new Error('bad Activity: missing id field');
|
throw new Error('bad Activity: missing id field');
|
||||||
@@ -95,32 +95,26 @@ export function assertActivityMatchesUrl(requestUrl: string | URL, activity: IOb
|
|||||||
const requestUrlParsed = normalizeSynonymousSubdomain(requestUrl);
|
const requestUrlParsed = normalizeSynonymousSubdomain(requestUrl);
|
||||||
const idParsed = normalizeSynonymousSubdomain(activity.id);
|
const idParsed = normalizeSynonymousSubdomain(activity.id);
|
||||||
|
|
||||||
const finalUrlParsed = normalizeSynonymousSubdomain(finalUrl);
|
const candidateUrlsParsed = candidateUrls.map(it => normalizeSynonymousSubdomain(it));
|
||||||
|
|
||||||
// mastodon sends activities with hash in the URL
|
|
||||||
// currently it only happens with likes, deletes etc.
|
|
||||||
// but object ID never has hash
|
|
||||||
requestUrlParsed.hash = '';
|
|
||||||
finalUrlParsed.hash = '';
|
|
||||||
|
|
||||||
const requestUrlSecure = requestUrlParsed.protocol === 'https:';
|
const requestUrlSecure = requestUrlParsed.protocol === 'https:';
|
||||||
const finalUrlSecure = finalUrlParsed.protocol === 'https:';
|
const finalUrlSecure = candidateUrlsParsed.every(it => it.protocol === 'https:');
|
||||||
if (requestUrlSecure && !finalUrlSecure) {
|
if (requestUrlSecure && !finalUrlSecure) {
|
||||||
throw new Error(`bad Activity: id(${activity.id}) is not allowed to have http:// in the url`);
|
throw new Error(`bad Activity: id(${activity.id}) is not allowed to have http:// in the url`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare final URL to the ID
|
// Compare final URL to the ID
|
||||||
if (finalUrlParsed.href !== idParsed.href) {
|
if (!candidateUrlsParsed.some(it => it.href === idParsed.href)) {
|
||||||
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match response url(${finalUrlParsed.toString()})`);
|
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match response url(${candidateUrlsParsed.map(it => it.toString())})`);
|
||||||
|
|
||||||
// at lease host need to match exactly (ActivityPub requirement)
|
// at lease host need to match exactly (ActivityPub requirement)
|
||||||
if (idParsed.host !== finalUrlParsed.host) {
|
if (!candidateUrlsParsed.some(it => idParsed.host === it.host)) {
|
||||||
throw new Error(`bad Activity: id(${activity.id}) does not match response host(${finalUrlParsed.host})`);
|
throw new Error(`bad Activity: id(${activity.id}) does not match response host(${candidateUrlsParsed.map(it => it.host)})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare request URL to the ID
|
// Compare request URL to the ID
|
||||||
if (requestUrlParsed.href !== idParsed.href) {
|
if (!requestUrlParsed.href.includes(idParsed.href)) {
|
||||||
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match request url(${requestUrlParsed.toString()})`);
|
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match request url(${requestUrlParsed.toString()})`);
|
||||||
|
|
||||||
// if cross-origin lookup is allowed, we can accept some variation between the original request URL to the final object ID (but not between the final URL and the object ID)
|
// if cross-origin lookup is allowed, we can accept some variation between the original request URL to the final object ID (but not between the final URL and the object ID)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import httpSignature from '@peertube/http-signature';
|
|||||||
|
|
||||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||||
import { assertActivityMatchesUrl, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import { IObject } from '@/core/activitypub/type.js';
|
import { IObject } from '@/core/activitypub/type.js';
|
||||||
|
|
||||||
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
||||||
@@ -66,26 +66,23 @@ describe('ap-request', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('rejects non matching domain', () => {
|
test('rejects non matching domain', () => {
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://alice.example.com/abc' } as IObject,
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
||||||
'https://alice.example.com/abc',
|
[
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'validation should pass base case');
|
), 'validation should pass base case');
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://bob.example.com/abc' } as IObject,
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
'https://alice.example.com/abc',
|
[
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Any,
|
FetchAllowSoftFailMask.Any,
|
||||||
), 'validation should fail no matter what if the response URL is inconsistent with the object ID');
|
), 'validation should fail no matter what if the response URL is inconsistent with the object ID');
|
||||||
|
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
||||||
'https://alice.example.com/abc#test',
|
|
||||||
{ id: 'https://alice.example.com/abc' } as IObject,
|
|
||||||
'https://alice.example.com/abc',
|
|
||||||
FetchAllowSoftFailMask.Strict,
|
|
||||||
), 'validation should pass with hash in request URL');
|
|
||||||
|
|
||||||
// fix issues like threads
|
// fix issues like threads
|
||||||
// https://github.com/misskey-dev/misskey/issues/15039
|
// https://github.com/misskey-dev/misskey/issues/15039
|
||||||
const withOrWithoutWWW = [
|
const withOrWithoutWWW = [
|
||||||
@@ -100,71 +97,89 @@ describe('ap-request', () => {
|
|||||||
),
|
),
|
||||||
withOrWithoutWWW,
|
withOrWithoutWWW,
|
||||||
).forEach(([[a, b], c]) => {
|
).forEach(([[a, b], c]) => {
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
a,
|
a,
|
||||||
{ id: b } as IObject,
|
{ id: b } as IObject,
|
||||||
c,
|
[
|
||||||
|
c,
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'validation should pass with or without www. subdomain');
|
), 'validation should pass with or without www. subdomain');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cross origin lookup', () => {
|
test('cross origin lookup', () => {
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://bob.example.com/abc' } as IObject,
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
'https://bob.example.com/abc',
|
[
|
||||||
|
'https://bob.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
), 'validation should pass if the response is otherwise consistent and cross-origin is allowed');
|
), 'validation should pass if the response is otherwise consistent and cross-origin is allowed');
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://bob.example.com/abc' } as IObject,
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
'https://bob.example.com/abc',
|
[
|
||||||
|
'https://bob.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
|
), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rejects non-canonical ID', () => {
|
test('rejects non-canonical ID', () => {
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/@alice',
|
'https://alice.example.com/@alice',
|
||||||
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
||||||
'https://alice.example.com/users/alice',
|
[
|
||||||
|
'https://alice.example.com/users/alice'
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'throws if the response ID did not exactly match the expected ID');
|
), 'throws if the response ID did not exactly match the expected ID');
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/@alice',
|
'https://alice.example.com/@alice',
|
||||||
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
||||||
'https://alice.example.com/users/alice',
|
[
|
||||||
|
'https://alice.example.com/users/alice',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.NonCanonicalId,
|
FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
), 'does not throw if non-canonical ID is allowed');
|
), 'does not throw if non-canonical ID is allowed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('origin relaxed alignment', () => {
|
test('origin relaxed alignment', () => {
|
||||||
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
||||||
'https://ap.alice.example.com/abc',
|
[
|
||||||
|
'https://ap.alice.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
), 'validation should pass if response is a subdomain of the expected origin');
|
), 'validation should pass if response is a subdomain of the expected origin');
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.multi-tenant.example.com/abc',
|
'https://alice.multi-tenant.example.com/abc',
|
||||||
{ id: 'https://alice.multi-tenant.example.com/abc' } as IObject,
|
{ id: 'https://alice.multi-tenant.example.com/abc' } as IObject,
|
||||||
'https://bob.multi-tenant.example.com/abc',
|
[
|
||||||
|
'https://bob.multi-tenant.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
), 'validation should fail if response is a disjoint domain of the expected origin');
|
), 'validation should fail if response is a disjoint domain of the expected origin');
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
||||||
'https://ap.alice.example.com/abc',
|
[
|
||||||
|
'https://ap.alice.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'throws if relaxed origin is forbidden');
|
), 'throws if relaxed origin is forbidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resist HTTP downgrade', () => {
|
test('resist HTTP downgrade', () => {
|
||||||
assert.throws(() => assertActivityMatchesUrl(
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
'https://alice.example.com/abc',
|
'https://alice.example.com/abc',
|
||||||
{ id: 'https://alice.example.com/abc' } as IObject,
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
||||||
'http://alice.example.com/abc',
|
[
|
||||||
|
'http://alice.example.com/abc',
|
||||||
|
],
|
||||||
FetchAllowSoftFailMask.Strict,
|
FetchAllowSoftFailMask.Strict,
|
||||||
), 'throws if HTTP downgrade is detected');
|
), 'throws if HTTP downgrade is detected');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,52 +17,8 @@ interface SatisfiesExpression extends estree.BaseExpression {
|
|||||||
reference: estree.Identifier;
|
reference: estree.Identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportDeclaration extends estree.ImportDeclaration {
|
|
||||||
kind?: 'type';
|
|
||||||
}
|
|
||||||
|
|
||||||
const generator = {
|
const generator = {
|
||||||
...GENERATOR,
|
...GENERATOR,
|
||||||
ImportDeclaration(node: ImportDeclaration, state: State) {
|
|
||||||
state.write('import ');
|
|
||||||
if (node.kind === 'type') state.write('type ');
|
|
||||||
const { specifiers } = node;
|
|
||||||
if (specifiers.length > 0) {
|
|
||||||
let i = 0;
|
|
||||||
for (; i < specifiers.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
state.write(', ');
|
|
||||||
}
|
|
||||||
const specifier = specifiers[i]!;
|
|
||||||
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
||||||
state.write(specifier.local.name, specifier);
|
|
||||||
} else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
||||||
state.write(`* as ${specifier.local.name}`, specifier);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i < specifiers.length) {
|
|
||||||
state.write('{');
|
|
||||||
for (; i < specifiers.length; i++) {
|
|
||||||
const specifier = specifiers[i]! as estree.ImportSpecifier;
|
|
||||||
const { name } = specifier.imported as estree.Identifier;
|
|
||||||
state.write(name, specifier);
|
|
||||||
if (name !== specifier.local.name) {
|
|
||||||
state.write(` as ${specifier.local.name}`);
|
|
||||||
}
|
|
||||||
if (i < specifiers.length - 1) {
|
|
||||||
state.write(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.write('}');
|
|
||||||
}
|
|
||||||
state.write(' from ');
|
|
||||||
}
|
|
||||||
this.Literal(node.source, state);
|
|
||||||
|
|
||||||
state.write(';');
|
|
||||||
},
|
|
||||||
SatisfiesExpression(node: SatisfiesExpression, state: State) {
|
SatisfiesExpression(node: SatisfiesExpression, state: State) {
|
||||||
switch (node.expression.type) {
|
switch (node.expression.type) {
|
||||||
case 'ArrowFunctionExpression': {
|
case 'ArrowFunctionExpression': {
|
||||||
@@ -106,7 +62,7 @@ type ToKebab<T extends readonly string[]> = T extends readonly [
|
|||||||
: T extends readonly [
|
: T extends readonly [
|
||||||
infer XH extends string,
|
infer XH extends string,
|
||||||
...infer XR extends readonly string[]
|
...infer XR extends readonly string[]
|
||||||
]
|
]
|
||||||
? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
|
? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
@@ -176,7 +132,7 @@ function toStories(component: string): Promise<string> {
|
|||||||
kind={'init' as const}
|
kind={'init' as const}
|
||||||
shorthand
|
shorthand
|
||||||
/> as estree.Property,
|
/> as estree.Property,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]}
|
]}
|
||||||
/> as estree.ObjectExpression;
|
/> as estree.ObjectExpression;
|
||||||
@@ -199,8 +155,7 @@ function toStories(component: string): Promise<string> {
|
|||||||
/> as estree.ImportSpecifier,
|
/> as estree.ImportSpecifier,
|
||||||
]),
|
]),
|
||||||
]}
|
]}
|
||||||
kind={'type'}
|
/> as estree.ImportDeclaration,
|
||||||
/> as ImportDeclaration,
|
|
||||||
...(hasMsw
|
...(hasMsw
|
||||||
? [
|
? [
|
||||||
<import-declaration
|
<import-declaration
|
||||||
@@ -210,8 +165,8 @@ function toStories(component: string): Promise<string> {
|
|||||||
local={<identifier name='msw' /> as estree.Identifier}
|
local={<identifier name='msw' /> as estree.Identifier}
|
||||||
/> as estree.ImportNamespaceSpecifier,
|
/> as estree.ImportNamespaceSpecifier,
|
||||||
]}
|
]}
|
||||||
/> as ImportDeclaration,
|
/> as estree.ImportDeclaration,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(hasImplStories
|
...(hasImplStories
|
||||||
? []
|
? []
|
||||||
@@ -221,8 +176,8 @@ function toStories(component: string): Promise<string> {
|
|||||||
specifiers={[
|
specifiers={[
|
||||||
<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
|
<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
|
||||||
]}
|
]}
|
||||||
/> as ImportDeclaration,
|
/> as estree.ImportDeclaration,
|
||||||
]),
|
]),
|
||||||
...(hasMetaStories
|
...(hasMetaStories
|
||||||
? [
|
? [
|
||||||
<import-declaration
|
<import-declaration
|
||||||
@@ -232,7 +187,7 @@ function toStories(component: string): Promise<string> {
|
|||||||
local={<identifier name='storiesMeta' /> as estree.Identifier}
|
local={<identifier name='storiesMeta' /> as estree.Identifier}
|
||||||
/> as estree.ImportNamespaceSpecifier,
|
/> as estree.ImportNamespaceSpecifier,
|
||||||
]}
|
]}
|
||||||
/> as ImportDeclaration,
|
/> as estree.ImportDeclaration,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<variable-declaration
|
<variable-declaration
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
@@ -1428,23 +1428,6 @@ async function processVueFile(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSearchIndex(options: Options, transformedCodeCache: Record<string, string> = {}) {
|
|
||||||
const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
|
|
||||||
const matchedFiles = glob.sync(filePathPattern);
|
|
||||||
return [...acc, ...matchedFiles];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
const id = path.resolve(filePath); // 絶対パスに変換
|
|
||||||
const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む
|
|
||||||
const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す
|
|
||||||
transformedCodeCache = newCache; // キャッシュを更新
|
|
||||||
}
|
|
||||||
|
|
||||||
await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行
|
|
||||||
|
|
||||||
return transformedCodeCache; // キャッシュを返す
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollup プラグインとして export
|
// Rollup プラグインとして export
|
||||||
export default function pluginCreateSearchIndex(options: Options): Plugin {
|
export default function pluginCreateSearchIndex(options: Options): Plugin {
|
||||||
@@ -1462,7 +1445,19 @@ export default function pluginCreateSearchIndex(options: Options): Plugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
transformedCodeCache = await generateSearchIndex(options, transformedCodeCache);
|
const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
|
||||||
|
const matchedFiles = glob.sync(filePathPattern);
|
||||||
|
return [...acc, ...matchedFiles];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
for (const filePath of filePaths) {
|
||||||
|
const id = path.resolve(filePath); // 絶対パスに変換
|
||||||
|
const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む
|
||||||
|
const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す
|
||||||
|
transformedCodeCache = newCache; // キャッシュを更新
|
||||||
|
}
|
||||||
|
|
||||||
|
await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行
|
||||||
},
|
},
|
||||||
|
|
||||||
async transform(code, id) {
|
async transform(code, id) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "vite",
|
"watch": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build-search-index": "vite-node --config \"./vite-node.config.ts\" \"./scripts/generate-search-index.ts\"",
|
|
||||||
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
||||||
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
||||||
"build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
|
"build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
|
||||||
@@ -134,7 +133,6 @@
|
|||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"storybook": "8.6.4",
|
"storybook": "8.6.4",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-node": "3.0.8",
|
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.0.8",
|
"vitest": "3.0.8",
|
||||||
"vitest-fetch-mock": "0.4.5",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { searchIndexes } from '../vite.config.js';
|
|
||||||
import { generateSearchIndex } from '../lib/vite-plugin-create-search-index.js';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
for (const searchIndex of searchIndexes) {
|
|
||||||
await generateSearchIndex(searchIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -17,7 +17,6 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||||||
import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
|
import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
// TODO: accountsはpreferences管理にする(tokenは別管理)
|
|
||||||
|
|
||||||
type Account = Misskey.entities.MeDetailed & { token: string };
|
type Account = Misskey.entities.MeDetailed & { token: string };
|
||||||
|
|
||||||
|
|||||||
@@ -180,12 +180,12 @@ export async function common(createVue: () => App<Element>) {
|
|||||||
|
|
||||||
//#region Sync dark mode
|
//#region Sync dark mode
|
||||||
if (prefer.s.syncDeviceDarkMode) {
|
if (prefer.s.syncDeviceDarkMode) {
|
||||||
store.set('darkMode', isDeviceDarkmode());
|
store.commit('darkMode', isDeviceDarkmode());
|
||||||
}
|
}
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
|
||||||
if (prefer.s.syncDeviceDarkMode) {
|
if (prefer.s.syncDeviceDarkMode) {
|
||||||
store.set('darkMode', mql.matches);
|
store.commit('darkMode', mql.matches);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@@ -6,11 +6,9 @@
|
|||||||
import { createApp, defineAsyncComponent, markRaw } from 'vue';
|
import { createApp, defineAsyncComponent, markRaw } from 'vue';
|
||||||
import { ui } from '@@/js/config.js';
|
import { ui } from '@@/js/config.js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { common } from './common.js';
|
import { common } from './common.js';
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import type { Keymap } from '@/utility/hotkey.js';
|
import type { Keymap } from '@/utility/hotkey.js';
|
||||||
import type { DeckProfile } from '@/deck.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { alert, confirm, popup, post, toast } from '@/os.js';
|
import { alert, confirm, popup, post, toast } from '@/os.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
@@ -145,34 +143,12 @@ export async function mainBoot() {
|
|||||||
if (themes.length > 0) {
|
if (themes.length > 0) {
|
||||||
prefer.commit('themes', themes);
|
prefer.commit('themes', themes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = ColdDeviceStorage.get('plugins');
|
const plugins = ColdDeviceStorage.get('plugins');
|
||||||
prefer.commit('plugins', plugins.map(p => ({
|
prefer.commit('plugins', plugins.map(p => ({
|
||||||
...p,
|
...p,
|
||||||
installId: (p as any).id,
|
installId: (p as any).id,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
prefer.commit('deck.profile', deckStore.s.profile);
|
|
||||||
misskeyApi('i/registry/keys', {
|
|
||||||
scope: ['client', 'deck', 'profiles'],
|
|
||||||
}).then(async keys => {
|
|
||||||
const profiles: DeckProfile[] = [];
|
|
||||||
for (const key of keys) {
|
|
||||||
const deck = await misskeyApi('i/registry/get', {
|
|
||||||
scope: ['client', 'deck', 'profiles'],
|
|
||||||
key: key,
|
|
||||||
});
|
|
||||||
profiles.push({
|
|
||||||
id: uuid(),
|
|
||||||
name: key,
|
|
||||||
columns: deck.columns,
|
|
||||||
layout: deck.layout,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
prefer.commit('deck.profiles', profiles);
|
|
||||||
});
|
|
||||||
|
|
||||||
prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme'));
|
prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme'));
|
||||||
prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme'));
|
prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme'));
|
||||||
prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode'));
|
prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode'));
|
||||||
@@ -247,7 +223,10 @@ export async function mainBoot() {
|
|||||||
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
|
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
|
||||||
prefer.commit('sound.on.notification', store.s.sound_notification as any);
|
prefer.commit('sound.on.notification', store.s.sound_notification as any);
|
||||||
prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
|
prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
|
||||||
store.set('menu', []);
|
store.commit('deck.profile', deckStore.s.profile);
|
||||||
|
store.commit('deck.columns', deckStore.s.columns);
|
||||||
|
store.commit('deck.layout', deckStore.s.layout);
|
||||||
|
store.commit('menu', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.s.accountSetupWizard !== -1) {
|
if (store.s.accountSetupWizard !== -1) {
|
||||||
@@ -523,7 +502,7 @@ export async function mainBoot() {
|
|||||||
post();
|
post();
|
||||||
},
|
},
|
||||||
'd': () => {
|
'd': () => {
|
||||||
store.set('darkMode', !store.s.darkMode);
|
store.commit('darkMode', !store.s.darkMode);
|
||||||
},
|
},
|
||||||
's': () => {
|
's': () => {
|
||||||
mainRouter.push('/search');
|
mainRouter.push('/search');
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ function complete(type: string, value: any) {
|
|||||||
let recents = store.s.recentlyUsedEmojis;
|
let recents = store.s.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji: any) => emoji !== value);
|
recents = recents.filter((emoji: any) => emoji !== value);
|
||||||
recents.unshift(value);
|
recents.unshift(value);
|
||||||
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
store.commit('recentlyUsedEmojis', recents.splice(0, 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -220,28 +220,28 @@ function onMousedown(evt: MouseEvent): void {
|
|||||||
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
|
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
|
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:disabled):active {
|
&:not(:disabled):active {
|
||||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
|
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.danger {
|
&.danger {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--MI_THEME-error);
|
color: #ff2a2a;
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: var(--MI_THEME-error);
|
background: #ff2a2a;
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background: hsl(from var(--MI_THEME-error) h s calc(l + 10));
|
background: #ff4242;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:disabled):active {
|
&:not(:disabled):active {
|
||||||
background: hsl(from var(--MI_THEME-error) h s calc(l - 10));
|
background: #d42e2e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
|
|||||||
let recents = store.s.recentlyUsedEmojis;
|
let recents = store.s.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji) => emoji !== key);
|
recents = recents.filter((emoji) => emoji !== key);
|
||||||
recents.unshift(key);
|
recents.unshift(key);
|
||||||
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
store.commit('recentlyUsedEmojis', recents.splice(0, 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-panel :class="$style.root">
|
|
||||||
<img :class="$style.img" :src="icon"/>
|
|
||||||
<div :class="$style.text">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
withDefaults(defineProps<{
|
|
||||||
icon: string;
|
|
||||||
color: string;
|
|
||||||
}>(), {
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style module lang="scss">
|
|
||||||
.root {
|
|
||||||
padding: 20px 24px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: var(--MI-radius);
|
|
||||||
background: linear-gradient(180deg, color(from v-bind(color) srgb r g b / 0.1), color(from v-bind(color) srgb r g b / 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.img {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 40px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 85%;
|
|
||||||
mix-blend-mode: luminosity;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -177,12 +177,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
|
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
|
||||||
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
|
||||||
import type { Keymap } from '@/utility/hotkey.js';
|
|
||||||
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
||||||
|
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { isTouchUsing } from '@/utility/touch.js';
|
import { isTouchUsing } from '@/utility/touch.js';
|
||||||
|
import type { Keymap } from '@/utility/hotkey.js';
|
||||||
import { isFocusable } from '@/utility/focus.js';
|
import { isFocusable } from '@/utility/focus.js';
|
||||||
import { getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
|
import { getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||||
|
|
||||||
@@ -558,11 +558,11 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.danger {
|
&.danger {
|
||||||
--menuFg: var(--MI_THEME-error);
|
--menuFg: #ff2a2a;
|
||||||
--menuHoverFg: #fff;
|
--menuHoverFg: #fff;
|
||||||
--menuHoverBg: var(--MI_THEME-error);
|
--menuHoverBg: #ff4242;
|
||||||
--menuActiveFg: #fff;
|
--menuActiveFg: #fff;
|
||||||
--menuActiveBg: hsl(from var(--MI_THEME-error) h s calc(l - 10));
|
--menuActiveBg: #d42e2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.radio {
|
&.radio {
|
||||||
|
|||||||
@@ -32,20 +32,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
|
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { getScrollContainer } from '@@/js/scroll.js';
|
import { getScrollContainer } from '@@/js/scroll.js';
|
||||||
import type { PageMetadata } from '@/page.js';
|
import type { PageMetadata } from '@/utility/page-metadata.js';
|
||||||
import RouterView from '@/components/global/RouterView.vue';
|
import RouterView from '@/components/global/RouterView.vue';
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import { popout as _popout } from '@/utility/popout.js';
|
import { popout as _popout } from '@/utility/popout.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { useScrollPositionManager } from '@/nirax.js';
|
import { useScrollPositionManager } from '@/nirax.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
|
import { provideMetadataReceiver, provideReactiveMetadata } from '@/utility/page-metadata.js';
|
||||||
import { openingWindowsCount } from '@/os.js';
|
import { openingWindowsCount } from '@/os.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { useRouterFactory } from '@/router/supplier.js';
|
import { useRouterFactory } from '@/router/supplier.js';
|
||||||
import { mainRouter } from '@/router/main.js';
|
import { mainRouter } from '@/router/main.js';
|
||||||
import { analytics } from '@/analytics.js';
|
import { analytics } from '@/analytics.js';
|
||||||
import { DI } from '@/di.js';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
@@ -120,7 +119,7 @@ windowRouter.addListener('change', ctx => {
|
|||||||
|
|
||||||
windowRouter.init();
|
windowRouter.init();
|
||||||
|
|
||||||
provide(DI.router, windowRouter);
|
provide('router', windowRouter);
|
||||||
provide('inAppSearchMarkerId', searchMarkerId);
|
provide('inAppSearchMarkerId', searchMarkerId);
|
||||||
provideMetadataReceiver((metadataGetter) => {
|
provideMetadataReceiver((metadataGetter) => {
|
||||||
const info = metadataGetter();
|
const info = metadataGetter();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
<div style="padding: 0 0 16px 0; text-align: center;">
|
<div style="padding: 0 0 16px 0; text-align: center;">
|
||||||
<img src="/client-assets/locked_with_key_3d.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
|
<img src="/fluent-emoji/1f510.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
|
||||||
<div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div>
|
<div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ const files = ref(props.initialFiles ?? []);
|
|||||||
const poll = ref<PollEditorModelValue | null>(null);
|
const poll = ref<PollEditorModelValue | null>(null);
|
||||||
const useCw = ref<boolean>(!!props.initialCw);
|
const useCw = ref<boolean>(!!props.initialCw);
|
||||||
const showPreview = ref(store.s.showPreview);
|
const showPreview = ref(store.s.showPreview);
|
||||||
watch(showPreview, () => store.set('showPreview', showPreview.value));
|
watch(showPreview, () => store.commit('showPreview', showPreview.value));
|
||||||
const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction);
|
const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction);
|
||||||
watch(showAddMfmFunction, () => prefer.commit('enableQuickAddMfmFunction', showAddMfmFunction.value));
|
watch(showAddMfmFunction, () => prefer.commit('enableQuickAddMfmFunction', showAddMfmFunction.value));
|
||||||
const cw = ref<string | null>(props.initialCw ?? null);
|
const cw = ref<string | null>(props.initialCw ?? null);
|
||||||
@@ -265,19 +265,13 @@ const canPost = computed((): boolean => {
|
|||||||
quoteId.value != null
|
quoteId.value != null
|
||||||
) &&
|
) &&
|
||||||
(textLength.value <= maxTextLength.value) &&
|
(textLength.value <= maxTextLength.value) &&
|
||||||
(
|
(cwTextLength.value <= maxCwTextLength) &&
|
||||||
useCw.value ?
|
|
||||||
(
|
|
||||||
cw.value != null && cw.value.trim() !== '' &&
|
|
||||||
cwTextLength.value <= maxCwTextLength
|
|
||||||
) : true
|
|
||||||
) &&
|
|
||||||
(files.value.length <= 16) &&
|
(files.value.length <= 16) &&
|
||||||
(!poll.value || poll.value.choices.length >= 2);
|
(!poll.value || poll.value.choices.length >= 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags'));
|
const withHashtags = store.model('postFormWithHashtags');
|
||||||
const hashtags = computed(store.makeGetterSetter('postFormHashtags'));
|
const hashtags = store.model('postFormHashtags');
|
||||||
|
|
||||||
watch(text, () => {
|
watch(text, () => {
|
||||||
checkMissingMention();
|
checkMissingMention();
|
||||||
@@ -486,7 +480,7 @@ function setVisibility() {
|
|||||||
changeVisibility: v => {
|
changeVisibility: v => {
|
||||||
visibility.value = v;
|
visibility.value = v;
|
||||||
if (prefer.s.rememberNoteVisibility) {
|
if (prefer.s.rememberNoteVisibility) {
|
||||||
store.set('visibility', visibility.value);
|
store.commit('visibility', visibility.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
@@ -534,7 +528,7 @@ async function toggleLocalOnly() {
|
|||||||
|
|
||||||
localOnly.value = !localOnly.value;
|
localOnly.value = !localOnly.value;
|
||||||
if (prefer.s.rememberNoteVisibility) {
|
if (prefer.s.rememberNoteVisibility) {
|
||||||
store.set('localOnly', localOnly.value);
|
store.commit('localOnly', localOnly.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,6 +744,14 @@ function isAnnoying(text: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function post(ev?: MouseEvent) {
|
async function post(ev?: MouseEvent) {
|
||||||
|
if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: i18n.ts.cwNotationRequired,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ev) {
|
if (ev) {
|
||||||
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root" @contextmenu.prevent.stop="showMenu($event, true)">
|
<div :class="$style.root">
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.menu">
|
<div :class="$style.menu">
|
||||||
<i v-if="isSyncEnabled" class="ti ti-cloud-cog" style="color: var(--MI_THEME-accent); opacity: 0.7;"></i>
|
|
||||||
<i v-if="isAccountOverrided" class="ti ti-user-cog" style="color: var(--MI_THEME-accent); opacity: 0.7;"></i>
|
<i v-if="isAccountOverrided" class="ti ti-user-cog" style="color: var(--MI_THEME-accent); opacity: 0.7;"></i>
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<button class="_button" style="color: var(--MI_THEME-fg)" @click="showMenu($event)"><i class="ti ti-dots"></i></button>
|
<button class="_button" style="color: var(--MI_THEME-fg)" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,32 +21,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import type { PREF_DEF } from '@/preferences/def.js';
|
import type { PREF_DEF } from '@/preferences/def.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { profileManager } from '@/preferences.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
k: keyof typeof PREF_DEF;
|
k: keyof typeof PREF_DEF;
|
||||||
}>(), {
|
}>(), {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAccountOverrided = ref(prefer.isAccountOverrided(props.k));
|
const isAccountOverrided = ref(profileManager.isAccountOverrided(props.k));
|
||||||
const isSyncEnabled = ref(prefer.isSyncEnabled(props.k));
|
|
||||||
|
|
||||||
function showMenu(ev: MouseEvent, contextmenu?: boolean) {
|
function showMenu(ev: MouseEvent) {
|
||||||
const i = window.setInterval(() => {
|
const i = window.setInterval(() => {
|
||||||
isAccountOverrided.value = prefer.isAccountOverrided(props.k);
|
isAccountOverrided.value = profileManager.isAccountOverrided(props.k);
|
||||||
isSyncEnabled.value = prefer.isSyncEnabled(props.k);
|
|
||||||
}, 100);
|
}, 100);
|
||||||
if (contextmenu) {
|
os.popupMenu(profileManager.getPerPrefMenu(props.k), ev.currentTarget ?? ev.target, {
|
||||||
os.contextMenu(prefer.getPerPrefMenu(props.k), ev).then(() => {
|
onClosing: () => {
|
||||||
window.clearInterval(i);
|
window.clearInterval(i);
|
||||||
});
|
},
|
||||||
} else {
|
});
|
||||||
os.popupMenu(prefer.getPerPrefMenu(props.k), ev.currentTarget ?? ev.target, {
|
|
||||||
onClosing: () => {
|
|
||||||
window.clearInterval(i);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 203.2 152.4"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
>
|
|
||||||
<g fill-rule="evenodd">
|
|
||||||
<rect width="203.2" height="152.4" :fill="themeVariables.bg" stroke-width=".26458" />
|
|
||||||
<rect width="65.498" height="152.4" :fill="themeVariables.panel" stroke-width=".26458" />
|
|
||||||
<rect x="65.498" width="137.7" height="40.892" :fill="themeVariables.acrylicBg" stroke-width=".265" />
|
|
||||||
<path transform="scale(.26458)" d="m439.77 247.19c-43.673 0-78.832 35.157-78.832 78.83v249.98h407.06v-328.81z" :fill="themeVariables.panel" />
|
|
||||||
</g>
|
|
||||||
<circle cx="32.749" cy="83.054" r="21.132" :fill="themeVariables.accentedBg" stroke-dasharray="0.319256, 0.319256" stroke-width=".15963" style="paint-order:stroke fill markers" />
|
|
||||||
<circle cx="136.67" cy="106.76" r="23.876" :fill="themeVariables.fg" fill-opacity="0.5" stroke-dasharray="0.352425, 0.352425" stroke-width=".17621" style="paint-order:stroke fill markers" />
|
|
||||||
<g :fill="themeVariables.fg" fill-rule="evenodd" stroke-width=".26458">
|
|
||||||
<rect x="171.27" y="87.815" width="48.576" height="6.8747" ry="3.4373"/>
|
|
||||||
<rect x="171.27" y="105.09" width="48.576" height="6.875" ry="3.4375"/>
|
|
||||||
<rect x="171.27" y="121.28" width="48.576" height="6.875" ry="3.4375"/>
|
|
||||||
<rect x="171.27" y="137.47" width="48.576" height="6.875" ry="3.4375"/>
|
|
||||||
</g>
|
|
||||||
<path d="m65.498 40.892h137.7" :stroke="themeVariables.divider" stroke-width="0.75" />
|
|
||||||
<g transform="matrix(.60823 0 0 .60823 25.45 75.755)" fill="none" :stroke="themeVariables.accent" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
|
||||||
<path d="m0 0h24v24h-24z" fill="none" stroke="none" />
|
|
||||||
<path d="m5 12h-2l9-9 9 9h-2" />
|
|
||||||
<path d="m5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7" />
|
|
||||||
<path d="m9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.61621 0 0 .61621 25.354 117.92)" fill="none" :stroke="themeVariables.fg" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
|
||||||
<path d="m0 0h24v24h-24z" fill="none" stroke="none" />
|
|
||||||
<path d="m10 5a2 2 0 1 1 4 0 7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6" />
|
|
||||||
<path d="m9 17v1a3 3 0 0 0 6 0v-1" />
|
|
||||||
</g>
|
|
||||||
<image x="20.948" y="18.388" width="23.602" height="23.602" image-rendering="optimizeSpeed" preserveAspectRatio="xMidYMid meet" v-bind="{ 'xlink:href': instance.iconUrl || '/favicon.ico' }" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import { instance } from '@/instance.js';
|
|
||||||
import { compile } from '@/theme.js';
|
|
||||||
import type { Theme } from '@/theme.js';
|
|
||||||
import { deepClone } from '@/utility/clone.js';
|
|
||||||
import lightTheme from '@@/themes/_light.json5';
|
|
||||||
import darkTheme from '@@/themes/_dark.json5';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
theme: Theme;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const themeVariables = ref<{
|
|
||||||
bg: string;
|
|
||||||
acrylicBg: string;
|
|
||||||
panel: string;
|
|
||||||
fg: string;
|
|
||||||
divider: string;
|
|
||||||
accent: string;
|
|
||||||
accentedBg: string;
|
|
||||||
}>({
|
|
||||||
bg: 'var(--MI_THEME-bg)',
|
|
||||||
acrylicBg: 'var(--MI_THEME-acrylicBg)',
|
|
||||||
panel: 'var(--MI_THEME-panel)',
|
|
||||||
fg: 'var(--MI_THEME-fg)',
|
|
||||||
divider: 'var(--MI_THEME-divider)',
|
|
||||||
accent: 'var(--MI_THEME-accent)',
|
|
||||||
accentedBg: 'var(--MI_THEME-accentedBg)',
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => props.theme, (theme) => {
|
|
||||||
if (theme == null) return;
|
|
||||||
|
|
||||||
const _theme = deepClone(theme);
|
|
||||||
|
|
||||||
if (_theme?.base != null) {
|
|
||||||
const base = [lightTheme, darkTheme].find(x => x.id === _theme.base);
|
|
||||||
if (base) _theme.props = Object.assign({}, base.props, _theme.props);
|
|
||||||
}
|
|
||||||
|
|
||||||
const compiled = compile(_theme);
|
|
||||||
|
|
||||||
themeVariables.value = {
|
|
||||||
bg: compiled.bg ?? 'var(--MI_THEME-bg)',
|
|
||||||
acrylicBg: compiled.acrylicBg ?? 'var(--MI_THEME-acrylicBg)',
|
|
||||||
panel: compiled.panel ?? 'var(--MI_THEME-panel)',
|
|
||||||
fg: compiled.fg ?? 'var(--MI_THEME-fg)',
|
|
||||||
divider: compiled.divider ?? 'var(--MI_THEME-divider)',
|
|
||||||
accent: compiled.accent ?? 'var(--MI_THEME-accent)',
|
|
||||||
accentedBg: compiled.accentedBg ?? 'var(--MI_THEME-accentedBg)',
|
|
||||||
};
|
|
||||||
}, { immediate: true });
|
|
||||||
</script>
|
|
||||||
@@ -131,7 +131,7 @@ async function ok() {
|
|||||||
let recents = store.s.recentlyUsedUsers;
|
let recents = store.s.recentlyUsedUsers;
|
||||||
recents = recents.filter(x => x !== selected.value?.id);
|
recents = recents.filter(x => x !== selected.value?.id);
|
||||||
recents.unshift(selected.value.id);
|
recents.unshift(selected.value.id);
|
||||||
store.set('recentlyUsedUsers', recents.splice(0, 16));
|
store.commit('recentlyUsedUsers', recents.splice(0, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
|||||||
const page = ref(store.s.accountSetupWizard);
|
const page = ref(store.s.accountSetupWizard);
|
||||||
|
|
||||||
watch(page, () => {
|
watch(page, () => {
|
||||||
store.set('accountSetupWizard', page.value);
|
store.commit('accountSetupWizard', page.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function close(skip: boolean) {
|
async function close(skip: boolean) {
|
||||||
@@ -165,11 +165,11 @@ async function close(skip: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
store.set('accountSetupWizard', -1);
|
store.commit('accountSetupWizard', -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupComplete() {
|
function setupComplete() {
|
||||||
store.set('accountSetupWizard', -1);
|
store.commit('accountSetupWizard', -1);
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ async function later(later: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
store.set('accountSetupWizard', 0);
|
store.commit('accountSetupWizard', 0);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const shouldHide = ref(!prefer.s.forceShowAds && $i && $i.policies.canHideAds &&
|
|||||||
function reduceFrequency(): void {
|
function reduceFrequency(): void {
|
||||||
if (chosen.value == null) return;
|
if (chosen.value == null) return;
|
||||||
if (store.s.mutedAds.includes(chosen.value.id)) return;
|
if (store.s.mutedAds.includes(chosen.value.id)) return;
|
||||||
store.push('mutedAds', chosen.value.id);
|
store.commit('mutedAds', [...store.s.mutedAds, chosen.value.id]);
|
||||||
os.success();
|
os.success();
|
||||||
chosen.value = choseAd();
|
chosen.value = choseAd();
|
||||||
showMenu.value = false;
|
showMenu.value = false;
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ import { scrollToTop } from '@@/js/scroll.js';
|
|||||||
import XTabs from './MkPageHeader.tabs.vue';
|
import XTabs from './MkPageHeader.tabs.vue';
|
||||||
import type { Tab } from './MkPageHeader.tabs.vue';
|
import type { Tab } from './MkPageHeader.tabs.vue';
|
||||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import type { PageMetadata } from '@/page.js';
|
import type { PageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { injectReactiveMetadata } from '@/page.js';
|
import { injectReactiveMetadata } from '@/utility/page-metadata.js';
|
||||||
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
|
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
|||||||
@@ -24,21 +24,20 @@ import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
|
|||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import MkLoadingPage from '@/pages/_loading_.vue';
|
import MkLoadingPage from '@/pages/_loading_.vue';
|
||||||
import { DI } from '@/di.js';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
router?: IRouter;
|
router?: IRouter;
|
||||||
nested?: boolean;
|
nested?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const router = props.router ?? inject(DI.router);
|
const router = props.router ?? inject('router');
|
||||||
|
|
||||||
if (router == null) {
|
if (router == null) {
|
||||||
throw new Error('no router provided');
|
throw new Error('no router provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDepth = inject(DI.routerCurrentDepth, 0);
|
const currentDepth = inject('routerCurrentDepth', 0);
|
||||||
provide(DI.routerCurrentDepth, currentDepth + 1);
|
provide('routerCurrentDepth', currentDepth + 1);
|
||||||
|
|
||||||
function resolveNested(current: Resolved, d = 0): Resolved | null {
|
function resolveNested(current: Resolved, d = 0): Resolved | null {
|
||||||
if (!props.nested) return current;
|
if (!props.nested) return current;
|
||||||
|
|||||||
@@ -3,23 +3,13 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { throttle } from 'throttle-debounce';
|
||||||
import { notificationTypes } from 'misskey-js';
|
import { notificationTypes } from 'misskey-js';
|
||||||
import { ref } from 'vue';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { i18n } from './i18n.js';
|
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { store } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
|
|
||||||
export type DeckProfile = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
columns: Column[];
|
|
||||||
layout: Column['id'][][];
|
|
||||||
};
|
|
||||||
|
|
||||||
type ColumnWidget = {
|
type ColumnWidget = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -63,132 +53,127 @@ export type Column = {
|
|||||||
soundSetting?: SoundStore;
|
soundSetting?: SoundStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
|
export const loadDeck = async () => {
|
||||||
const __currentProfile = _currentProfile ? deepClone(_currentProfile) : null;
|
let deck;
|
||||||
export const columns = ref(__currentProfile ? __currentProfile.columns : []);
|
|
||||||
export const layout = ref(__currentProfile ? __currentProfile.layout : []);
|
|
||||||
|
|
||||||
if (prefer.s['deck.profile'] == null) {
|
try {
|
||||||
addProfile('Main');
|
deck = await misskeyApi('i/registry/get', {
|
||||||
}
|
scope: ['client', 'deck', 'profiles'],
|
||||||
|
key: store.s['deck.profile'],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') {
|
||||||
|
// 後方互換性のため
|
||||||
|
if (store.s['deck.profile'] === 'default') {
|
||||||
|
saveDeck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
export function forceSaveCurrentDeckProfile() {
|
store.commit('deck.columns', []);
|
||||||
const currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
|
store.commit('deck.layout', []);
|
||||||
if (currentProfile == null) return;
|
return;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
const newProfile = deepClone(currentProfile);
|
store.commit('deck.columns', deck.columns);
|
||||||
newProfile.columns = columns.value;
|
store.commit('deck.layout', deck.layout);
|
||||||
newProfile.layout = layout.value;
|
|
||||||
|
|
||||||
const newProfiles = prefer.s['deck.profiles'].filter(p => p.name !== prefer.s['deck.profile']);
|
|
||||||
newProfiles.push(newProfile);
|
|
||||||
prefer.commit('deck.profiles', newProfiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveCurrentDeckProfile = () => {
|
|
||||||
forceSaveCurrentDeckProfile();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function switchProfile(profile: DeckProfile) {
|
export async function forceSaveDeck() {
|
||||||
prefer.commit('deck.profile', profile.name);
|
await misskeyApi('i/registry/set', {
|
||||||
const currentProfile = deepClone(profile);
|
scope: ['client', 'deck', 'profiles'],
|
||||||
columns.value = currentProfile.columns;
|
key: store.s['deck.profile'],
|
||||||
layout.value = currentProfile.layout;
|
value: {
|
||||||
forceSaveCurrentDeckProfile();
|
columns: store.r['deck.columns'].value,
|
||||||
|
layout: store.r['deck.layout'].value,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addProfile(name: string) {
|
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
|
||||||
if (name.trim() === '') return;
|
export const saveDeck = throttle(1000, () => {
|
||||||
if (prefer.s['deck.profiles'].find(p => p.name === name)) return;
|
forceSaveDeck();
|
||||||
|
});
|
||||||
|
|
||||||
const newProfile: DeckProfile = {
|
export async function getProfiles(): Promise<string[]> {
|
||||||
id: uuid(),
|
return await misskeyApi('i/registry/keys', {
|
||||||
name,
|
scope: ['client', 'deck', 'profiles'],
|
||||||
columns: [],
|
});
|
||||||
layout: [],
|
|
||||||
};
|
|
||||||
prefer.commit('deck.profiles', [...prefer.s['deck.profiles'], newProfile]);
|
|
||||||
switchProfile(newProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFirstProfile() {
|
export async function deleteProfile(key: string): Promise<void> {
|
||||||
addProfile('Main');
|
return await misskeyApi('i/registry/remove', {
|
||||||
}
|
scope: ['client', 'deck', 'profiles'],
|
||||||
|
key: key,
|
||||||
export function deleteProfile(name: string): void {
|
});
|
||||||
const newProfiles = prefer.s['deck.profiles'].filter(p => p.name !== name);
|
|
||||||
prefer.commit('deck.profiles', newProfiles);
|
|
||||||
|
|
||||||
if (prefer.s['deck.profiles'].length === 0) {
|
|
||||||
createFirstProfile();
|
|
||||||
} else {
|
|
||||||
switchProfile(prefer.s['deck.profiles'][0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addColumn(column: Column) {
|
export function addColumn(column: Column) {
|
||||||
if (column.name === undefined) column.name = null;
|
if (column.name === undefined) column.name = null;
|
||||||
columns.value.push(column);
|
store.commit('deck.columns', [...store.s['deck.columns'], column]);
|
||||||
layout.value.push([column.id]);
|
store.commit('deck.layout', [...store.s['deck.layout'], [column.id]]);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeColumn(id: Column['id']) {
|
export function removeColumn(id: Column['id']) {
|
||||||
columns.value = columns.value.filter(c => c.id !== id);
|
store.commit('deck.columns', store.s['deck.columns'].filter(c => c.id !== id));
|
||||||
layout.value = layout.value.map(ids => ids.filter(_id => _id !== id)).filter(ids => ids.length > 0);
|
store.commit('deck.layout', store.s['deck.layout']
|
||||||
saveCurrentDeckProfile();
|
.map(ids => ids.filter(_id => _id !== id))
|
||||||
|
.filter(ids => ids.length > 0));
|
||||||
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapColumn(a: Column['id'], b: Column['id']) {
|
export function swapColumn(a: Column['id'], b: Column['id']) {
|
||||||
const aX = layout.value.findIndex(ids => ids.indexOf(a) !== -1);
|
const aX = store.s['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1);
|
||||||
const aY = layout.value[aX].findIndex(id => id === a);
|
const aY = store.s['deck.layout'][aX].findIndex(id => id === a);
|
||||||
const bX = layout.value.findIndex(ids => ids.indexOf(b) !== -1);
|
const bX = store.s['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1);
|
||||||
const bY = layout.value[bX].findIndex(id => id === b);
|
const bY = store.s['deck.layout'][bX].findIndex(id => id === b);
|
||||||
const newLayout = deepClone(layout.value);
|
const layout = deepClone(store.s['deck.layout']);
|
||||||
newLayout[aX][aY] = b;
|
layout[aX][aY] = b;
|
||||||
newLayout[bX][bY] = a;
|
layout[bX][bY] = a;
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapLeftColumn(id: Column['id']) {
|
export function swapLeftColumn(id: Column['id']) {
|
||||||
const newLayout = deepClone(layout.value);
|
const layout = deepClone(store.s['deck.layout']);
|
||||||
layout.value.some((ids, i) => {
|
store.s['deck.layout'].some((ids, i) => {
|
||||||
if (ids.includes(id)) {
|
if (ids.includes(id)) {
|
||||||
const left = layout.value[i - 1];
|
const left = store.s['deck.layout'][i - 1];
|
||||||
if (left) {
|
if (left) {
|
||||||
newLayout[i - 1] = layout.value[i];
|
layout[i - 1] = store.s['deck.layout'][i];
|
||||||
newLayout[i] = left;
|
layout[i] = left;
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapRightColumn(id: Column['id']) {
|
export function swapRightColumn(id: Column['id']) {
|
||||||
const newLayout = deepClone(layout.value);
|
const layout = deepClone(store.s['deck.layout']);
|
||||||
layout.value.some((ids, i) => {
|
store.s['deck.layout'].some((ids, i) => {
|
||||||
if (ids.includes(id)) {
|
if (ids.includes(id)) {
|
||||||
const right = layout.value[i + 1];
|
const right = store.s['deck.layout'][i + 1];
|
||||||
if (right) {
|
if (right) {
|
||||||
newLayout[i + 1] = layout.value[i];
|
layout[i + 1] = store.s['deck.layout'][i];
|
||||||
newLayout[i] = right;
|
layout[i] = right;
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapUpColumn(id: Column['id']) {
|
export function swapUpColumn(id: Column['id']) {
|
||||||
const newLayout = deepClone(layout.value);
|
const layout = deepClone(store.s['deck.layout']);
|
||||||
const idsIndex = layout.value.findIndex(ids => ids.includes(id));
|
const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
|
||||||
const ids = deepClone(layout.value[idsIndex]);
|
const ids = deepClone(store.s['deck.layout'][idsIndex]);
|
||||||
ids.some((x, i) => {
|
ids.some((x, i) => {
|
||||||
if (x === id) {
|
if (x === id) {
|
||||||
const up = ids[i - 1];
|
const up = ids[i - 1];
|
||||||
@@ -196,20 +181,20 @@ export function swapUpColumn(id: Column['id']) {
|
|||||||
ids[i - 1] = id;
|
ids[i - 1] = id;
|
||||||
ids[i] = up;
|
ids[i] = up;
|
||||||
|
|
||||||
newLayout[idsIndex] = ids;
|
layout[idsIndex] = ids;
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapDownColumn(id: Column['id']) {
|
export function swapDownColumn(id: Column['id']) {
|
||||||
const newLayout = deepClone(layout.value);
|
const layout = deepClone(store.s['deck.layout']);
|
||||||
const idsIndex = layout.value.findIndex(ids => ids.includes(id));
|
const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
|
||||||
const ids = deepClone(layout.value[idsIndex]);
|
const ids = deepClone(store.s['deck.layout'][idsIndex]);
|
||||||
ids.some((x, i) => {
|
ids.some((x, i) => {
|
||||||
if (x === id) {
|
if (x === id) {
|
||||||
const down = ids[i + 1];
|
const down = ids[i + 1];
|
||||||
@@ -217,137 +202,105 @@ export function swapDownColumn(id: Column['id']) {
|
|||||||
ids[i + 1] = id;
|
ids[i + 1] = id;
|
||||||
ids[i] = down;
|
ids[i] = down;
|
||||||
|
|
||||||
newLayout[idsIndex] = ids;
|
layout[idsIndex] = ids;
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stackLeftColumn(id: Column['id']) {
|
export function stackLeftColumn(id: Column['id']) {
|
||||||
let newLayout = deepClone(layout.value);
|
let layout = deepClone(store.s['deck.layout']);
|
||||||
const i = layout.value.findIndex(ids => ids.includes(id));
|
const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
|
||||||
newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
|
layout = layout.map(ids => ids.filter(_id => _id !== id));
|
||||||
newLayout[i - 1].push(id);
|
layout[i - 1].push(id);
|
||||||
newLayout = newLayout.filter(ids => ids.length > 0);
|
layout = layout.filter(ids => ids.length > 0);
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popRightColumn(id: Column['id']) {
|
export function popRightColumn(id: Column['id']) {
|
||||||
let newLayout = deepClone(layout.value);
|
let layout = deepClone(store.s['deck.layout']);
|
||||||
const i = layout.value.findIndex(ids => ids.includes(id));
|
const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
|
||||||
const affected = newLayout[i];
|
const affected = layout[i];
|
||||||
newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
|
layout = layout.map(ids => ids.filter(_id => _id !== id));
|
||||||
newLayout.splice(i + 1, 0, [id]);
|
layout.splice(i + 1, 0, [id]);
|
||||||
newLayout = newLayout.filter(ids => ids.length > 0);
|
layout = layout.filter(ids => ids.length > 0);
|
||||||
layout.value = newLayout;
|
store.commit('deck.layout', layout);
|
||||||
|
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
for (const column of newColumns) {
|
for (const column of columns) {
|
||||||
if (affected.includes(column.id)) {
|
if (affected.includes(column.id)) {
|
||||||
column.active = true;
|
column.active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
|
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
const columnIndex = columns.value.findIndex(c => c.id === id);
|
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
|
||||||
const column = deepClone(columns.value[columnIndex]);
|
const column = deepClone(store.s['deck.columns'][columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
if (column.widgets == null) column.widgets = [];
|
if (column.widgets == null) column.widgets = [];
|
||||||
column.widgets.unshift(widget);
|
column.widgets.unshift(widget);
|
||||||
newColumns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
const columnIndex = columns.value.findIndex(c => c.id === id);
|
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
|
||||||
const column = deepClone(columns.value[columnIndex]);
|
const column = deepClone(store.s['deck.columns'][columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
if (column.widgets == null) column.widgets = [];
|
if (column.widgets == null) column.widgets = [];
|
||||||
column.widgets = column.widgets.filter(w => w.id !== widget.id);
|
column.widgets = column.widgets.filter(w => w.id !== widget.id);
|
||||||
newColumns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
|
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
const columnIndex = columns.value.findIndex(c => c.id === id);
|
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
|
||||||
const column = deepClone(columns.value[columnIndex]);
|
const column = deepClone(store.s['deck.columns'][columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
column.widgets = widgets;
|
column.widgets = widgets;
|
||||||
newColumns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
|
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
const columnIndex = columns.value.findIndex(c => c.id === id);
|
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
|
||||||
const column = deepClone(columns.value[columnIndex]);
|
const column = deepClone(store.s['deck.columns'][columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
if (column.widgets == null) column.widgets = [];
|
if (column.widgets == null) column.widgets = [];
|
||||||
column.widgets = column.widgets.map(w => w.id === widgetId ? {
|
column.widgets = column.widgets.map(w => w.id === widgetId ? {
|
||||||
...w,
|
...w,
|
||||||
data: widgetData,
|
data: widgetData,
|
||||||
} : w);
|
} : w);
|
||||||
newColumns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
||||||
const newColumns = deepClone(columns.value);
|
const columns = deepClone(store.s['deck.columns']);
|
||||||
const columnIndex = columns.value.findIndex(c => c.id === id);
|
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
|
||||||
const currentColumn = deepClone(columns.value[columnIndex]);
|
const currentColumn = deepClone(store.s['deck.columns'][columnIndex]);
|
||||||
if (currentColumn == null) return;
|
if (currentColumn == null) return;
|
||||||
for (const [k, v] of Object.entries(column)) {
|
for (const [k, v] of Object.entries(column)) {
|
||||||
currentColumn[k] = v;
|
currentColumn[k] = v;
|
||||||
}
|
}
|
||||||
newColumns[columnIndex] = currentColumn;
|
columns[columnIndex] = currentColumn;
|
||||||
columns.value = newColumns;
|
store.commit('deck.columns', columns);
|
||||||
saveCurrentDeckProfile();
|
saveDeck();
|
||||||
}
|
|
||||||
|
|
||||||
export function switchProfileMenu(ev: MouseEvent) {
|
|
||||||
const items: MenuItem[] = prefer.s['deck.profile'] ? [{
|
|
||||||
text: prefer.s['deck.profile'],
|
|
||||||
active: true,
|
|
||||||
action: () => {},
|
|
||||||
}] : [];
|
|
||||||
|
|
||||||
const profiles = prefer.s['deck.profiles'];
|
|
||||||
|
|
||||||
items.push(...(profiles.filter(p => p.name !== prefer.s['deck.profile']).map(p => ({
|
|
||||||
text: p.name,
|
|
||||||
action: () => {
|
|
||||||
switchProfile(p);
|
|
||||||
},
|
|
||||||
}))), { type: 'divider' as const }, {
|
|
||||||
text: i18n.ts._deck.newProfile,
|
|
||||||
icon: 'ti ti-plus',
|
|
||||||
action: async () => {
|
|
||||||
const { canceled, result: name } = await os.inputText({
|
|
||||||
title: i18n.ts._deck.profile,
|
|
||||||
minLength: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (canceled || name == null || name.trim() === '') return;
|
|
||||||
|
|
||||||
addProfile(name);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { InjectionKey, Ref } from 'vue';
|
|
||||||
import type { IRouter } from '@/nirax.js';
|
|
||||||
|
|
||||||
export const DI = {
|
|
||||||
routerCurrentDepth: Symbol() as InjectionKey<number>,
|
|
||||||
router: Symbol() as InjectionKey<IRouter>,
|
|
||||||
};
|
|
||||||
@@ -33,7 +33,7 @@ import MkLink from '@/components/MkLink.vue';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { unisonReload } from '@/utility/unison-reload.js';
|
import { unisonReload } from '@/utility/unison-reload.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { serverErrorImageUrl } from '@/instance.js';
|
import { serverErrorImageUrl } from '@/instance.js';
|
||||||
@@ -67,7 +67,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.error,
|
title: i18n.ts.error,
|
||||||
icon: 'ti ti-alert-triangle',
|
icon: 'ti ti-alert-triangle',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ import { i18n } from '@/i18n.js';
|
|||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
|
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.aboutMisskey,
|
title: i18n.ts.aboutMisskey,
|
||||||
icon: null,
|
icon: null,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
|||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
|
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
|
||||||
@@ -81,7 +81,7 @@ const headerTabs = computed(() => {
|
|||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.instanceInfo,
|
title: i18n.ts.instanceInfo,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
|
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
|
||||||
import MkAchievements from '@/components/MkAchievements.vue';
|
import MkAchievements from '@/components/MkAchievements.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ onDeactivated(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.achievements,
|
title: i18n.ts.achievements,
|
||||||
icon: 'ti ti-medal',
|
icon: 'ti ti-medal',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ import bytes from '@/filters/bytes.js';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { iAmAdmin, iAmModerator } from '@/account.js';
|
import { iAmAdmin, iAmModerator } from '@/account.js';
|
||||||
|
|
||||||
const tab = ref('overview');
|
const tab = ref('overview');
|
||||||
@@ -161,7 +161,7 @@ const headerTabs = computed(() => [{
|
|||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
|
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
|
||||||
icon: 'ti ti-file',
|
icon: 'ti ti-file',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { acct } from '@/filters/user.js';
|
import { acct } from '@/filters/user.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { iAmAdmin, $i, iAmModerator } from '@/account.js';
|
import { iAmAdmin, $i, iAmModerator } from '@/account.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
@@ -545,7 +545,7 @@ const headerTabs = computed(() => isSystem.value ? [{
|
|||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: user.value ? acct(user.value) : i18n.ts.userInfo,
|
title: user.value ? acct(user.value) : i18n.ts.userInfo,
|
||||||
icon: 'ti ti-user-exclamation',
|
icon: 'ti ti-user-exclamation',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { scrollToTop } from '@@/js/scroll.js';
|
|||||||
import { popupMenu } from '@/os.js';
|
import { popupMenu } from '@/os.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { injectReactiveMetadata } from '@/page.js';
|
import { injectReactiveMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
type Tab = {
|
type Tab = {
|
||||||
key?: string | null;
|
key?: string | null;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
|||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import XAbuseReport from '@/components/MkAbuseReport.vue';
|
import XAbuseReport from '@/components/MkAbuseReport.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
@@ -93,14 +93,14 @@ function resolved(reportId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeTutorial() {
|
function closeTutorial() {
|
||||||
store.set('abusesTutorial', false);
|
store.commit('abusesTutorial', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.abuseReports,
|
title: i18n.ts.abuseReports,
|
||||||
icon: 'ti ti-exclamation-circle',
|
icon: 'ti ti-exclamation-circle',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ import FormSplit from '@/components/form/split.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const ads = ref<Misskey.entities.Ad[]>([]);
|
const ads = ref<Misskey.entities.Ad[]>([]);
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ const headerActions = computed(() => [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.ads,
|
title: i18n.ts.ads,
|
||||||
icon: 'ti ti-ad',
|
icon: 'ti ti-ad',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ const headerActions = computed(() => [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.announcements,
|
title: i18n.ts.announcements,
|
||||||
icon: 'ti ti-speakerphone',
|
icon: 'ti ti-speakerphone',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { instance, fetchInstance } from '@/instance.js';
|
import { instance, fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkColorInput from '@/components/MkColorInput.vue';
|
import MkColorInput from '@/components/MkColorInput.vue';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
@@ -175,7 +175,7 @@ function save() {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.branding,
|
title: i18n.ts.branding,
|
||||||
icon: 'ti ti-paint',
|
icon: 'ti ti-paint',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import XGridLocalComponent from '@/pages/admin/custom-emojis-manager.local.vue';
|
import XGridLocalComponent from '@/pages/admin/custom-emojis-manager.local.vue';
|
||||||
import XGridRemoteComponent from '@/pages/admin/custom-emojis-manager.remote.vue';
|
import XGridRemoteComponent from '@/pages/admin/custom-emojis-manager.remote.vue';
|
||||||
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
||||||
@@ -36,7 +36,7 @@ const headerTabs = computed(() => [{
|
|||||||
title: i18n.ts.remote,
|
title: i18n.ts.remote,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(computed(() => ({
|
definePageMetadata(computed(() => ({
|
||||||
title: i18n.ts.customEmojis,
|
title: i18n.ts.customEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
needWideArea: true,
|
needWideArea: true,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const databasePromiseFactory = () => misskeyApi('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
|
const databasePromiseFactory = () => misskeyApi('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.database,
|
title: i18n.ts.database,
|
||||||
icon: 'ti ti-database',
|
icon: 'ti ti-database',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
const enableEmail = ref<boolean>(false);
|
const enableEmail = ref<boolean>(false);
|
||||||
@@ -130,7 +130,7 @@ function save() {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.emailServer,
|
title: i18n.ts.emailServer,
|
||||||
icon: 'ti ti-mail',
|
icon: 'ti ti-mail',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
const deeplAuthKey = ref<string>('');
|
const deeplAuthKey = ref<string>('');
|
||||||
@@ -88,7 +88,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.externalServices,
|
title: i18n.ts.externalServices,
|
||||||
icon: 'ti ti-link',
|
icon: 'ti ti-link',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
|||||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const state = ref('federating');
|
const state = ref('federating');
|
||||||
@@ -112,7 +112,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.federation,
|
title: i18n.ts.federation,
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { lookupFile } from '@/utility/admin-lookup.js';
|
import { lookupFile } from '@/utility/admin-lookup.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const origin = ref('local');
|
const origin = ref('local');
|
||||||
const type = ref<string | null>(null);
|
const type = ref<string | null>(null);
|
||||||
@@ -85,7 +85,7 @@ const headerActions = computed(() => [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.files,
|
title: i18n.ts.files,
|
||||||
icon: 'ti ti-cloud',
|
icon: 'ti ti-cloud',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import { lookup } from '@/utility/lookup.js';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
|
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
|
||||||
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
|
import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/utility/page-metadata.js';
|
||||||
import type { PageMetadata } from '@/page.js';
|
import type { PageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
|
||||||
const isEmpty = (x: string | null) => x == null || x === '';
|
const isEmpty = (x: string | null) => x == null || x === '';
|
||||||
@@ -318,7 +318,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => INFO.value);
|
definePageMetadata(() => INFO.value);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
header: {
|
header: {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
|||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkInviteCode from '@/components/MkInviteCode.vue';
|
import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ function deleted(id: string) {
|
|||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.invite,
|
title: i18n.ts.invite,
|
||||||
icon: 'ti ti-user-plus',
|
icon: 'ti ti-user-plus',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormLink from '@/components/form/link.vue';
|
import FormLink from '@/components/form/link.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
@@ -259,7 +259,7 @@ function save_mediaSilencedHosts() {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.moderation,
|
title: i18n.ts.moderation,
|
||||||
icon: 'ti ti-shield',
|
icon: 'ti ti-shield',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
|||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||||
|
|
||||||
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
@@ -59,7 +59,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.moderationLogs,
|
title: i18n.ts.moderationLogs,
|
||||||
icon: 'ti ti-list-search',
|
icon: 'ti ti-list-search',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
const useObjectStorage = ref<boolean>(false);
|
const useObjectStorage = ref<boolean>(false);
|
||||||
@@ -149,7 +149,7 @@ function save() {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.objectStorage,
|
title: i18n.ts.objectStorage,
|
||||||
icon: 'ti ti-cloud',
|
icon: 'ti ti-cloud',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
|
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
|
||||||
const rootEl = shallowRef<HTMLElement>();
|
const rootEl = shallowRef<HTMLElement>();
|
||||||
@@ -184,7 +184,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.dashboard,
|
title: i18n.ts.dashboard,
|
||||||
icon: 'ti ti-dashboard',
|
icon: 'ti ti-dashboard',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
@@ -202,7 +202,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.other,
|
title: i18n.ts.other,
|
||||||
icon: 'ti ti-adjustments',
|
icon: 'ti ti-adjustments',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import XHeader from './_header_.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import * as config from '@@/js/config.js';
|
import * as config from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
export type ApQueueDomain = 'deliver' | 'inbox';
|
export type ApQueueDomain = 'deliver' | 'inbox';
|
||||||
@@ -71,7 +71,7 @@ const headerTabs = computed(() => [{
|
|||||||
title: 'Inbox',
|
title: 'Inbox',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.jobQueue,
|
title: i18n.ts.jobQueue,
|
||||||
icon: 'ti ti-clock-play',
|
icon: 'ti ti-clock-play',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
|
const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ const headerActions = computed(() => [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.relays,
|
title: i18n.ts.relays,
|
||||||
icon: 'ti ti-planet',
|
icon: 'ti ti-planet',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import XEditor from './roles.editor.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { rolesCache } from '@/cache.js';
|
import { rolesCache } from '@/cache.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
@@ -87,7 +87,7 @@ async function save() {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
|
title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
|
||||||
icon: 'ti ti-badge',
|
icon: 'ti ti-badge',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
@@ -170,7 +170,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: `${i18n.ts.role}: ${role.name}`,
|
title: `${i18n.ts.role}: ${role.name}`,
|
||||||
icon: 'ti ti-badge',
|
icon: 'ti ti-badge',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ import MkRolePreview from '@/components/MkRolePreview.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { instance, fetchInstance } from '@/instance.js';
|
import { instance, fetchInstance } from '@/instance.js';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
@@ -338,7 +338,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.roles,
|
title: i18n.ts.roles,
|
||||||
icon: 'ti ti-badges',
|
icon: 'ti ti-badges',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { useForm } from '@/utility/use-form.js';
|
import { useForm } from '@/utility/use-form.js';
|
||||||
import MkFormFooter from '@/components/MkFormFooter.vue';
|
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.security,
|
title: i18n.ts.security,
|
||||||
icon: 'ti ti-lock',
|
icon: 'ti ti-lock',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import XHeader from './_header_.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const remove = (index: number): void => {
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.serverRules,
|
title: i18n.ts.serverRules,
|
||||||
icon: 'ti ti-checkbox',
|
icon: 'ti ti-checkbox',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
@@ -391,7 +391,7 @@ const proxyAccountForm = useForm({
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.general,
|
title: i18n.ts.general,
|
||||||
icon: 'ti ti-settings',
|
icon: 'ti ti-settings',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { computed, onMounted, ref } from 'vue';
|
|||||||
import { entities } from 'misskey-js';
|
import { entities } from 'misskey-js';
|
||||||
import XItem from './system-webhook.item.vue';
|
import XItem from './system-webhook.item.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import XHeader from '@/pages/admin/_header_.vue';
|
import XHeader from '@/pages/admin/_header_.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
@@ -82,7 +82,7 @@ onMounted(async () => {
|
|||||||
await fetchWebhooks();
|
await fetchWebhooks();
|
||||||
});
|
});
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: 'SystemWebhook',
|
title: 'SystemWebhook',
|
||||||
icon: 'ti ti-webhook',
|
icon: 'ti ti-webhook',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { lookupUser } from '@/utility/admin-lookup.js';
|
import { lookupUser } from '@/utility/admin-lookup.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ watchEffect(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.users,
|
title: i18n.ts.users,
|
||||||
icon: 'ti ti-users',
|
icon: 'ti ti-users',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.ads,
|
title: i18n.ts.ads,
|
||||||
icon: 'ti ti-ad',
|
icon: 'ti ti-ad',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { $i, updateAccountPartial } from '@/account.js';
|
import { $i, updateAccountPartial } from '@/account.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
|
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
|
||||||
icon: 'ti ti-speakerphone',
|
icon: 'ti ti-speakerphone',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { $i, updateAccountPartial } from '@/account.js';
|
import { $i, updateAccountPartial } from '@/account.js';
|
||||||
|
|
||||||
const paginationCurrent = {
|
const paginationCurrent = {
|
||||||
@@ -111,7 +111,7 @@ const headerTabs = computed(() => [{
|
|||||||
icon: 'ti ti-point',
|
icon: 'ti ti-point',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.announcements,
|
title: i18n.ts.announcements,
|
||||||
icon: 'ti ti-speakerphone',
|
icon: 'ti ti-speakerphone',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
|
|||||||
import { scroll } from '@@/js/scroll.js';
|
import { scroll } from '@@/js/scroll.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ const headerActions = computed(() => antenna.value ? [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: antenna.value ? antenna.value.name : i18n.ts.antennas,
|
title: antenna.value ? antenna.value.name : i18n.ts.antennas,
|
||||||
icon: 'ti ti-antenna',
|
icon: 'ti ti-antenna',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import MkInput from '@/components/MkInput.vue';
|
|||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const body = ref('{}');
|
const body = ref('{}');
|
||||||
const endpoint = ref('');
|
const endpoint = ref('');
|
||||||
@@ -87,7 +87,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: 'API console',
|
title: 'API console',
|
||||||
icon: 'ti ti-terminal-2',
|
icon: 'ti ti-terminal-2',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import XForm from './auth.form.vue';
|
|||||||
import MkSignin from '@/components/MkSignin.vue';
|
import MkSignin from '@/components/MkSignin.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { $i, login } from '@/account.js';
|
import { $i, login } from '@/account.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -97,7 +97,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts._auth.shareAccessTitle,
|
title: i18n.ts._auth.shareAccessTitle,
|
||||||
icon: 'ti ti-apps',
|
icon: 'ti ti-apps',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { signinRequired } from '@/account.js';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ const headerActions = computed(() => [{
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.avatarDecorations,
|
title: i18n.ts.avatarDecorations,
|
||||||
icon: 'ti ti-sparkles',
|
icon: 'ti ti-sparkles',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ import MkColorInput from '@/components/MkColorInput.vue';
|
|||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/select-file.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
@@ -202,7 +202,7 @@ const headerActions = computed(() => []);
|
|||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
|
title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
|
||||||
icon: 'ti ti-device-tv',
|
icon: 'ti ti-device-tv',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { deviceKind } from '@/utility/device-kind.js';
|
import { deviceKind } from '@/utility/device-kind.js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
import { favoritedChannelsCache } from '@/cache.js';
|
import { favoritedChannelsCache } from '@/cache.js';
|
||||||
@@ -265,7 +265,7 @@ const headerTabs = computed(() => [{
|
|||||||
icon: 'ti ti-search',
|
icon: 'ti ti-search',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: channel.value ? channel.value.name : i18n.ts.channel,
|
title: channel.value ? channel.value.name : i18n.ts.channel,
|
||||||
icon: 'ti ti-device-tv',
|
icon: 'ti ti-device-tv',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ import MkRadios from '@/components/MkRadios.vue';
|
|||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ const headerTabs = computed(() => [{
|
|||||||
icon: 'ti ti-edit',
|
icon: 'ti ti-edit',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.channel,
|
title: i18n.ts.channel,
|
||||||
icon: 'ti ti-device-tv',
|
icon: 'ti ti-device-tv',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MkClickerGame from '@/components/MkClickerGame.vue';
|
import MkClickerGame from '@/components/MkClickerGame.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: '🍪👈',
|
title: '🍪👈',
|
||||||
icon: 'ti ti-cookie',
|
icon: 'ti ti-cookie',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { $i } from '@/account.js';
|
|||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { clipsCache } from '@/cache.js';
|
import { clipsCache } from '@/cache.js';
|
||||||
import { isSupportShare } from '@/utility/navigator.js';
|
import { isSupportShare } from '@/utility/navigator.js';
|
||||||
@@ -193,7 +193,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
|||||||
},
|
},
|
||||||
}] : null);
|
}] : null);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: clip.value ? clip.value.name : i18n.ts.clip,
|
title: clip.value ? clip.value.name : i18n.ts.clip,
|
||||||
icon: 'ti ti-paperclip',
|
icon: 'ti ti-paperclip',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkLink from '@/components/MkLink.vue';
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.inquiry,
|
title: i18n.ts.inquiry,
|
||||||
icon: 'ti ti-help-circle',
|
icon: 'ti ti-help-circle',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ import * as os from '@/os.js';
|
|||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
|
|
||||||
const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ const headerTabs = computed(() => [{
|
|||||||
title: i18n.ts.remote,
|
title: i18n.ts.remote,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.customEmojis,
|
title: i18n.ts.customEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, defineAsyncComponent } from 'vue';
|
import { computed, ref, defineAsyncComponent } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePageMetadata } from '@/utility/page-metadata.js';
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -48,7 +48,7 @@ const headerTabs = computed(() => [{
|
|||||||
icon: 'ti ti-pencil',
|
icon: 'ti ti-pencil',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts._fileViewer.title,
|
title: i18n.ts._fileViewer.title,
|
||||||
icon: 'ti ti-file',
|
icon: 'ti ti-file',
|
||||||
}));
|
}));
|
||||||
|
|||||||