Compare commits

..

11 Commits

Author SHA1 Message Date
syuilo
2402754dcc Update pizzax.ts 2025-03-10 21:44:23 +09:00
syuilo
2493592bd0 wip 2025-03-10 12:08:42 +09:00
syuilo
eec4ab841a Merge branch 'develop' into refine-pizzax 2025-03-10 11:29:16 +09:00
syuilo
d0b8ffe629 Merge branch 'develop' into refine-pizzax 2025-03-10 11:28:13 +09:00
syuilo
cef7575b76 commit 2025-03-10 11:23:15 +09:00
syuilo
9842eb2eeb wip 2025-03-10 11:21:17 +09:00
syuilo
05078e9c14 wip 2025-03-10 11:17:08 +09:00
syuilo
db5c6fa3c2 wip 2025-03-10 11:13:33 +09:00
syuilo
8a4e2659ed Merge branch 'develop' into refine-pizzax 2025-03-10 11:10:32 +09:00
syuilo
d19c094a9b refactor(frontend): rename pizzax fields 2025-03-10 11:09:59 +09:00
syuilo
a7f7ff33e7 wip 2025-03-10 10:47:50 +09:00
218 changed files with 2144 additions and 3115 deletions

View File

@@ -6,16 +6,12 @@
### Client
- Feat: 設定の管理が強化されました
- 自動でバックアップされるように
- 任意の設定項目をデバイス間で同期できるように(実験的)
- Enhance: プラグインの管理が強化されました
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
- Enhance: テーマ設定画面のデザインを改善
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
## 2025.3.1

BIN
assets/about/drive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
assets/about/post.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

BIN
assets/about/reaction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/about/ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
assets/ss/explore.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

BIN
assets/ss/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

102
locales/index.d.ts vendored
View File

@@ -5310,96 +5310,6 @@ export interface Locale extends ILocale {
* 復元
*/
"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": {
/**
* プロファイル名
@@ -5485,10 +5395,6 @@ export interface Locale extends ILocale {
* リモートサーバーに連合されたノートには効果が及ばない場合があります。
*/
"mayNotEffectForFederatedNotes": string;
/**
* これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。
*/
"mayNotEffectSomeSituations": string;
/**
* 指定した時間を経過しているノート
*/
@@ -7836,10 +7742,6 @@ export interface Locale extends ILocale {
* 標準のテーマ
*/
"builtinThemes": string;
/**
* サーバーのテーマ
*/
"instanceTheme": string;
/**
* そのテーマは既にインストールされています
*/
@@ -9848,10 +9750,6 @@ export interface Locale extends ILocale {
* 幅を自動調整
*/
"flexible": string;
/**
* プロファイル情報のデバイス間同期を有効にする
*/
"enableSyncBetweenDevicesForProfiles": string;
"_columns": {
/**
* メイン

View File

@@ -1323,30 +1323,6 @@ untitled: "無題"
noName: "名前はありません"
skip: "スキップ"
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:
profileName: "プロファイル名"
@@ -1373,7 +1349,6 @@ _accountSettings:
makeNotesHiddenBefore: "過去のノートを非公開化する"
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
mayNotEffectSomeSituations: "これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。"
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
@@ -2055,7 +2030,6 @@ _theme:
installed: "{name}をインストールしました"
installedThemes: "インストールされたテーマ"
builtinThemes: "標準のテーマ"
instanceTheme: "サーバーのテーマ"
alreadyInstalled: "そのテーマは既にインストールされています"
invalid: "テーマの形式が間違っています"
make: "テーマを作る"
@@ -2603,7 +2577,6 @@ _deck:
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
flexible: "幅を自動調整"
enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする"
_columns:
main: "メイン"

View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.3.2-alpha.9",
"version": "2025.3.2-alpha.4",
"codename": "nasubi",
"repository": {
"type": "git",
@@ -24,7 +24,6 @@
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"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-frontend-search-index": "pnpm --filter frontend build-search-index",
"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",
"init": "pnpm migrate",
@@ -66,12 +65,12 @@
},
"devDependencies": {
"@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/parser": "8.26.0",
"cross-env": "7.0.3",
"cypress": "14.1.0",
"eslint": "9.22.0",
"eslint": "9.21.0",
"globals": "16.0.0",
"ncp": "2.0.0",
"pnpm": "10.6.1",

View File

@@ -16,7 +16,7 @@ import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.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 { Response } from 'node-fetch';
import type { URL } from 'node:url';
@@ -265,7 +265,7 @@ export class HttpRequestService {
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
assertActivityMatchesUrl(url, activity, finalUrl, allowSoftfail);
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
return activity;
}

View File

@@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.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';
type Request = {
@@ -258,7 +258,7 @@ export class ApRequestService {
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
assertActivityMatchesUrl(url, activity, finalUrl, allowSoftfail);
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
return activity;
}

View File

@@ -75,7 +75,7 @@ function normalizeSynonymousSubdomain(url: URL | string): URL {
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
if (!activity.id) {
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 idParsed = normalizeSynonymousSubdomain(activity.id);
const finalUrlParsed = normalizeSynonymousSubdomain(finalUrl);
// 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 candidateUrlsParsed = candidateUrls.map(it => normalizeSynonymousSubdomain(it));
const requestUrlSecure = requestUrlParsed.protocol === 'https:';
const finalUrlSecure = finalUrlParsed.protocol === 'https:';
const finalUrlSecure = candidateUrlsParsed.every(it => it.protocol === 'https:');
if (requestUrlSecure && !finalUrlSecure) {
throw new Error(`bad Activity: id(${activity.id}) is not allowed to have http:// in the url`);
}
// Compare final URL to the ID
if (finalUrlParsed.href !== idParsed.href) {
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match response url(${finalUrlParsed.toString()})`);
if (!candidateUrlsParsed.some(it => it.href === idParsed.href)) {
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)
if (idParsed.host !== finalUrlParsed.host) {
throw new Error(`bad Activity: id(${activity.id}) does not match response host(${finalUrlParsed.host})`);
if (!candidateUrlsParsed.some(it => idParsed.host === it.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
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()})`);
// 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)

View File

@@ -8,7 +8,7 @@ import httpSignature from '@peertube/http-signature';
import { genRsaKeyPair } from '@/misc/gen-key-pair.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';
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
@@ -66,26 +66,23 @@ describe('ap-request', () => {
});
test('rejects non matching domain', () => {
assert.doesNotThrow(() => assertActivityMatchesUrl(
assert.doesNotThrow(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ id: 'https://alice.example.com/abc' } as IObject,
'https://alice.example.com/abc',
[
'https://alice.example.com/abc',
],
FetchAllowSoftFailMask.Strict,
), 'validation should pass base case');
assert.throws(() => assertActivityMatchesUrl(
assert.throws(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ id: 'https://bob.example.com/abc' } as IObject,
'https://alice.example.com/abc',
[
'https://alice.example.com/abc',
],
FetchAllowSoftFailMask.Any,
), '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
// https://github.com/misskey-dev/misskey/issues/15039
const withOrWithoutWWW = [
@@ -100,71 +97,89 @@ describe('ap-request', () => {
),
withOrWithoutWWW,
).forEach(([[a, b], c]) => {
assert.doesNotThrow(() => assertActivityMatchesUrl(
assert.doesNotThrow(() => assertActivityMatchesUrls(
a,
{ id: b } as IObject,
c,
[
c,
],
FetchAllowSoftFailMask.Strict,
), 'validation should pass with or without www. subdomain');
});
});
test('cross origin lookup', () => {
assert.doesNotThrow(() => assertActivityMatchesUrl(
assert.doesNotThrow(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ id: 'https://bob.example.com/abc' } as IObject,
'https://bob.example.com/abc',
[
'https://bob.example.com/abc',
],
FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
), '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',
{ id: 'https://bob.example.com/abc' } as IObject,
'https://bob.example.com/abc',
[
'https://bob.example.com/abc',
],
FetchAllowSoftFailMask.Strict,
), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
});
test('rejects non-canonical ID', () => {
assert.throws(() => assertActivityMatchesUrl(
assert.throws(() => assertActivityMatchesUrls(
'https://alice.example.com/@alice',
{ id: 'https://alice.example.com/users/alice' } as IObject,
'https://alice.example.com/users/alice',
[
'https://alice.example.com/users/alice'
],
FetchAllowSoftFailMask.Strict,
), 'throws if the response ID did not exactly match the expected ID');
assert.doesNotThrow(() => assertActivityMatchesUrl(
assert.doesNotThrow(() => assertActivityMatchesUrls(
'https://alice.example.com/@alice',
{ id: 'https://alice.example.com/users/alice' } as IObject,
'https://alice.example.com/users/alice',
[
'https://alice.example.com/users/alice',
],
FetchAllowSoftFailMask.NonCanonicalId,
), 'does not throw if non-canonical ID is allowed');
});
test('origin relaxed alignment', () => {
assert.doesNotThrow(() => assertActivityMatchesUrl(
assert.doesNotThrow(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ 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,
), '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',
{ 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,
), 'validation should fail if response is a disjoint domain of the expected origin');
assert.throws(() => assertActivityMatchesUrl(
assert.throws(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ id: 'https://ap.alice.example.com/abc' } as IObject,
'https://ap.alice.example.com/abc',
[
'https://ap.alice.example.com/abc',
],
FetchAllowSoftFailMask.Strict,
), 'throws if relaxed origin is forbidden');
});
test('resist HTTP downgrade', () => {
assert.throws(() => assertActivityMatchesUrl(
assert.throws(() => assertActivityMatchesUrls(
'https://alice.example.com/abc',
{ id: 'https://alice.example.com/abc' } as IObject,
'http://alice.example.com/abc',
[
'http://alice.example.com/abc',
],
FetchAllowSoftFailMask.Strict,
), 'throws if HTTP downgrade is detected');
});

View File

@@ -17,52 +17,8 @@ interface SatisfiesExpression extends estree.BaseExpression {
reference: estree.Identifier;
}
interface ImportDeclaration extends estree.ImportDeclaration {
kind?: 'type';
}
const 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) {
switch (node.expression.type) {
case 'ArrowFunctionExpression': {
@@ -106,7 +62,7 @@ type ToKebab<T extends readonly string[]> = T extends readonly [
: T extends readonly [
infer XH extends string,
...infer XR extends readonly string[]
]
]
? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
: '';
@@ -176,7 +132,7 @@ function toStories(component: string): Promise<string> {
kind={'init' as const}
shorthand
/> as estree.Property,
]
]
: []),
]}
/> as estree.ObjectExpression;
@@ -199,8 +155,7 @@ function toStories(component: string): Promise<string> {
/> as estree.ImportSpecifier,
]),
]}
kind={'type'}
/> as ImportDeclaration,
/> as estree.ImportDeclaration,
...(hasMsw
? [
<import-declaration
@@ -210,8 +165,8 @@ function toStories(component: string): Promise<string> {
local={<identifier name='msw' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
/> as ImportDeclaration,
]
/> as estree.ImportDeclaration,
]
: []),
...(hasImplStories
? []
@@ -221,8 +176,8 @@ function toStories(component: string): Promise<string> {
specifiers={[
<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
]}
/> as ImportDeclaration,
]),
/> as estree.ImportDeclaration,
]),
...(hasMetaStories
? [
<import-declaration
@@ -232,7 +187,7 @@ function toStories(component: string): Promise<string> {
local={<identifier name='storiesMeta' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
/> as ImportDeclaration,
/> as estree.ImportDeclaration,
]
: []),
<variable-declaration

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -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
export default function pluginCreateSearchIndex(options: Options): Plugin {
@@ -1462,7 +1445,19 @@ export default function pluginCreateSearchIndex(options: Options): Plugin {
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) {

View File

@@ -5,7 +5,6 @@
"scripts": {
"watch": "vite",
"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\"",
"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",
@@ -134,7 +133,6 @@
"start-server-and-test": "2.0.10",
"storybook": "8.6.4",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-node": "3.0.8",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "3.0.8",
"vitest-fetch-mock": "0.4.5",

View File

@@ -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();

View File

@@ -17,7 +17,6 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
// TODO: 他のタブと永続化されたstateを同期
// TODO: accountsはpreferences管理にする(tokenは別管理)
type Account = Misskey.entities.MeDetailed & { token: string };

View File

@@ -180,12 +180,12 @@ export async function common(createVue: () => App<Element>) {
//#region Sync dark mode
if (prefer.s.syncDeviceDarkMode) {
store.set('darkMode', isDeviceDarkmode());
store.commit('darkMode', isDeviceDarkmode());
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
if (prefer.s.syncDeviceDarkMode) {
store.set('darkMode', mql.matches);
store.commit('darkMode', mql.matches);
}
});
//#endregion

View File

@@ -6,11 +6,9 @@
import { createApp, defineAsyncComponent, markRaw } from 'vue';
import { ui } from '@@/js/config.js';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import { common } from './common.js';
import type { Component } from 'vue';
import type { Keymap } from '@/utility/hotkey.js';
import type { DeckProfile } from '@/deck.js';
import { i18n } from '@/i18n.js';
import { alert, confirm, popup, post, toast } from '@/os.js';
import { useStream } from '@/stream.js';
@@ -145,34 +143,12 @@ export async function mainBoot() {
if (themes.length > 0) {
prefer.commit('themes', themes);
}
const plugins = ColdDeviceStorage.get('plugins');
prefer.commit('plugins', plugins.map(p => ({
...p,
installId: (p as any).id,
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('darkTheme', ColdDeviceStorage.get('darkTheme'));
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.notification', store.s.sound_notification 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) {
@@ -523,7 +502,7 @@ export async function mainBoot() {
post();
},
'd': () => {
store.set('darkMode', !store.s.darkMode);
store.commit('darkMode', !store.s.darkMode);
},
's': () => {
mainRouter.push('/search');

View File

@@ -158,7 +158,7 @@ function complete(type: string, value: any) {
let recents = store.s.recentlyUsedEmojis;
recents = recents.filter((emoji: any) => emoji !== value);
recents.unshift(value);
store.set('recentlyUsedEmojis', recents.splice(0, 32));
store.commit('recentlyUsedEmojis', recents.splice(0, 32));
}
}

View File

@@ -220,28 +220,28 @@ function onMousedown(evt: MouseEvent): void {
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
&: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 {
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 {
font-weight: bold;
color: var(--MI_THEME-error);
color: #ff2a2a;
&.primary {
color: #fff;
background: var(--MI_THEME-error);
background: #ff2a2a;
&:not(:disabled):hover {
background: hsl(from var(--MI_THEME-error) h s calc(l + 10));
background: #ff4242;
}
&:not(:disabled):active {
background: hsl(from var(--MI_THEME-error) h s calc(l - 10));
background: #d42e2e;
}
}
}

View File

@@ -432,7 +432,7 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
let recents = store.s.recentlyUsedEmojis;
recents = recents.filter((emoji) => emoji !== key);
recents.unshift(key);
store.set('recentlyUsedEmojis', recents.splice(0, 32));
store.commit('recentlyUsedEmojis', recents.splice(0, 32));
}
}

View File

@@ -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>

View File

@@ -177,12 +177,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
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 type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { isTouchUsing } from '@/utility/touch.js';
import type { Keymap } from '@/utility/hotkey.js';
import { isFocusable } from '@/utility/focus.js';
import { getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
@@ -558,11 +558,11 @@ onBeforeUnmount(() => {
}
&.danger {
--menuFg: var(--MI_THEME-error);
--menuFg: #ff2a2a;
--menuHoverFg: #fff;
--menuHoverBg: var(--MI_THEME-error);
--menuHoverBg: #ff4242;
--menuActiveFg: #fff;
--menuActiveBg: hsl(from var(--MI_THEME-error) h s calc(l - 10));
--menuActiveBg: #d42e2e;
}
&.radio {

View File

@@ -32,20 +32,19 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { url } from '@@/js/config.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 MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/utility/popout.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { useScrollPositionManager } from '@/nirax.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 { claimAchievement } from '@/utility/achievements.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
import { analytics } from '@/analytics.js';
import { DI } from '@/di.js';
const props = defineProps<{
initialPath: string;
@@ -120,7 +119,7 @@ windowRouter.addListener('change', ctx => {
windowRouter.init();
provide(DI.router, windowRouter);
provide('router', windowRouter);
provide('inAppSearchMarkerId', searchMarkerId);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();

View File

@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :marginMin="20" :marginMax="28">
<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>

View File

@@ -177,7 +177,7 @@ const files = ref(props.initialFiles ?? []);
const poll = ref<PollEditorModelValue | null>(null);
const useCw = ref<boolean>(!!props.initialCw);
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);
watch(showAddMfmFunction, () => prefer.commit('enableQuickAddMfmFunction', showAddMfmFunction.value));
const cw = ref<string | null>(props.initialCw ?? null);
@@ -265,19 +265,13 @@ const canPost = computed((): boolean => {
quoteId.value != null
) &&
(textLength.value <= maxTextLength.value) &&
(
useCw.value ?
(
cw.value != null && cw.value.trim() !== '' &&
cwTextLength.value <= maxCwTextLength
) : true
) &&
(cwTextLength.value <= maxCwTextLength) &&
(files.value.length <= 16) &&
(!poll.value || poll.value.choices.length >= 2);
});
const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags'));
const hashtags = computed(store.makeGetterSetter('postFormHashtags'));
const withHashtags = store.model('postFormWithHashtags');
const hashtags = store.model('postFormHashtags');
watch(text, () => {
checkMissingMention();
@@ -486,7 +480,7 @@ function setVisibility() {
changeVisibility: v => {
visibility.value = v;
if (prefer.s.rememberNoteVisibility) {
store.set('visibility', visibility.value);
store.commit('visibility', visibility.value);
}
},
closed: () => dispose(),
@@ -534,7 +528,7 @@ async function toggleLocalOnly() {
localOnly.value = !localOnly.value;
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) {
if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
os.alert({
type: 'error',
text: i18n.ts.cwNotationRequired,
});
return;
}
if (ev) {
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;

View File

@@ -4,15 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root" @contextmenu.prevent.stop="showMenu($event, true)">
<div :class="$style.root">
<div :class="$style.body">
<slot></slot>
</div>
<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>
<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>
@@ -22,32 +21,24 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import type { PREF_DEF } from '@/preferences/def.js';
import * as os from '@/os.js';
import { prefer } from '@/preferences.js';
import { profileManager } from '@/preferences.js';
const props = withDefaults(defineProps<{
k: keyof typeof PREF_DEF;
}>(), {
});
const isAccountOverrided = ref(prefer.isAccountOverrided(props.k));
const isSyncEnabled = ref(prefer.isSyncEnabled(props.k));
const isAccountOverrided = ref(profileManager.isAccountOverrided(props.k));
function showMenu(ev: MouseEvent, contextmenu?: boolean) {
function showMenu(ev: MouseEvent) {
const i = window.setInterval(() => {
isAccountOverrided.value = prefer.isAccountOverrided(props.k);
isSyncEnabled.value = prefer.isSyncEnabled(props.k);
isAccountOverrided.value = profileManager.isAccountOverrided(props.k);
}, 100);
if (contextmenu) {
os.contextMenu(prefer.getPerPrefMenu(props.k), ev).then(() => {
os.popupMenu(profileManager.getPerPrefMenu(props.k), ev.currentTarget ?? ev.target, {
onClosing: () => {
window.clearInterval(i);
});
} else {
os.popupMenu(prefer.getPerPrefMenu(props.k), ev.currentTarget ?? ev.target, {
onClosing: () => {
window.clearInterval(i);
},
});
}
},
});
}
</script>

View File

@@ -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>

View File

@@ -131,7 +131,7 @@ async function ok() {
let recents = store.s.recentlyUsedUsers;
recents = recents.filter(x => x !== selected.value?.id);
recents.unshift(selected.value.id);
store.set('recentlyUsedUsers', recents.splice(0, 16));
store.commit('recentlyUsedUsers', recents.splice(0, 16));
}
function cancel() {

View File

@@ -152,7 +152,7 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const page = ref(store.s.accountSetupWizard);
watch(page, () => {
store.set('accountSetupWizard', page.value);
store.commit('accountSetupWizard', page.value);
});
async function close(skip: boolean) {
@@ -165,11 +165,11 @@ async function close(skip: boolean) {
}
dialog.value?.close();
store.set('accountSetupWizard', -1);
store.commit('accountSetupWizard', -1);
}
function setupComplete() {
store.set('accountSetupWizard', -1);
store.commit('accountSetupWizard', -1);
dialog.value?.close();
}
@@ -194,7 +194,7 @@ async function later(later: boolean) {
}
dialog.value?.close();
store.set('accountSetupWizard', 0);
store.commit('accountSetupWizard', 0);
}
</script>

View File

@@ -113,7 +113,7 @@ const shouldHide = ref(!prefer.s.forceShowAds && $i && $i.policies.canHideAds &&
function reduceFrequency(): void {
if (chosen.value == null) 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();
chosen.value = choseAd();
showMenu.value = false;

View File

@@ -47,9 +47,9 @@ import { scrollToTop } from '@@/js/scroll.js';
import XTabs from './MkPageHeader.tabs.vue';
import type { Tab } from './MkPageHeader.tabs.vue';
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 { injectReactiveMetadata } from '@/page.js';
import { injectReactiveMetadata } from '@/utility/page-metadata.js';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
const props = withDefaults(defineProps<{

View File

@@ -24,21 +24,20 @@ import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';
const props = defineProps<{
router?: IRouter;
nested?: boolean;
}>();
const router = props.router ?? inject(DI.router);
const router = props.router ?? inject('router');
if (router == null) {
throw new Error('no router provided');
}
const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1);
const currentDepth = inject('routerCurrentDepth', 0);
provide('routerCurrentDepth', currentDepth + 1);
function resolveNested(current: Resolved, d = 0): Resolved | null {
if (!props.nested) return current;

View File

@@ -3,23 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { throttle } from 'throttle-debounce';
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 { 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 { prefer } from '@/preferences.js';
import * as os from '@/os.js';
export type DeckProfile = {
name: string;
id: string;
columns: Column[];
layout: Column['id'][][];
};
import { store } from '@/store.js';
type ColumnWidget = {
name: string;
@@ -63,132 +53,127 @@ export type Column = {
soundSetting?: SoundStore;
};
const _currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
const __currentProfile = _currentProfile ? deepClone(_currentProfile) : null;
export const columns = ref(__currentProfile ? __currentProfile.columns : []);
export const layout = ref(__currentProfile ? __currentProfile.layout : []);
export const loadDeck = async () => {
let deck;
if (prefer.s['deck.profile'] == null) {
addProfile('Main');
}
try {
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() {
const currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
if (currentProfile == null) return;
store.commit('deck.columns', []);
store.commit('deck.layout', []);
return;
}
throw err;
}
const newProfile = deepClone(currentProfile);
newProfile.columns = columns.value;
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();
store.commit('deck.columns', deck.columns);
store.commit('deck.layout', deck.layout);
};
function switchProfile(profile: DeckProfile) {
prefer.commit('deck.profile', profile.name);
const currentProfile = deepClone(profile);
columns.value = currentProfile.columns;
layout.value = currentProfile.layout;
forceSaveCurrentDeckProfile();
export async function forceSaveDeck() {
await misskeyApi('i/registry/set', {
scope: ['client', 'deck', 'profiles'],
key: store.s['deck.profile'],
value: {
columns: store.r['deck.columns'].value,
layout: store.r['deck.layout'].value,
},
});
}
function addProfile(name: string) {
if (name.trim() === '') return;
if (prefer.s['deck.profiles'].find(p => p.name === name)) return;
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
export const saveDeck = throttle(1000, () => {
forceSaveDeck();
});
const newProfile: DeckProfile = {
id: uuid(),
name,
columns: [],
layout: [],
};
prefer.commit('deck.profiles', [...prefer.s['deck.profiles'], newProfile]);
switchProfile(newProfile);
export async function getProfiles(): Promise<string[]> {
return await misskeyApi('i/registry/keys', {
scope: ['client', 'deck', 'profiles'],
});
}
function createFirstProfile() {
addProfile('Main');
}
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 async function deleteProfile(key: string): Promise<void> {
return await misskeyApi('i/registry/remove', {
scope: ['client', 'deck', 'profiles'],
key: key,
});
}
export function addColumn(column: Column) {
if (column.name === undefined) column.name = null;
columns.value.push(column);
layout.value.push([column.id]);
saveCurrentDeckProfile();
store.commit('deck.columns', [...store.s['deck.columns'], column]);
store.commit('deck.layout', [...store.s['deck.layout'], [column.id]]);
saveDeck();
}
export function removeColumn(id: Column['id']) {
columns.value = columns.value.filter(c => c.id !== id);
layout.value = layout.value.map(ids => ids.filter(_id => _id !== id)).filter(ids => ids.length > 0);
saveCurrentDeckProfile();
store.commit('deck.columns', store.s['deck.columns'].filter(c => c.id !== id));
store.commit('deck.layout', store.s['deck.layout']
.map(ids => ids.filter(_id => _id !== id))
.filter(ids => ids.length > 0));
saveDeck();
}
export function swapColumn(a: Column['id'], b: Column['id']) {
const aX = layout.value.findIndex(ids => ids.indexOf(a) !== -1);
const aY = layout.value[aX].findIndex(id => id === a);
const bX = layout.value.findIndex(ids => ids.indexOf(b) !== -1);
const bY = layout.value[bX].findIndex(id => id === b);
const newLayout = deepClone(layout.value);
newLayout[aX][aY] = b;
newLayout[bX][bY] = a;
layout.value = newLayout;
saveCurrentDeckProfile();
const aX = store.s['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1);
const aY = store.s['deck.layout'][aX].findIndex(id => id === a);
const bX = store.s['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1);
const bY = store.s['deck.layout'][bX].findIndex(id => id === b);
const layout = deepClone(store.s['deck.layout']);
layout[aX][aY] = b;
layout[bX][bY] = a;
store.commit('deck.layout', layout);
saveDeck();
}
export function swapLeftColumn(id: Column['id']) {
const newLayout = deepClone(layout.value);
layout.value.some((ids, i) => {
const layout = deepClone(store.s['deck.layout']);
store.s['deck.layout'].some((ids, i) => {
if (ids.includes(id)) {
const left = layout.value[i - 1];
const left = store.s['deck.layout'][i - 1];
if (left) {
newLayout[i - 1] = layout.value[i];
newLayout[i] = left;
layout.value = newLayout;
layout[i - 1] = store.s['deck.layout'][i];
layout[i] = left;
store.commit('deck.layout', layout);
}
return true;
}
return false;
});
saveCurrentDeckProfile();
saveDeck();
}
export function swapRightColumn(id: Column['id']) {
const newLayout = deepClone(layout.value);
layout.value.some((ids, i) => {
const layout = deepClone(store.s['deck.layout']);
store.s['deck.layout'].some((ids, i) => {
if (ids.includes(id)) {
const right = layout.value[i + 1];
const right = store.s['deck.layout'][i + 1];
if (right) {
newLayout[i + 1] = layout.value[i];
newLayout[i] = right;
layout.value = newLayout;
layout[i + 1] = store.s['deck.layout'][i];
layout[i] = right;
store.commit('deck.layout', layout);
}
return true;
}
return false;
});
saveCurrentDeckProfile();
saveDeck();
}
export function swapUpColumn(id: Column['id']) {
const newLayout = deepClone(layout.value);
const idsIndex = layout.value.findIndex(ids => ids.includes(id));
const ids = deepClone(layout.value[idsIndex]);
const layout = deepClone(store.s['deck.layout']);
const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
const ids = deepClone(store.s['deck.layout'][idsIndex]);
ids.some((x, i) => {
if (x === id) {
const up = ids[i - 1];
@@ -196,20 +181,20 @@ export function swapUpColumn(id: Column['id']) {
ids[i - 1] = id;
ids[i] = up;
newLayout[idsIndex] = ids;
layout.value = newLayout;
layout[idsIndex] = ids;
store.commit('deck.layout', layout);
}
return true;
}
return false;
});
saveCurrentDeckProfile();
saveDeck();
}
export function swapDownColumn(id: Column['id']) {
const newLayout = deepClone(layout.value);
const idsIndex = layout.value.findIndex(ids => ids.includes(id));
const ids = deepClone(layout.value[idsIndex]);
const layout = deepClone(store.s['deck.layout']);
const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
const ids = deepClone(store.s['deck.layout'][idsIndex]);
ids.some((x, i) => {
if (x === id) {
const down = ids[i + 1];
@@ -217,137 +202,105 @@ export function swapDownColumn(id: Column['id']) {
ids[i + 1] = id;
ids[i] = down;
newLayout[idsIndex] = ids;
layout.value = newLayout;
layout[idsIndex] = ids;
store.commit('deck.layout', layout);
}
return true;
}
return false;
});
saveCurrentDeckProfile();
saveDeck();
}
export function stackLeftColumn(id: Column['id']) {
let newLayout = deepClone(layout.value);
const i = layout.value.findIndex(ids => ids.includes(id));
newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
newLayout[i - 1].push(id);
newLayout = newLayout.filter(ids => ids.length > 0);
layout.value = newLayout;
saveCurrentDeckProfile();
let layout = deepClone(store.s['deck.layout']);
const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout[i - 1].push(id);
layout = layout.filter(ids => ids.length > 0);
store.commit('deck.layout', layout);
saveDeck();
}
export function popRightColumn(id: Column['id']) {
let newLayout = deepClone(layout.value);
const i = layout.value.findIndex(ids => ids.includes(id));
const affected = newLayout[i];
newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
newLayout.splice(i + 1, 0, [id]);
newLayout = newLayout.filter(ids => ids.length > 0);
layout.value = newLayout;
let layout = deepClone(store.s['deck.layout']);
const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
const affected = layout[i];
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout.splice(i + 1, 0, [id]);
layout = layout.filter(ids => ids.length > 0);
store.commit('deck.layout', layout);
const newColumns = deepClone(columns.value);
for (const column of newColumns) {
const columns = deepClone(store.s['deck.columns']);
for (const column of columns) {
if (affected.includes(column.id)) {
column.active = true;
}
}
columns.value = newColumns;
store.commit('deck.columns', columns);
saveCurrentDeckProfile();
saveDeck();
}
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
const newColumns = deepClone(columns.value);
const columnIndex = columns.value.findIndex(c => c.id === id);
const column = deepClone(columns.value[columnIndex]);
const columns = deepClone(store.s['deck.columns']);
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
const column = deepClone(store.s['deck.columns'][columnIndex]);
if (column == null) return;
if (column.widgets == null) column.widgets = [];
column.widgets.unshift(widget);
newColumns[columnIndex] = column;
columns.value = newColumns;
saveCurrentDeckProfile();
columns[columnIndex] = column;
store.commit('deck.columns', columns);
saveDeck();
}
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
const newColumns = deepClone(columns.value);
const columnIndex = columns.value.findIndex(c => c.id === id);
const column = deepClone(columns.value[columnIndex]);
const columns = deepClone(store.s['deck.columns']);
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
const column = deepClone(store.s['deck.columns'][columnIndex]);
if (column == null) return;
if (column.widgets == null) column.widgets = [];
column.widgets = column.widgets.filter(w => w.id !== widget.id);
newColumns[columnIndex] = column;
columns.value = newColumns;
saveCurrentDeckProfile();
columns[columnIndex] = column;
store.commit('deck.columns', columns);
saveDeck();
}
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
const newColumns = deepClone(columns.value);
const columnIndex = columns.value.findIndex(c => c.id === id);
const column = deepClone(columns.value[columnIndex]);
const columns = deepClone(store.s['deck.columns']);
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
const column = deepClone(store.s['deck.columns'][columnIndex]);
if (column == null) return;
column.widgets = widgets;
newColumns[columnIndex] = column;
columns.value = newColumns;
saveCurrentDeckProfile();
columns[columnIndex] = column;
store.commit('deck.columns', columns);
saveDeck();
}
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
const newColumns = deepClone(columns.value);
const columnIndex = columns.value.findIndex(c => c.id === id);
const column = deepClone(columns.value[columnIndex]);
const columns = deepClone(store.s['deck.columns']);
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
const column = deepClone(store.s['deck.columns'][columnIndex]);
if (column == null) return;
if (column.widgets == null) column.widgets = [];
column.widgets = column.widgets.map(w => w.id === widgetId ? {
...w,
data: widgetData,
} : w);
newColumns[columnIndex] = column;
columns.value = newColumns;
saveCurrentDeckProfile();
columns[columnIndex] = column;
store.commit('deck.columns', columns);
saveDeck();
}
export function updateColumn(id: Column['id'], column: Partial<Column>) {
const newColumns = deepClone(columns.value);
const columnIndex = columns.value.findIndex(c => c.id === id);
const currentColumn = deepClone(columns.value[columnIndex]);
const columns = deepClone(store.s['deck.columns']);
const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
const currentColumn = deepClone(store.s['deck.columns'][columnIndex]);
if (currentColumn == null) return;
for (const [k, v] of Object.entries(column)) {
currentColumn[k] = v;
}
newColumns[columnIndex] = currentColumn;
columns.value = newColumns;
saveCurrentDeckProfile();
}
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);
columns[columnIndex] = currentColumn;
store.commit('deck.columns', columns);
saveDeck();
}

View File

@@ -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>,
};

View File

@@ -33,7 +33,7 @@ import MkLink from '@/components/MkLink.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { unisonReload } from '@/utility/unison-reload.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 { prefer } from '@/preferences.js';
import { serverErrorImageUrl } from '@/instance.js';
@@ -67,7 +67,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.error,
icon: 'ti ti-alert-triangle',
}));

View File

@@ -145,7 +145,7 @@ import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { store } from '@/store.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 { $i } from '@/account.js';
@@ -450,7 +450,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.aboutMisskey,
icon: null,
}));

View File

@@ -28,7 +28,7 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.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';
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
@@ -81,7 +81,7 @@ const headerTabs = computed(() => {
return items;
});
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.instanceInfo,
icon: 'ti ti-info-circle',
}));

View File

@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
import MkAchievements from '@/components/MkAchievements.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import { $i } from '@/account.js';
import { claimAchievement } from '@/utility/achievements.js';
@@ -48,7 +48,7 @@ onDeactivated(() => {
}
});
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.achievements,
icon: 'ti ti-medal',
}));

View File

@@ -85,7 +85,7 @@ import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import { iAmAdmin, iAmModerator } from '@/account.js';
const tab = ref('overview');
@@ -161,7 +161,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-code',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
icon: 'ti ti-file',
}));

View File

@@ -231,7 +231,7 @@ import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 { iAmAdmin, $i, iAmModerator } from '@/account.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
@@ -545,7 +545,7 @@ const headerTabs = computed(() => isSystem.value ? [{
icon: 'ti ti-code',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: user.value ? acct(user.value) : i18n.ts.userInfo,
icon: 'ti ti-user-exclamation',
}));

View File

@@ -39,7 +39,7 @@ import { scrollToTop } from '@@/js/scroll.js';
import { popupMenu } from '@/os.js';
import MkButton from '@/components/MkButton.vue';
import { globalEvents } from '@/events.js';
import { injectReactiveMetadata } from '@/page.js';
import { injectReactiveMetadata } from '@/utility/page-metadata.js';
type Tab = {
key?: string | null;

View File

@@ -65,7 +65,7 @@ import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import { store } from '@/store.js';
@@ -93,14 +93,14 @@ function resolved(reportId) {
}
function closeTutorial() {
store.set('abusesTutorial', false);
store.commit('abusesTutorial', false);
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.abuseReports,
icon: 'ti ti-exclamation-circle',
}));

View File

@@ -98,7 +98,7 @@ import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const ads = ref<Misskey.entities.Ad[]>([]);
@@ -255,7 +255,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.ads,
icon: 'ti ti-ad',
}));

View File

@@ -96,7 +96,7 @@ import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 MkTextarea from '@/components/MkTextarea.vue';
@@ -199,7 +199,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));

View File

@@ -114,7 +114,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { instance, fetchInstance } from '@/instance.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 MkColorInput from '@/components/MkColorInput.vue';
import { host } from '@@/js/config.js';
@@ -175,7 +175,7 @@ function save() {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.branding,
icon: 'ti ti-paint',
}));

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, ref } from 'vue';
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 XGridRemoteComponent from '@/pages/admin/custom-emojis-manager.remote.vue';
import MkPageHeader from '@/components/global/MkPageHeader.vue';
@@ -36,7 +36,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.remote,
}]);
definePage(computed(() => ({
definePageMetadata(computed(() => ({
title: i18n.ts.customEmojis,
icon: 'ti ti-icons',
needWideArea: true,

View File

@@ -25,7 +25,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import bytes from '@/filters/bytes.js';
import number from '@/filters/number.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));
@@ -33,7 +33,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.database,
icon: 'ti ti-database',
}));

View File

@@ -76,7 +76,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
const enableEmail = ref<boolean>(false);
@@ -130,7 +130,7 @@ function save() {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.emailServer,
icon: 'ti ti-mail',
}));

View File

@@ -52,7 +52,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkFolder from '@/components/MkFolder.vue';
const deeplAuthKey = ref<string>('');
@@ -88,7 +88,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.externalServices,
icon: 'ti ti-link',
}));

View File

@@ -67,7 +67,7 @@ import MkPagination from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const host = ref('');
const state = ref('federating');
@@ -112,7 +112,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.federation,
icon: 'ti ti-whirl',
}));

View File

@@ -44,7 +44,7 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
import * as os from '@/os.js';
import { lookupFile } from '@/utility/admin-lookup.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const origin = ref('local');
const type = ref<string | null>(null);
@@ -85,7 +85,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.files,
icon: 'ti ti-cloud',
}));

View File

@@ -41,8 +41,8 @@ import { lookup } from '@/utility/lookup.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import type { PageMetadata } from '@/page.js';
import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/utility/page-metadata.js';
import type { PageMetadata } from '@/utility/page-metadata.js';
import { useRouter } from '@/router/supplier.js';
const isEmpty = (x: string | null) => x == null || x === '';
@@ -318,7 +318,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => INFO.value);
definePageMetadata(() => INFO.value);
defineExpose({
header: {

View File

@@ -68,7 +68,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkPagination from '@/components/MkPagination.vue';
import type { Paging } from '@/components/MkPagination.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>>();
@@ -114,7 +114,7 @@ function deleted(id: string) {
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.invite,
icon: 'ti ti-user-plus',
}));

View File

@@ -137,7 +137,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.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 FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue';
@@ -259,7 +259,7 @@ function save_mediaSilencedHosts() {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.moderation,
icon: 'ti ti-shield',
}));

View File

@@ -38,7 +38,7 @@ import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkPagination from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
const logs = shallowRef<InstanceType<typeof MkPagination>>();
@@ -59,7 +59,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.moderationLogs,
icon: 'ti ti-list-search',
}));

View File

@@ -93,7 +93,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
const useObjectStorage = ref<boolean>(false);
@@ -149,7 +149,7 @@ function save() {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.objectStorage,
icon: 'ti ti-cloud',
}));

View File

@@ -82,7 +82,7 @@ import * as os from '@/os.js';
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
const rootEl = shallowRef<HTMLElement>();
@@ -184,7 +184,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.dashboard,
icon: 'ti ti-dashboard',
}));

View File

@@ -114,7 +114,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.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 MkFolder from '@/components/MkFolder.vue';
import MkInput from '@/components/MkInput.vue';
@@ -202,7 +202,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.other,
icon: 'ti ti-adjustments',
}));

View File

@@ -23,7 +23,7 @@ import XHeader from './_header_.vue';
import * as os from '@/os.js';
import * as config from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
export type ApQueueDomain = 'deliver' | 'inbox';
@@ -71,7 +71,7 @@ const headerTabs = computed(() => [{
title: 'Inbox',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.jobQueue,
icon: 'ti ti-clock-play',
}));

View File

@@ -31,7 +31,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
@@ -84,7 +84,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.relays,
icon: 'ti ti-planet',
}));

View File

@@ -30,7 +30,7 @@ import XEditor from './roles.editor.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 { rolesCache } from '@/cache.js';
import { useRouter } from '@/router/supplier.js';
@@ -87,7 +87,7 @@ async function save() {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
icon: 'ti ti-badge',
}));

View File

@@ -69,7 +69,7 @@ import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
@@ -170,7 +170,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: `${i18n.ts.role}: ${role.name}`,
icon: 'ti ti-badge',
}));

View File

@@ -292,7 +292,7 @@ import MkRolePreview from '@/components/MkRolePreview.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 MkFoldableSection from '@/components/MkFoldableSection.vue';
import { useRouter } from '@/router/supplier.js';
@@ -338,7 +338,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.roles,
icon: 'ti ti-badges',
}));

View File

@@ -134,7 +134,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.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 MkFormFooter from '@/components/MkFormFooter.vue';
@@ -206,7 +206,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.security,
icon: 'ti ti-lock',
}));

View File

@@ -46,7 +46,7 @@ import XHeader from './_header_.vue';
import * as os from '@/os.js';
import { fetchInstance, instance } from '@/instance.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 MkInput from '@/components/MkInput.vue';
@@ -67,7 +67,7 @@ const remove = (index: number): void => {
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.serverRules,
icon: 'ti ti-checkbox',
}));

View File

@@ -269,7 +269,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance, instance } from '@/instance.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 MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
@@ -391,7 +391,7 @@ const proxyAccountForm = useForm({
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.general,
icon: 'ti ti-settings',
}));

View File

@@ -30,7 +30,7 @@ import { computed, onMounted, ref } from 'vue';
import { entities } from 'misskey-js';
import XItem from './system-webhook.item.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 XHeader from '@/pages/admin/_header_.vue';
import MkButton from '@/components/MkButton.vue';
@@ -82,7 +82,7 @@ onMounted(async () => {
await fetchWebhooks();
});
definePage(() => ({
definePageMetadata(() => ({
title: 'SystemWebhook',
icon: 'ti ti-webhook',
}));

View File

@@ -70,7 +70,7 @@ import MkPagination from '@/components/MkPagination.vue';
import * as os from '@/os.js';
import { lookupUser } from '@/utility/admin-lookup.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 { dateString } from '@/filters/date.js';
@@ -169,7 +169,7 @@ watchEffect(() => {
}));
});
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.users,
icon: 'ti ti-users',
}));

View File

@@ -16,11 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.ads,
icon: 'ti ti-ad',
}));

View File

@@ -54,7 +54,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.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 { prefer } from '@/preferences.js';
@@ -102,7 +102,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));

View File

@@ -55,7 +55,7 @@ import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import { $i, updateAccountPartial } from '@/account.js';
const paginationCurrent = {
@@ -111,7 +111,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-point',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));

View File

@@ -30,7 +30,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import { scroll } from '@@/js/scroll.js';
import * as os from '@/os.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 { useRouter } from '@/router/supplier.js';
@@ -88,7 +88,7 @@ const headerActions = computed(() => antenna.value ? [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: antenna.value ? antenna.value.name : i18n.ts.antennas,
icon: 'ti ti-antenna',
}));

View File

@@ -42,7 +42,7 @@ import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const body = ref('{}');
const endpoint = ref('');
@@ -87,7 +87,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: 'API console',
icon: 'ti ti-terminal-2',
}));

View File

@@ -48,7 +48,7 @@ import XForm from './auth.form.vue';
import MkSignin from '@/components/MkSignin.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { $i, login } from '@/account.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
@@ -97,7 +97,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts._auth.shareAccessTitle,
icon: 'ti ti-apps',
}));

View File

@@ -32,7 +32,7 @@ import { signinRequired } from '@/account.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const $i = signinRequired();
@@ -86,7 +86,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.avatarDecorations,
icon: 'ti ti-sparkles',
}));

View File

@@ -77,7 +77,7 @@ import MkColorInput from '@/components/MkColorInput.vue';
import { selectFile } from '@/utility/select-file.js';
import * as os from '@/os.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 MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
@@ -202,7 +202,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
definePageMetadata(() => ({
title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
icon: 'ti ti-device-tv',
}));

View File

@@ -84,7 +84,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { $i, iAmModerator } from '@/account.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 MkNotes from '@/components/MkNotes.vue';
import { favoritedChannelsCache } from '@/cache.js';
@@ -265,7 +265,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-search',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: channel.value ? channel.value.name : i18n.ts.channel,
icon: 'ti ti-device-tv',
}));

View File

@@ -69,7 +69,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.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 { useRouter } from '@/router/supplier.js';
@@ -161,7 +161,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-edit',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.channel,
icon: 'ti ti-device-tv',
}));

View File

@@ -14,9 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import MkClickerGame from '@/components/MkClickerGame.vue';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
definePage(() => ({
definePageMetadata(() => ({
title: '🍪👈',
icon: 'ti ti-cookie',
}));

View File

@@ -40,7 +40,7 @@ import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.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 { clipsCache } from '@/cache.js';
import { isSupportShare } from '@/utility/navigator.js';
@@ -193,7 +193,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
},
}] : null);
definePage(() => ({
definePageMetadata(() => ({
title: clip.value ? clip.value.name : i18n.ts.clip,
icon: 'ti ti-paperclip',
}));

View File

@@ -37,11 +37,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { i18n } from '@/i18n.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 MkLink from '@/components/MkLink.vue';
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.inquiry,
icon: 'ti ti-help-circle',
}));

View File

@@ -86,7 +86,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
@@ -326,7 +326,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.remote,
}]);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts.customEmojis,
icon: 'ti ti-icons',
}));

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { definePageMetadata } from '@/utility/page-metadata.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
const props = defineProps<{
@@ -48,7 +48,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-pencil',
}]);
definePage(() => ({
definePageMetadata(() => ({
title: i18n.ts._fileViewer.title,
icon: 'ti ti-file',
}));

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