Merge branch 'develop' into reversi
This commit is contained in:
commit
4c43ee4b53
|
@ -24,6 +24,7 @@
|
||||||
- Feat: 絵文字の詳細ダイアログを追加
|
- Feat: 絵文字の詳細ダイアログを追加
|
||||||
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
|
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
|
||||||
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
|
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
|
||||||
|
- Feat: スワイプでタブを切り替えられるように
|
||||||
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
|
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
|
||||||
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
||||||
|
|
|
@ -6,54 +6,171 @@ import ts from 'typescript';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
const parameterRegExp = /\{(\w+)\}/g;
|
||||||
|
|
||||||
|
function createMemberType(item) {
|
||||||
|
if (typeof item !== 'string') {
|
||||||
|
return ts.factory.createTypeLiteralNode(createMembers(item));
|
||||||
|
}
|
||||||
|
const parameters = Array.from(
|
||||||
|
item.matchAll(parameterRegExp),
|
||||||
|
([, parameter]) => parameter,
|
||||||
|
);
|
||||||
|
if (!parameters.length) {
|
||||||
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
||||||
|
}
|
||||||
|
return ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
|
[
|
||||||
|
ts.factory.createUnionTypeNode(
|
||||||
|
parameters.map((parameter) =>
|
||||||
|
ts.factory.createLiteralTypeNode(
|
||||||
|
ts.factory.createStringLiteral(parameter),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createMembers(record) {
|
function createMembers(record) {
|
||||||
return Object.entries(record)
|
return Object.entries(record).map(([k, v]) =>
|
||||||
.map(([k, v]) => ts.factory.createPropertySignature(
|
ts.factory.createPropertySignature(
|
||||||
undefined,
|
undefined,
|
||||||
ts.factory.createStringLiteral(k),
|
ts.factory.createStringLiteral(k),
|
||||||
undefined,
|
undefined,
|
||||||
typeof v === 'string'
|
createMemberType(v),
|
||||||
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
),
|
||||||
: ts.factory.createTypeLiteralNode(createMembers(v)),
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function generateDTS() {
|
export default function generateDTS() {
|
||||||
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
||||||
const members = createMembers(locale);
|
const members = createMembers(locale);
|
||||||
const elements = [
|
const elements = [
|
||||||
|
ts.factory.createVariableStatement(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
||||||
|
ts.factory.createVariableDeclarationList(
|
||||||
|
[
|
||||||
|
ts.factory.createVariableDeclaration(
|
||||||
|
ts.factory.createIdentifier('kParameters'),
|
||||||
|
undefined,
|
||||||
|
ts.factory.createTypeOperatorNode(
|
||||||
|
ts.SyntaxKind.UniqueKeyword,
|
||||||
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ts.NodeFlags.Const,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ts.factory.createInterfaceDeclaration(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||||
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
|
[
|
||||||
|
ts.factory.createTypeParameterDeclaration(
|
||||||
|
undefined,
|
||||||
|
ts.factory.createIdentifier('T'),
|
||||||
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('string'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
ts.factory.createPropertySignature(
|
||||||
|
undefined,
|
||||||
|
ts.factory.createComputedPropertyName(
|
||||||
|
ts.factory.createIdentifier('kParameters'),
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('T'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ts.factory.createInterfaceDeclaration(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||||
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
ts.factory.createIndexSignature(
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
ts.factory.createParameterDeclaration(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
ts.factory.createIdentifier('_'),
|
||||||
|
undefined,
|
||||||
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ts.factory.createUnionTypeNode([
|
||||||
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
|
[ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)],
|
||||||
|
),
|
||||||
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
ts.factory.createInterfaceDeclaration(
|
ts.factory.createInterfaceDeclaration(
|
||||||
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||||
ts.factory.createIdentifier('Locale'),
|
ts.factory.createIdentifier('Locale'),
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
[
|
||||||
|
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
||||||
|
ts.factory.createExpressionWithTypeArguments(
|
||||||
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
members,
|
members,
|
||||||
),
|
),
|
||||||
ts.factory.createVariableStatement(
|
ts.factory.createVariableStatement(
|
||||||
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
||||||
ts.factory.createVariableDeclarationList(
|
ts.factory.createVariableDeclarationList(
|
||||||
[ts.factory.createVariableDeclaration(
|
[
|
||||||
ts.factory.createIdentifier('locales'),
|
ts.factory.createVariableDeclaration(
|
||||||
undefined,
|
ts.factory.createIdentifier('locales'),
|
||||||
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
|
|
||||||
undefined,
|
undefined,
|
||||||
[ts.factory.createParameterDeclaration(
|
ts.factory.createTypeLiteralNode([
|
||||||
undefined,
|
ts.factory.createIndexSignature(
|
||||||
undefined,
|
undefined,
|
||||||
ts.factory.createIdentifier('lang'),
|
[
|
||||||
undefined,
|
ts.factory.createParameterDeclaration(
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
)],
|
ts.factory.createIdentifier('lang'),
|
||||||
ts.factory.createTypeReferenceNode(
|
undefined,
|
||||||
ts.factory.createIdentifier('Locale'),
|
ts.factory.createKeywordTypeNode(
|
||||||
undefined,
|
ts.SyntaxKind.StringKeyword,
|
||||||
),
|
),
|
||||||
)]),
|
undefined,
|
||||||
undefined,
|
),
|
||||||
)],
|
],
|
||||||
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('Locale'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ts.NodeFlags.Const,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ts.factory.createFunctionDeclaration(
|
ts.factory.createFunctionDeclaration(
|
||||||
|
@ -70,16 +187,28 @@ export default function generateDTS() {
|
||||||
),
|
),
|
||||||
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
|
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
|
||||||
];
|
];
|
||||||
const printed = ts.createPrinter({
|
const printed = ts
|
||||||
newLine: ts.NewLineKind.LineFeed,
|
.createPrinter({
|
||||||
}).printList(
|
newLine: ts.NewLineKind.LineFeed,
|
||||||
ts.ListFormat.MultiLine,
|
})
|
||||||
ts.factory.createNodeArray(elements),
|
.printList(
|
||||||
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
|
ts.ListFormat.MultiLine,
|
||||||
);
|
ts.factory.createNodeArray(elements),
|
||||||
|
ts.createSourceFile(
|
||||||
|
'index.d.ts',
|
||||||
|
'',
|
||||||
|
ts.ScriptTarget.ESNext,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TS,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
|
fs.writeFileSync(
|
||||||
|
`${__dirname}/index.d.ts`,
|
||||||
|
`/* eslint-disable */
|
||||||
// This file is generated by locales/generateDTS.js
|
// This file is generated by locales/generateDTS.js
|
||||||
// Do not edit this file directly.
|
// Do not edit this file directly.
|
||||||
${printed}`, 'utf-8');
|
${printed}`,
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file is generated by locales/generateDTS.js
|
// This file is generated by locales/generateDTS.js
|
||||||
// Do not edit this file directly.
|
// Do not edit this file directly.
|
||||||
export interface Locale {
|
declare const kParameters: unique symbol;
|
||||||
|
export interface ParameterizedString<T extends string> {
|
||||||
|
[kParameters]: T;
|
||||||
|
}
|
||||||
|
export interface ILocale {
|
||||||
|
[_: string]: string | ParameterizedString<string> | ILocale;
|
||||||
|
}
|
||||||
|
export interface Locale extends ILocale {
|
||||||
"_lang_": string;
|
"_lang_": string;
|
||||||
"headlineMisskey": string;
|
"headlineMisskey": string;
|
||||||
"introMisskey": string;
|
"introMisskey": string;
|
||||||
"poweredByMisskeyDescription": string;
|
"poweredByMisskeyDescription": ParameterizedString<"name">;
|
||||||
"monthAndDay": string;
|
"monthAndDay": ParameterizedString<"month" | "day">;
|
||||||
"search": string;
|
"search": string;
|
||||||
"notifications": string;
|
"notifications": string;
|
||||||
"username": string;
|
"username": string;
|
||||||
|
@ -18,7 +25,7 @@ export interface Locale {
|
||||||
"cancel": string;
|
"cancel": string;
|
||||||
"noThankYou": string;
|
"noThankYou": string;
|
||||||
"enterUsername": string;
|
"enterUsername": string;
|
||||||
"renotedBy": string;
|
"renotedBy": ParameterizedString<"user">;
|
||||||
"noNotes": string;
|
"noNotes": string;
|
||||||
"noNotifications": string;
|
"noNotifications": string;
|
||||||
"instance": string;
|
"instance": string;
|
||||||
|
@ -78,8 +85,8 @@ export interface Locale {
|
||||||
"export": string;
|
"export": string;
|
||||||
"files": string;
|
"files": string;
|
||||||
"download": string;
|
"download": string;
|
||||||
"driveFileDeleteConfirm": string;
|
"driveFileDeleteConfirm": ParameterizedString<"name">;
|
||||||
"unfollowConfirm": string;
|
"unfollowConfirm": ParameterizedString<"name">;
|
||||||
"exportRequested": string;
|
"exportRequested": string;
|
||||||
"importRequested": string;
|
"importRequested": string;
|
||||||
"lists": string;
|
"lists": string;
|
||||||
|
@ -183,9 +190,9 @@ export interface Locale {
|
||||||
"wallpaper": string;
|
"wallpaper": string;
|
||||||
"setWallpaper": string;
|
"setWallpaper": string;
|
||||||
"removeWallpaper": string;
|
"removeWallpaper": string;
|
||||||
"searchWith": string;
|
"searchWith": ParameterizedString<"q">;
|
||||||
"youHaveNoLists": string;
|
"youHaveNoLists": string;
|
||||||
"followConfirm": string;
|
"followConfirm": ParameterizedString<"name">;
|
||||||
"proxyAccount": string;
|
"proxyAccount": string;
|
||||||
"proxyAccountDescription": string;
|
"proxyAccountDescription": string;
|
||||||
"host": string;
|
"host": string;
|
||||||
|
@ -208,7 +215,7 @@ export interface Locale {
|
||||||
"software": string;
|
"software": string;
|
||||||
"version": string;
|
"version": string;
|
||||||
"metadata": string;
|
"metadata": string;
|
||||||
"withNFiles": string;
|
"withNFiles": ParameterizedString<"n">;
|
||||||
"monitor": string;
|
"monitor": string;
|
||||||
"jobQueue": string;
|
"jobQueue": string;
|
||||||
"cpuAndMemory": string;
|
"cpuAndMemory": string;
|
||||||
|
@ -237,7 +244,7 @@ export interface Locale {
|
||||||
"processing": string;
|
"processing": string;
|
||||||
"preview": string;
|
"preview": string;
|
||||||
"default": string;
|
"default": string;
|
||||||
"defaultValueIs": string;
|
"defaultValueIs": ParameterizedString<"value">;
|
||||||
"noCustomEmojis": string;
|
"noCustomEmojis": string;
|
||||||
"noJobs": string;
|
"noJobs": string;
|
||||||
"federating": string;
|
"federating": string;
|
||||||
|
@ -266,8 +273,8 @@ export interface Locale {
|
||||||
"imageUrl": string;
|
"imageUrl": string;
|
||||||
"remove": string;
|
"remove": string;
|
||||||
"removed": string;
|
"removed": string;
|
||||||
"removeAreYouSure": string;
|
"removeAreYouSure": ParameterizedString<"x">;
|
||||||
"deleteAreYouSure": string;
|
"deleteAreYouSure": ParameterizedString<"x">;
|
||||||
"resetAreYouSure": string;
|
"resetAreYouSure": string;
|
||||||
"areYouSure": string;
|
"areYouSure": string;
|
||||||
"saved": string;
|
"saved": string;
|
||||||
|
@ -285,8 +292,8 @@ export interface Locale {
|
||||||
"messageRead": string;
|
"messageRead": string;
|
||||||
"noMoreHistory": string;
|
"noMoreHistory": string;
|
||||||
"startMessaging": string;
|
"startMessaging": string;
|
||||||
"nUsersRead": string;
|
"nUsersRead": ParameterizedString<"n">;
|
||||||
"agreeTo": string;
|
"agreeTo": ParameterizedString<"0">;
|
||||||
"agree": string;
|
"agree": string;
|
||||||
"agreeBelow": string;
|
"agreeBelow": string;
|
||||||
"basicNotesBeforeCreateAccount": string;
|
"basicNotesBeforeCreateAccount": string;
|
||||||
|
@ -298,7 +305,7 @@ export interface Locale {
|
||||||
"images": string;
|
"images": string;
|
||||||
"image": string;
|
"image": string;
|
||||||
"birthday": string;
|
"birthday": string;
|
||||||
"yearsOld": string;
|
"yearsOld": ParameterizedString<"age">;
|
||||||
"registeredDate": string;
|
"registeredDate": string;
|
||||||
"location": string;
|
"location": string;
|
||||||
"theme": string;
|
"theme": string;
|
||||||
|
@ -353,9 +360,9 @@ export interface Locale {
|
||||||
"thisYear": string;
|
"thisYear": string;
|
||||||
"thisMonth": string;
|
"thisMonth": string;
|
||||||
"today": string;
|
"today": string;
|
||||||
"dayX": string;
|
"dayX": ParameterizedString<"day">;
|
||||||
"monthX": string;
|
"monthX": ParameterizedString<"month">;
|
||||||
"yearX": string;
|
"yearX": ParameterizedString<"year">;
|
||||||
"pages": string;
|
"pages": string;
|
||||||
"integration": string;
|
"integration": string;
|
||||||
"connectService": string;
|
"connectService": string;
|
||||||
|
@ -420,7 +427,7 @@ export interface Locale {
|
||||||
"recentlyUpdatedUsers": string;
|
"recentlyUpdatedUsers": string;
|
||||||
"recentlyRegisteredUsers": string;
|
"recentlyRegisteredUsers": string;
|
||||||
"recentlyDiscoveredUsers": string;
|
"recentlyDiscoveredUsers": string;
|
||||||
"exploreUsersCount": string;
|
"exploreUsersCount": ParameterizedString<"count">;
|
||||||
"exploreFediverse": string;
|
"exploreFediverse": string;
|
||||||
"popularTags": string;
|
"popularTags": string;
|
||||||
"userList": string;
|
"userList": string;
|
||||||
|
@ -437,16 +444,16 @@ export interface Locale {
|
||||||
"moderationNote": string;
|
"moderationNote": string;
|
||||||
"addModerationNote": string;
|
"addModerationNote": string;
|
||||||
"moderationLogs": string;
|
"moderationLogs": string;
|
||||||
"nUsersMentioned": string;
|
"nUsersMentioned": ParameterizedString<"n">;
|
||||||
"securityKeyAndPasskey": string;
|
"securityKeyAndPasskey": string;
|
||||||
"securityKey": string;
|
"securityKey": string;
|
||||||
"lastUsed": string;
|
"lastUsed": string;
|
||||||
"lastUsedAt": string;
|
"lastUsedAt": ParameterizedString<"t">;
|
||||||
"unregister": string;
|
"unregister": string;
|
||||||
"passwordLessLogin": string;
|
"passwordLessLogin": string;
|
||||||
"passwordLessLoginDescription": string;
|
"passwordLessLoginDescription": string;
|
||||||
"resetPassword": string;
|
"resetPassword": string;
|
||||||
"newPasswordIs": string;
|
"newPasswordIs": ParameterizedString<"password">;
|
||||||
"reduceUiAnimation": string;
|
"reduceUiAnimation": string;
|
||||||
"share": string;
|
"share": string;
|
||||||
"notFound": string;
|
"notFound": string;
|
||||||
|
@ -466,7 +473,7 @@ export interface Locale {
|
||||||
"enable": string;
|
"enable": string;
|
||||||
"next": string;
|
"next": string;
|
||||||
"retype": string;
|
"retype": string;
|
||||||
"noteOf": string;
|
"noteOf": ParameterizedString<"user">;
|
||||||
"quoteAttached": string;
|
"quoteAttached": string;
|
||||||
"quoteQuestion": string;
|
"quoteQuestion": string;
|
||||||
"noMessagesYet": string;
|
"noMessagesYet": string;
|
||||||
|
@ -486,12 +493,12 @@ export interface Locale {
|
||||||
"strongPassword": string;
|
"strongPassword": string;
|
||||||
"passwordMatched": string;
|
"passwordMatched": string;
|
||||||
"passwordNotMatched": string;
|
"passwordNotMatched": string;
|
||||||
"signinWith": string;
|
"signinWith": ParameterizedString<"x">;
|
||||||
"signinFailed": string;
|
"signinFailed": string;
|
||||||
"or": string;
|
"or": string;
|
||||||
"language": string;
|
"language": string;
|
||||||
"uiLanguage": string;
|
"uiLanguage": string;
|
||||||
"aboutX": string;
|
"aboutX": ParameterizedString<"x">;
|
||||||
"emojiStyle": string;
|
"emojiStyle": string;
|
||||||
"native": string;
|
"native": string;
|
||||||
"disableDrawer": string;
|
"disableDrawer": string;
|
||||||
|
@ -509,7 +516,7 @@ export interface Locale {
|
||||||
"regenerate": string;
|
"regenerate": string;
|
||||||
"fontSize": string;
|
"fontSize": string;
|
||||||
"mediaListWithOneImageAppearance": string;
|
"mediaListWithOneImageAppearance": string;
|
||||||
"limitTo": string;
|
"limitTo": ParameterizedString<"x">;
|
||||||
"noFollowRequests": string;
|
"noFollowRequests": string;
|
||||||
"openImageInNewTab": string;
|
"openImageInNewTab": string;
|
||||||
"dashboard": string;
|
"dashboard": string;
|
||||||
|
@ -587,7 +594,7 @@ export interface Locale {
|
||||||
"deleteAllFiles": string;
|
"deleteAllFiles": string;
|
||||||
"deleteAllFilesConfirm": string;
|
"deleteAllFilesConfirm": string;
|
||||||
"removeAllFollowing": string;
|
"removeAllFollowing": string;
|
||||||
"removeAllFollowingDescription": string;
|
"removeAllFollowingDescription": ParameterizedString<"host">;
|
||||||
"userSuspended": string;
|
"userSuspended": string;
|
||||||
"userSilenced": string;
|
"userSilenced": string;
|
||||||
"yourAccountSuspendedTitle": string;
|
"yourAccountSuspendedTitle": string;
|
||||||
|
@ -658,9 +665,9 @@ export interface Locale {
|
||||||
"wordMute": string;
|
"wordMute": string;
|
||||||
"hardWordMute": string;
|
"hardWordMute": string;
|
||||||
"regexpError": string;
|
"regexpError": string;
|
||||||
"regexpErrorDescription": string;
|
"regexpErrorDescription": ParameterizedString<"tab" | "line">;
|
||||||
"instanceMute": string;
|
"instanceMute": string;
|
||||||
"userSaysSomething": string;
|
"userSaysSomething": ParameterizedString<"name">;
|
||||||
"makeActive": string;
|
"makeActive": string;
|
||||||
"display": string;
|
"display": string;
|
||||||
"copy": string;
|
"copy": string;
|
||||||
|
@ -686,7 +693,7 @@ export interface Locale {
|
||||||
"abuseReports": string;
|
"abuseReports": string;
|
||||||
"reportAbuse": string;
|
"reportAbuse": string;
|
||||||
"reportAbuseRenote": string;
|
"reportAbuseRenote": string;
|
||||||
"reportAbuseOf": string;
|
"reportAbuseOf": ParameterizedString<"name">;
|
||||||
"fillAbuseReportDescription": string;
|
"fillAbuseReportDescription": string;
|
||||||
"abuseReported": string;
|
"abuseReported": string;
|
||||||
"reporter": string;
|
"reporter": string;
|
||||||
|
@ -701,7 +708,7 @@ export interface Locale {
|
||||||
"defaultNavigationBehaviour": string;
|
"defaultNavigationBehaviour": string;
|
||||||
"editTheseSettingsMayBreakAccount": string;
|
"editTheseSettingsMayBreakAccount": string;
|
||||||
"instanceTicker": string;
|
"instanceTicker": string;
|
||||||
"waitingFor": string;
|
"waitingFor": ParameterizedString<"x">;
|
||||||
"random": string;
|
"random": string;
|
||||||
"system": string;
|
"system": string;
|
||||||
"switchUi": string;
|
"switchUi": string;
|
||||||
|
@ -711,10 +718,10 @@ export interface Locale {
|
||||||
"optional": string;
|
"optional": string;
|
||||||
"createNewClip": string;
|
"createNewClip": string;
|
||||||
"unclip": string;
|
"unclip": string;
|
||||||
"confirmToUnclipAlreadyClippedNote": string;
|
"confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">;
|
||||||
"public": string;
|
"public": string;
|
||||||
"private": string;
|
"private": string;
|
||||||
"i18nInfo": string;
|
"i18nInfo": ParameterizedString<"link">;
|
||||||
"manageAccessTokens": string;
|
"manageAccessTokens": string;
|
||||||
"accountInfo": string;
|
"accountInfo": string;
|
||||||
"notesCount": string;
|
"notesCount": string;
|
||||||
|
@ -764,9 +771,9 @@ export interface Locale {
|
||||||
"needReloadToApply": string;
|
"needReloadToApply": string;
|
||||||
"showTitlebar": string;
|
"showTitlebar": string;
|
||||||
"clearCache": string;
|
"clearCache": string;
|
||||||
"onlineUsersCount": string;
|
"onlineUsersCount": ParameterizedString<"n">;
|
||||||
"nUsers": string;
|
"nUsers": ParameterizedString<"n">;
|
||||||
"nNotes": string;
|
"nNotes": ParameterizedString<"n">;
|
||||||
"sendErrorReports": string;
|
"sendErrorReports": string;
|
||||||
"sendErrorReportsDescription": string;
|
"sendErrorReportsDescription": string;
|
||||||
"myTheme": string;
|
"myTheme": string;
|
||||||
|
@ -798,7 +805,7 @@ export interface Locale {
|
||||||
"publish": string;
|
"publish": string;
|
||||||
"inChannelSearch": string;
|
"inChannelSearch": string;
|
||||||
"useReactionPickerForContextMenu": string;
|
"useReactionPickerForContextMenu": string;
|
||||||
"typingUsers": string;
|
"typingUsers": ParameterizedString<"users">;
|
||||||
"jumpToSpecifiedDate": string;
|
"jumpToSpecifiedDate": string;
|
||||||
"showingPastTimeline": string;
|
"showingPastTimeline": string;
|
||||||
"clear": string;
|
"clear": string;
|
||||||
|
@ -865,7 +872,7 @@ export interface Locale {
|
||||||
"misskeyUpdated": string;
|
"misskeyUpdated": string;
|
||||||
"whatIsNew": string;
|
"whatIsNew": string;
|
||||||
"translate": string;
|
"translate": string;
|
||||||
"translatedFrom": string;
|
"translatedFrom": ParameterizedString<"x">;
|
||||||
"accountDeletionInProgress": string;
|
"accountDeletionInProgress": string;
|
||||||
"usernameInfo": string;
|
"usernameInfo": string;
|
||||||
"aiChanMode": string;
|
"aiChanMode": string;
|
||||||
|
@ -896,11 +903,11 @@ export interface Locale {
|
||||||
"continueThread": string;
|
"continueThread": string;
|
||||||
"deleteAccountConfirm": string;
|
"deleteAccountConfirm": string;
|
||||||
"incorrectPassword": string;
|
"incorrectPassword": string;
|
||||||
"voteConfirm": string;
|
"voteConfirm": ParameterizedString<"choice">;
|
||||||
"hide": string;
|
"hide": string;
|
||||||
"useDrawerReactionPickerForMobile": string;
|
"useDrawerReactionPickerForMobile": string;
|
||||||
"welcomeBackWithName": string;
|
"welcomeBackWithName": ParameterizedString<"name">;
|
||||||
"clickToFinishEmailVerification": string;
|
"clickToFinishEmailVerification": ParameterizedString<"ok">;
|
||||||
"overridedDeviceKind": string;
|
"overridedDeviceKind": string;
|
||||||
"smartphone": string;
|
"smartphone": string;
|
||||||
"tablet": string;
|
"tablet": string;
|
||||||
|
@ -928,8 +935,8 @@ export interface Locale {
|
||||||
"cropYes": string;
|
"cropYes": string;
|
||||||
"cropNo": string;
|
"cropNo": string;
|
||||||
"file": string;
|
"file": string;
|
||||||
"recentNHours": string;
|
"recentNHours": ParameterizedString<"n">;
|
||||||
"recentNDays": string;
|
"recentNDays": ParameterizedString<"n">;
|
||||||
"noEmailServerWarning": string;
|
"noEmailServerWarning": string;
|
||||||
"thereIsUnresolvedAbuseReportWarning": string;
|
"thereIsUnresolvedAbuseReportWarning": string;
|
||||||
"recommended": string;
|
"recommended": string;
|
||||||
|
@ -938,7 +945,7 @@ export interface Locale {
|
||||||
"driveCapOverrideCaption": string;
|
"driveCapOverrideCaption": string;
|
||||||
"requireAdminForView": string;
|
"requireAdminForView": string;
|
||||||
"isSystemAccount": string;
|
"isSystemAccount": string;
|
||||||
"typeToConfirm": string;
|
"typeToConfirm": ParameterizedString<"x">;
|
||||||
"deleteAccount": string;
|
"deleteAccount": string;
|
||||||
"document": string;
|
"document": string;
|
||||||
"numberOfPageCache": string;
|
"numberOfPageCache": string;
|
||||||
|
@ -992,7 +999,7 @@ export interface Locale {
|
||||||
"neverShow": string;
|
"neverShow": string;
|
||||||
"remindMeLater": string;
|
"remindMeLater": string;
|
||||||
"didYouLikeMisskey": string;
|
"didYouLikeMisskey": string;
|
||||||
"pleaseDonate": string;
|
"pleaseDonate": ParameterizedString<"host">;
|
||||||
"roles": string;
|
"roles": string;
|
||||||
"role": string;
|
"role": string;
|
||||||
"noRole": string;
|
"noRole": string;
|
||||||
|
@ -1090,7 +1097,7 @@ export interface Locale {
|
||||||
"preservedUsernamesDescription": string;
|
"preservedUsernamesDescription": string;
|
||||||
"createNoteFromTheFile": string;
|
"createNoteFromTheFile": string;
|
||||||
"archive": string;
|
"archive": string;
|
||||||
"channelArchiveConfirmTitle": string;
|
"channelArchiveConfirmTitle": ParameterizedString<"name">;
|
||||||
"channelArchiveConfirmDescription": string;
|
"channelArchiveConfirmDescription": string;
|
||||||
"thisChannelArchived": string;
|
"thisChannelArchived": string;
|
||||||
"displayOfNote": string;
|
"displayOfNote": string;
|
||||||
|
@ -1120,8 +1127,8 @@ export interface Locale {
|
||||||
"createCount": string;
|
"createCount": string;
|
||||||
"inviteCodeCreated": string;
|
"inviteCodeCreated": string;
|
||||||
"inviteLimitExceeded": string;
|
"inviteLimitExceeded": string;
|
||||||
"createLimitRemaining": string;
|
"createLimitRemaining": ParameterizedString<"limit">;
|
||||||
"inviteLimitResetCycle": string;
|
"inviteLimitResetCycle": ParameterizedString<"time" | "limit">;
|
||||||
"expirationDate": string;
|
"expirationDate": string;
|
||||||
"noExpirationDate": string;
|
"noExpirationDate": string;
|
||||||
"inviteCodeUsedAt": string;
|
"inviteCodeUsedAt": string;
|
||||||
|
@ -1134,7 +1141,7 @@ export interface Locale {
|
||||||
"expired": string;
|
"expired": string;
|
||||||
"doYouAgree": string;
|
"doYouAgree": string;
|
||||||
"beSureToReadThisAsItIsImportant": string;
|
"beSureToReadThisAsItIsImportant": string;
|
||||||
"iHaveReadXCarefullyAndAgree": string;
|
"iHaveReadXCarefullyAndAgree": ParameterizedString<"x">;
|
||||||
"dialog": string;
|
"dialog": string;
|
||||||
"icon": string;
|
"icon": string;
|
||||||
"forYou": string;
|
"forYou": string;
|
||||||
|
@ -1189,7 +1196,7 @@ export interface Locale {
|
||||||
"doReaction": string;
|
"doReaction": string;
|
||||||
"code": string;
|
"code": string;
|
||||||
"reloadRequiredToApplySettings": string;
|
"reloadRequiredToApplySettings": string;
|
||||||
"remainingN": string;
|
"remainingN": ParameterizedString<"n">;
|
||||||
"overwriteContentConfirm": string;
|
"overwriteContentConfirm": string;
|
||||||
"seasonalScreenEffect": string;
|
"seasonalScreenEffect": string;
|
||||||
"decorate": string;
|
"decorate": string;
|
||||||
|
@ -1202,8 +1209,9 @@ export interface Locale {
|
||||||
"replay": string;
|
"replay": string;
|
||||||
"replaying": string;
|
"replaying": string;
|
||||||
"ranking": string;
|
"ranking": string;
|
||||||
"lastNDays": string;
|
"lastNDays": ParameterizedString<"n">;
|
||||||
"backToTitle": string;
|
"backToTitle": string;
|
||||||
|
"enableHorizontalSwipe": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
"howToPlay": string;
|
"howToPlay": string;
|
||||||
"_howToPlay": {
|
"_howToPlay": {
|
||||||
|
@ -1220,7 +1228,7 @@ export interface Locale {
|
||||||
"end": string;
|
"end": string;
|
||||||
"tooManyActiveAnnouncementDescription": string;
|
"tooManyActiveAnnouncementDescription": string;
|
||||||
"readConfirmTitle": string;
|
"readConfirmTitle": string;
|
||||||
"readConfirmText": string;
|
"readConfirmText": ParameterizedString<"title">;
|
||||||
"shouldNotBeUsedToPresentPermanentInfo": string;
|
"shouldNotBeUsedToPresentPermanentInfo": string;
|
||||||
"dialogAnnouncementUxWarn": string;
|
"dialogAnnouncementUxWarn": string;
|
||||||
"silence": string;
|
"silence": string;
|
||||||
|
@ -1235,10 +1243,10 @@ export interface Locale {
|
||||||
"theseSettingsCanEditLater": string;
|
"theseSettingsCanEditLater": string;
|
||||||
"youCanEditMoreSettingsInSettingsPageLater": string;
|
"youCanEditMoreSettingsInSettingsPageLater": string;
|
||||||
"followUsers": string;
|
"followUsers": string;
|
||||||
"pushNotificationDescription": string;
|
"pushNotificationDescription": ParameterizedString<"name">;
|
||||||
"initialAccountSettingCompleted": string;
|
"initialAccountSettingCompleted": string;
|
||||||
"haveFun": string;
|
"haveFun": ParameterizedString<"name">;
|
||||||
"youCanContinueTutorial": string;
|
"youCanContinueTutorial": ParameterizedString<"name">;
|
||||||
"startTutorial": string;
|
"startTutorial": string;
|
||||||
"skipAreYouSure": string;
|
"skipAreYouSure": string;
|
||||||
"laterAreYouSure": string;
|
"laterAreYouSure": string;
|
||||||
|
@ -1276,7 +1284,7 @@ export interface Locale {
|
||||||
"social": string;
|
"social": string;
|
||||||
"global": string;
|
"global": string;
|
||||||
"description2": string;
|
"description2": string;
|
||||||
"description3": string;
|
"description3": ParameterizedString<"link">;
|
||||||
};
|
};
|
||||||
"_postNote": {
|
"_postNote": {
|
||||||
"title": string;
|
"title": string;
|
||||||
|
@ -1314,7 +1322,7 @@ export interface Locale {
|
||||||
};
|
};
|
||||||
"_done": {
|
"_done": {
|
||||||
"title": string;
|
"title": string;
|
||||||
"description": string;
|
"description": ParameterizedString<"link">;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_timelineDescription": {
|
"_timelineDescription": {
|
||||||
|
@ -1328,10 +1336,10 @@ export interface Locale {
|
||||||
};
|
};
|
||||||
"_serverSettings": {
|
"_serverSettings": {
|
||||||
"iconUrl": string;
|
"iconUrl": string;
|
||||||
"appIconDescription": string;
|
"appIconDescription": ParameterizedString<"host">;
|
||||||
"appIconUsageExample": string;
|
"appIconUsageExample": string;
|
||||||
"appIconStyleRecommendation": string;
|
"appIconStyleRecommendation": string;
|
||||||
"appIconResolutionMustBe": string;
|
"appIconResolutionMustBe": ParameterizedString<"resolution">;
|
||||||
"manifestJsonOverride": string;
|
"manifestJsonOverride": string;
|
||||||
"shortName": string;
|
"shortName": string;
|
||||||
"shortNameDescription": string;
|
"shortNameDescription": string;
|
||||||
|
@ -1342,7 +1350,7 @@ export interface Locale {
|
||||||
"_accountMigration": {
|
"_accountMigration": {
|
||||||
"moveFrom": string;
|
"moveFrom": string;
|
||||||
"moveFromSub": string;
|
"moveFromSub": string;
|
||||||
"moveFromLabel": string;
|
"moveFromLabel": ParameterizedString<"n">;
|
||||||
"moveFromDescription": string;
|
"moveFromDescription": string;
|
||||||
"moveTo": string;
|
"moveTo": string;
|
||||||
"moveToLabel": string;
|
"moveToLabel": string;
|
||||||
|
@ -1350,7 +1358,7 @@ export interface Locale {
|
||||||
"moveAccountDescription": string;
|
"moveAccountDescription": string;
|
||||||
"moveAccountHowTo": string;
|
"moveAccountHowTo": string;
|
||||||
"startMigration": string;
|
"startMigration": string;
|
||||||
"migrationConfirm": string;
|
"migrationConfirm": ParameterizedString<"account">;
|
||||||
"movedAndCannotBeUndone": string;
|
"movedAndCannotBeUndone": string;
|
||||||
"postMigrationNote": string;
|
"postMigrationNote": string;
|
||||||
"movedTo": string;
|
"movedTo": string;
|
||||||
|
@ -1792,7 +1800,7 @@ export interface Locale {
|
||||||
"_signup": {
|
"_signup": {
|
||||||
"almostThere": string;
|
"almostThere": string;
|
||||||
"emailAddressInfo": string;
|
"emailAddressInfo": string;
|
||||||
"emailSent": string;
|
"emailSent": ParameterizedString<"email">;
|
||||||
};
|
};
|
||||||
"_accountDelete": {
|
"_accountDelete": {
|
||||||
"accountDelete": string;
|
"accountDelete": string;
|
||||||
|
@ -1845,14 +1853,14 @@ export interface Locale {
|
||||||
"save": string;
|
"save": string;
|
||||||
"inputName": string;
|
"inputName": string;
|
||||||
"cannotSave": string;
|
"cannotSave": string;
|
||||||
"nameAlreadyExists": string;
|
"nameAlreadyExists": ParameterizedString<"name">;
|
||||||
"applyConfirm": string;
|
"applyConfirm": ParameterizedString<"name">;
|
||||||
"saveConfirm": string;
|
"saveConfirm": ParameterizedString<"name">;
|
||||||
"deleteConfirm": string;
|
"deleteConfirm": ParameterizedString<"name">;
|
||||||
"renameConfirm": string;
|
"renameConfirm": ParameterizedString<"old" | "new">;
|
||||||
"noBackups": string;
|
"noBackups": string;
|
||||||
"createdAt": string;
|
"createdAt": ParameterizedString<"date" | "time">;
|
||||||
"updatedAt": string;
|
"updatedAt": ParameterizedString<"date" | "time">;
|
||||||
"cannotLoad": string;
|
"cannotLoad": string;
|
||||||
"invalidFile": string;
|
"invalidFile": string;
|
||||||
};
|
};
|
||||||
|
@ -1897,8 +1905,8 @@ export interface Locale {
|
||||||
"featured": string;
|
"featured": string;
|
||||||
"owned": string;
|
"owned": string;
|
||||||
"following": string;
|
"following": string;
|
||||||
"usersCount": string;
|
"usersCount": ParameterizedString<"n">;
|
||||||
"notesCount": string;
|
"notesCount": ParameterizedString<"n">;
|
||||||
"nameAndDescription": string;
|
"nameAndDescription": string;
|
||||||
"nameOnly": string;
|
"nameOnly": string;
|
||||||
"allowRenoteToExternal": string;
|
"allowRenoteToExternal": string;
|
||||||
|
@ -1926,7 +1934,7 @@ export interface Locale {
|
||||||
"manage": string;
|
"manage": string;
|
||||||
"code": string;
|
"code": string;
|
||||||
"description": string;
|
"description": string;
|
||||||
"installed": string;
|
"installed": ParameterizedString<"name">;
|
||||||
"installedThemes": string;
|
"installedThemes": string;
|
||||||
"builtinThemes": string;
|
"builtinThemes": string;
|
||||||
"alreadyInstalled": string;
|
"alreadyInstalled": string;
|
||||||
|
@ -1949,7 +1957,7 @@ export interface Locale {
|
||||||
"lighten": string;
|
"lighten": string;
|
||||||
"inputConstantName": string;
|
"inputConstantName": string;
|
||||||
"importInfo": string;
|
"importInfo": string;
|
||||||
"deleteConstantConfirm": string;
|
"deleteConstantConfirm": ParameterizedString<"const">;
|
||||||
"keys": {
|
"keys": {
|
||||||
"accent": string;
|
"accent": string;
|
||||||
"bg": string;
|
"bg": string;
|
||||||
|
@ -2012,23 +2020,23 @@ export interface Locale {
|
||||||
"_ago": {
|
"_ago": {
|
||||||
"future": string;
|
"future": string;
|
||||||
"justNow": string;
|
"justNow": string;
|
||||||
"secondsAgo": string;
|
"secondsAgo": ParameterizedString<"n">;
|
||||||
"minutesAgo": string;
|
"minutesAgo": ParameterizedString<"n">;
|
||||||
"hoursAgo": string;
|
"hoursAgo": ParameterizedString<"n">;
|
||||||
"daysAgo": string;
|
"daysAgo": ParameterizedString<"n">;
|
||||||
"weeksAgo": string;
|
"weeksAgo": ParameterizedString<"n">;
|
||||||
"monthsAgo": string;
|
"monthsAgo": ParameterizedString<"n">;
|
||||||
"yearsAgo": string;
|
"yearsAgo": ParameterizedString<"n">;
|
||||||
"invalid": string;
|
"invalid": string;
|
||||||
};
|
};
|
||||||
"_timeIn": {
|
"_timeIn": {
|
||||||
"seconds": string;
|
"seconds": ParameterizedString<"n">;
|
||||||
"minutes": string;
|
"minutes": ParameterizedString<"n">;
|
||||||
"hours": string;
|
"hours": ParameterizedString<"n">;
|
||||||
"days": string;
|
"days": ParameterizedString<"n">;
|
||||||
"weeks": string;
|
"weeks": ParameterizedString<"n">;
|
||||||
"months": string;
|
"months": ParameterizedString<"n">;
|
||||||
"years": string;
|
"years": ParameterizedString<"n">;
|
||||||
};
|
};
|
||||||
"_time": {
|
"_time": {
|
||||||
"second": string;
|
"second": string;
|
||||||
|
@ -2039,7 +2047,7 @@ export interface Locale {
|
||||||
"_2fa": {
|
"_2fa": {
|
||||||
"alreadyRegistered": string;
|
"alreadyRegistered": string;
|
||||||
"registerTOTP": string;
|
"registerTOTP": string;
|
||||||
"step1": string;
|
"step1": ParameterizedString<"a" | "b">;
|
||||||
"step2": string;
|
"step2": string;
|
||||||
"step2Click": string;
|
"step2Click": string;
|
||||||
"step2Uri": string;
|
"step2Uri": string;
|
||||||
|
@ -2054,7 +2062,7 @@ export interface Locale {
|
||||||
"securityKeyName": string;
|
"securityKeyName": string;
|
||||||
"tapSecurityKey": string;
|
"tapSecurityKey": string;
|
||||||
"removeKey": string;
|
"removeKey": string;
|
||||||
"removeKeyConfirm": string;
|
"removeKeyConfirm": ParameterizedString<"name">;
|
||||||
"whyTOTPOnlyRenew": string;
|
"whyTOTPOnlyRenew": string;
|
||||||
"renewTOTP": string;
|
"renewTOTP": string;
|
||||||
"renewTOTPConfirm": string;
|
"renewTOTPConfirm": string;
|
||||||
|
@ -2155,9 +2163,9 @@ export interface Locale {
|
||||||
};
|
};
|
||||||
"_auth": {
|
"_auth": {
|
||||||
"shareAccessTitle": string;
|
"shareAccessTitle": string;
|
||||||
"shareAccess": string;
|
"shareAccess": ParameterizedString<"name">;
|
||||||
"shareAccessAsk": string;
|
"shareAccessAsk": string;
|
||||||
"permission": string;
|
"permission": ParameterizedString<"name">;
|
||||||
"permissionAsk": string;
|
"permissionAsk": string;
|
||||||
"pleaseGoBack": string;
|
"pleaseGoBack": string;
|
||||||
"callback": string;
|
"callback": string;
|
||||||
|
@ -2216,12 +2224,12 @@ export interface Locale {
|
||||||
"_cw": {
|
"_cw": {
|
||||||
"hide": string;
|
"hide": string;
|
||||||
"show": string;
|
"show": string;
|
||||||
"chars": string;
|
"chars": ParameterizedString<"count">;
|
||||||
"files": string;
|
"files": ParameterizedString<"count">;
|
||||||
};
|
};
|
||||||
"_poll": {
|
"_poll": {
|
||||||
"noOnlyOneChoice": string;
|
"noOnlyOneChoice": string;
|
||||||
"choiceN": string;
|
"choiceN": ParameterizedString<"n">;
|
||||||
"noMore": string;
|
"noMore": string;
|
||||||
"canMultipleVote": string;
|
"canMultipleVote": string;
|
||||||
"expiration": string;
|
"expiration": string;
|
||||||
|
@ -2231,16 +2239,16 @@ export interface Locale {
|
||||||
"deadlineDate": string;
|
"deadlineDate": string;
|
||||||
"deadlineTime": string;
|
"deadlineTime": string;
|
||||||
"duration": string;
|
"duration": string;
|
||||||
"votesCount": string;
|
"votesCount": ParameterizedString<"n">;
|
||||||
"totalVotes": string;
|
"totalVotes": ParameterizedString<"n">;
|
||||||
"vote": string;
|
"vote": string;
|
||||||
"showResult": string;
|
"showResult": string;
|
||||||
"voted": string;
|
"voted": string;
|
||||||
"closed": string;
|
"closed": string;
|
||||||
"remainingDays": string;
|
"remainingDays": ParameterizedString<"d" | "h">;
|
||||||
"remainingHours": string;
|
"remainingHours": ParameterizedString<"h" | "m">;
|
||||||
"remainingMinutes": string;
|
"remainingMinutes": ParameterizedString<"m" | "s">;
|
||||||
"remainingSeconds": string;
|
"remainingSeconds": ParameterizedString<"s">;
|
||||||
};
|
};
|
||||||
"_visibility": {
|
"_visibility": {
|
||||||
"public": string;
|
"public": string;
|
||||||
|
@ -2280,7 +2288,7 @@ export interface Locale {
|
||||||
"changeAvatar": string;
|
"changeAvatar": string;
|
||||||
"changeBanner": string;
|
"changeBanner": string;
|
||||||
"verifiedLinkDescription": string;
|
"verifiedLinkDescription": string;
|
||||||
"avatarDecorationMax": string;
|
"avatarDecorationMax": ParameterizedString<"max">;
|
||||||
};
|
};
|
||||||
"_exportOrImport": {
|
"_exportOrImport": {
|
||||||
"allNotes": string;
|
"allNotes": string;
|
||||||
|
@ -2403,16 +2411,16 @@ export interface Locale {
|
||||||
};
|
};
|
||||||
"_notification": {
|
"_notification": {
|
||||||
"fileUploaded": string;
|
"fileUploaded": string;
|
||||||
"youGotMention": string;
|
"youGotMention": ParameterizedString<"name">;
|
||||||
"youGotReply": string;
|
"youGotReply": ParameterizedString<"name">;
|
||||||
"youGotQuote": string;
|
"youGotQuote": ParameterizedString<"name">;
|
||||||
"youRenoted": string;
|
"youRenoted": ParameterizedString<"name">;
|
||||||
"youWereFollowed": string;
|
"youWereFollowed": string;
|
||||||
"youReceivedFollowRequest": string;
|
"youReceivedFollowRequest": string;
|
||||||
"yourFollowRequestAccepted": string;
|
"yourFollowRequestAccepted": string;
|
||||||
"pollEnded": string;
|
"pollEnded": string;
|
||||||
"newNote": string;
|
"newNote": string;
|
||||||
"unreadAntennaNote": string;
|
"unreadAntennaNote": ParameterizedString<"name">;
|
||||||
"roleAssigned": string;
|
"roleAssigned": string;
|
||||||
"emptyPushNotificationMessage": string;
|
"emptyPushNotificationMessage": string;
|
||||||
"achievementEarned": string;
|
"achievementEarned": string;
|
||||||
|
@ -2420,9 +2428,9 @@ export interface Locale {
|
||||||
"checkNotificationBehavior": string;
|
"checkNotificationBehavior": string;
|
||||||
"sendTestNotification": string;
|
"sendTestNotification": string;
|
||||||
"notificationWillBeDisplayedLikeThis": string;
|
"notificationWillBeDisplayedLikeThis": string;
|
||||||
"reactedBySomeUsers": string;
|
"reactedBySomeUsers": ParameterizedString<"n">;
|
||||||
"renotedBySomeUsers": string;
|
"renotedBySomeUsers": ParameterizedString<"n">;
|
||||||
"followedBySomeUsers": string;
|
"followedBySomeUsers": ParameterizedString<"n">;
|
||||||
"_types": {
|
"_types": {
|
||||||
"all": string;
|
"all": string;
|
||||||
"note": string;
|
"note": string;
|
||||||
|
@ -2479,8 +2487,8 @@ export interface Locale {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_dialog": {
|
"_dialog": {
|
||||||
"charactersExceeded": string;
|
"charactersExceeded": ParameterizedString<"current" | "max">;
|
||||||
"charactersBelow": string;
|
"charactersBelow": ParameterizedString<"current" | "min">;
|
||||||
};
|
};
|
||||||
"_disabledTimeline": {
|
"_disabledTimeline": {
|
||||||
"title": string;
|
"title": string;
|
||||||
|
|
|
@ -1201,6 +1201,7 @@ replaying: "リプレイ中"
|
||||||
ranking: "ランキング"
|
ranking: "ランキング"
|
||||||
lastNDays: "直近{n}日"
|
lastNDays: "直近{n}日"
|
||||||
backToTitle: "タイトルへ"
|
backToTitle: "タイトルへ"
|
||||||
|
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
|
|
@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
|
<div :class="$style.codeBlockRoot">
|
||||||
<div v-else :class="$style.codeBlockRoot">
|
|
||||||
<button :class="$style.codeBlockCopyButton" class="_button" @click="copy">
|
<button :class="$style.codeBlockCopyButton" class="_button" @click="copy">
|
||||||
<i class="ti ti-copy"></i>
|
<i class="ti ti-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -36,7 +35,6 @@ import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
code: string;
|
code: string;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
inline?: boolean;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const show = ref(!defaultStore.state.dataSaver.code);
|
const show = ref(!defaultStore.state.dataSaver.code);
|
||||||
|
@ -66,16 +64,6 @@ function copy() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeInlineRoot {
|
|
||||||
display: inline-block;
|
|
||||||
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
color: #D4D4D4;
|
|
||||||
background: #1E1E1E;
|
|
||||||
padding: .1em;
|
|
||||||
border-radius: .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codeBlockFallbackRoot {
|
.codeBlockFallbackRoot {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<code :class="$style.root">{{ code }}</code>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps<{
|
||||||
|
code: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.root {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: #D4D4D4;
|
||||||
|
background: #1E1E1E;
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,209 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="rootEl"
|
||||||
|
:class="[$style.transitionRoot, (defaultStore.state.animation && $style.enableAnimation)]"
|
||||||
|
@touchstart="touchStart"
|
||||||
|
@touchmove="touchMove"
|
||||||
|
@touchend="touchEnd"
|
||||||
|
>
|
||||||
|
<Transition
|
||||||
|
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
||||||
|
:enterActiveClass="$style.swipeAnimation_enterActive"
|
||||||
|
:leaveActiveClass="$style.swipeAnimation_leaveActive"
|
||||||
|
:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
|
||||||
|
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
|
||||||
|
:style="`--swipe: ${pullDistance}px;`"
|
||||||
|
>
|
||||||
|
<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
|
||||||
|
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
||||||
|
<slot></slot>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
|
||||||
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
|
const rootEl = shallowRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const tabModel = defineModel<string>('tab');
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tabs: Tab[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// ▼ しきい値 ▼ //
|
||||||
|
|
||||||
|
// スワイプと判定される最小の距離
|
||||||
|
const MIN_SWIPE_DISTANCE = 50;
|
||||||
|
|
||||||
|
// スワイプ時の動作を発火する最小の距離
|
||||||
|
const SWIPE_DISTANCE_THRESHOLD = 125;
|
||||||
|
|
||||||
|
// スワイプを中断するY方向の移動距離
|
||||||
|
const SWIPE_ABORT_Y_THRESHOLD = 75;
|
||||||
|
|
||||||
|
// スワイプできる最大の距離
|
||||||
|
const MAX_SWIPE_DISTANCE = 150;
|
||||||
|
|
||||||
|
// ▲ しきい値 ▲ //
|
||||||
|
|
||||||
|
let startScreenX: number | null = null;
|
||||||
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
|
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
||||||
|
|
||||||
|
const pullDistance = ref(0);
|
||||||
|
const isSwiping = ref(false);
|
||||||
|
const isSwipingForClass = ref(false);
|
||||||
|
let swipeAborted = false;
|
||||||
|
|
||||||
|
function touchStart(event: TouchEvent) {
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
|
startScreenX = event.touches[0].screenX;
|
||||||
|
startScreenY = event.touches[0].screenY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchMove(event: TouchEvent) {
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
|
if (startScreenX == null || startScreenY == null) return;
|
||||||
|
|
||||||
|
if (swipeAborted) return;
|
||||||
|
|
||||||
|
let distanceX = event.touches[0].screenX - startScreenX;
|
||||||
|
let distanceY = event.touches[0].screenY - startScreenY;
|
||||||
|
|
||||||
|
if (Math.abs(distanceY) > SWIPE_ABORT_Y_THRESHOLD) {
|
||||||
|
swipeAborted = true;
|
||||||
|
|
||||||
|
pullDistance.value = 0;
|
||||||
|
isSwiping.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
isSwipingForClass.value = false;
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(distanceX) < MIN_SWIPE_DISTANCE) return;
|
||||||
|
if (Math.abs(distanceX) > MAX_SWIPE_DISTANCE) return;
|
||||||
|
|
||||||
|
if (currentTabIndex.value === 0 || props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
|
distanceX = Math.min(distanceX, 0);
|
||||||
|
}
|
||||||
|
if (currentTabIndex.value === props.tabs.length - 1 || props.tabs[currentTabIndex.value + 1].onClick) {
|
||||||
|
distanceX = Math.max(distanceX, 0);
|
||||||
|
}
|
||||||
|
if (distanceX === 0) return;
|
||||||
|
|
||||||
|
isSwiping.value = true;
|
||||||
|
isSwipingForClass.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
// グリッチを控えるため、1.5px以上の差がないと更新しない
|
||||||
|
if (Math.abs(distanceX - pullDistance.value) < 1.5) return;
|
||||||
|
pullDistance.value = distanceX;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchEnd(event: TouchEvent) {
|
||||||
|
if (swipeAborted) {
|
||||||
|
swipeAborted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 0) return;
|
||||||
|
|
||||||
|
if (startScreenX == null) return;
|
||||||
|
|
||||||
|
if (!isSwiping.value) return;
|
||||||
|
|
||||||
|
const distance = event.changedTouches[0].screenX - startScreenX;
|
||||||
|
|
||||||
|
if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
|
||||||
|
if (distance > 0) {
|
||||||
|
if (props.tabs[currentTabIndex.value - 1] && !props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
|
tabModel.value = props.tabs[currentTabIndex.value - 1].key;
|
||||||
|
emit('swiped', props.tabs[currentTabIndex.value - 1].key, 'right');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.tabs[currentTabIndex.value + 1] && !props.tabs[currentTabIndex.value + 1].onClick) {
|
||||||
|
tabModel.value = props.tabs[currentTabIndex.value + 1].key;
|
||||||
|
emit('swiped', props.tabs[currentTabIndex.value + 1].key, 'left');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pullDistance.value = 0;
|
||||||
|
isSwiping.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
isSwipingForClass.value = false;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
|
||||||
|
|
||||||
|
watch(tabModel, (newTab, oldTab) => {
|
||||||
|
const newIndex = props.tabs.findIndex(tab => tab.key === newTab);
|
||||||
|
const oldIndex = props.tabs.findIndex(tab => tab.key === oldTab);
|
||||||
|
|
||||||
|
if (oldIndex >= 0 && newIndex && oldIndex < newIndex) {
|
||||||
|
transitionName.value = 'swipeAnimationLeft';
|
||||||
|
} else {
|
||||||
|
transitionName.value = 'swipeAnimationRight';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
transitionName.value = undefined;
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.transitionRoot.enableAnimation {
|
||||||
|
display: grid;
|
||||||
|
overflow: clip;
|
||||||
|
|
||||||
|
.transitionChildren {
|
||||||
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
|
transform: translateX(var(--swipe));
|
||||||
|
|
||||||
|
&.swipeAnimation_enterActive,
|
||||||
|
&.swipeAnimation_leaveActive {
|
||||||
|
transition: transform .3s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swipeAnimationRight_leaveTo,
|
||||||
|
&.swipeAnimationLeft_enterFrom {
|
||||||
|
transform: translateX(calc(100% + 24px));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swipeAnimationRight_enterFrom,
|
||||||
|
&.swipeAnimationLeft_leaveTo {
|
||||||
|
transform: translateX(calc(-100% - 24px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiping {
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -138,7 +138,7 @@ const rangePercent = computed({
|
||||||
audioEl.value.currentTime = to * durationMs.value / 1000;
|
audioEl.value.currentTime = to * durationMs.value / 1000;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const volume = ref(.5);
|
const volume = ref(.25);
|
||||||
const bufferedEnd = ref(0);
|
const bufferedEnd = ref(0);
|
||||||
const bufferedDataRatio = computed(() => {
|
const bufferedDataRatio = computed(() => {
|
||||||
if (!audioEl.value) return 0;
|
if (!audioEl.value) return 0;
|
||||||
|
@ -161,7 +161,7 @@ function togglePlayPause() {
|
||||||
|
|
||||||
function toggleMute() {
|
function toggleMute() {
|
||||||
if (volume.value === 0) {
|
if (volume.value === 0) {
|
||||||
volume.value = .5;
|
volume.value = .25;
|
||||||
} else {
|
} else {
|
||||||
volume.value = 0;
|
volume.value = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<!-- Media系専用のinput range -->
|
<!-- Media系専用のinput range -->
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.controlsSeekbar" :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--scrollbarHandle);'">
|
<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--scrollbarHandle);'">
|
||||||
<progress v-if="buffer !== undefined" :class="$style.buffer" :value="isNaN(buffer) ? 0 : buffer" min="0" max="1">{{ Math.round(buffer * 100) }}% buffered</progress>
|
<div :class="$style.controlsSeekbar">
|
||||||
<input v-model="model" :class="$style.seek" :style="`--value: ${modelValue * 100}%;`" type="range" min="0" max="1" step="any" @change="emit('dragEnded', modelValue)"/>
|
<progress v-if="buffer !== undefined" :class="$style.buffer" :value="isNaN(buffer) ? 0 : buffer" min="0" max="1">{{ Math.round(buffer * 100) }}% buffered</progress>
|
||||||
|
<input v-model="model" :class="$style.seek" :style="`--value: ${modelValue * 100}%;`" type="range" min="0" max="1" step="any" @change="emit('dragEnded', modelValue)"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ const rangePercent = computed({
|
||||||
videoEl.value.currentTime = to * durationMs.value / 1000;
|
videoEl.value.currentTime = to * durationMs.value / 1000;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const volume = ref(.5);
|
const volume = ref(.25);
|
||||||
const bufferedEnd = ref(0);
|
const bufferedEnd = ref(0);
|
||||||
const bufferedDataRatio = computed(() => {
|
const bufferedDataRatio = computed(() => {
|
||||||
if (!videoEl.value) return 0;
|
if (!videoEl.value) return 0;
|
||||||
|
@ -236,7 +236,7 @@ function toggleFullscreen() {
|
||||||
|
|
||||||
function toggleMute() {
|
function toggleMute() {
|
||||||
if (volume.value === 0) {
|
if (volume.value === 0) {
|
||||||
volume.value = .5;
|
volume.value = .25;
|
||||||
} else {
|
} else {
|
||||||
volume.value = 0;
|
volume.value = 0;
|
||||||
}
|
}
|
||||||
|
@ -535,6 +535,9 @@ onDeactivated(() => {
|
||||||
|
|
||||||
.seekbarRoot {
|
.seekbarRoot {
|
||||||
grid-area: seekbar;
|
grid-area: seekbar;
|
||||||
|
/* ▼シークバー操作をやりやすくするためにクリックイベントが伝播されないエリアを拡張する */
|
||||||
|
margin: -10px;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (min-width: 500px) {
|
@container (min-width: 500px) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import MkMention from '@/components/MkMention.vue';
|
||||||
import MkEmoji from '@/components/global/MkEmoji.vue';
|
import MkEmoji from '@/components/global/MkEmoji.vue';
|
||||||
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
|
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
|
||||||
import MkCode from '@/components/MkCode.vue';
|
import MkCode from '@/components/MkCode.vue';
|
||||||
|
import MkCodeInline from '@/components/MkCodeInline.vue';
|
||||||
import MkGoogle from '@/components/MkGoogle.vue';
|
import MkGoogle from '@/components/MkGoogle.vue';
|
||||||
import MkSparkle from '@/components/MkSparkle.vue';
|
import MkSparkle from '@/components/MkSparkle.vue';
|
||||||
import MkA from '@/components/global/MkA.vue';
|
import MkA from '@/components/global/MkA.vue';
|
||||||
|
@ -373,10 +374,9 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'inlineCode': {
|
case 'inlineCode': {
|
||||||
return [h(MkCode, {
|
return [h(MkCodeInline, {
|
||||||
key: Math.random(),
|
key: Math.random(),
|
||||||
code: token.props.code,
|
code: token.props.code,
|
||||||
inline: true,
|
|
||||||
})];
|
})];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
export function hms(ms: number, options: {
|
export function hms(ms: number, options?: {
|
||||||
textFormat?: 'colon' | 'locale';
|
textFormat?: 'colon' | 'locale';
|
||||||
enableSeconds?: boolean;
|
enableSeconds?: boolean;
|
||||||
enableMs?: boolean;
|
enableMs?: boolean;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { I18n } from '@/scripts/i18n.js';
|
||||||
|
|
||||||
export const i18n = markRaw(new I18n<Locale>(locale));
|
export const i18n = markRaw(new I18n<Locale>(locale));
|
||||||
|
|
||||||
export function updateI18n(newLocale) {
|
export function updateI18n(newLocale: Locale) {
|
||||||
i18n.ts = newLocale;
|
// @ts-expect-error -- private field
|
||||||
|
i18n.locale = newLocale;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,98 +6,100 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div class="_gaps_m">
|
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
<div class="_gaps_m">
|
||||||
<div style="overflow: clip;">
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
<div style="overflow: clip;">
|
||||||
<div :class="$style.bannerName">
|
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
||||||
<b>{{ instance.name ?? host }}</b>
|
<div :class="$style.bannerName">
|
||||||
|
<b>{{ instance.name ?? host }}</b>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkKeyValue>
|
<MkKeyValue>
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
<template #value><div v-html="instance.description"></div></template>
|
<template #value><div v-html="instance.description"></div></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<MkKeyValue :copy="version">
|
|
||||||
<template #key>Misskey</template>
|
|
||||||
<template #value>{{ version }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
|
|
||||||
</div>
|
|
||||||
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<FormSplit>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.administrator }}</template>
|
|
||||||
<template #value>{{ instance.maintainerName }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.contact }}</template>
|
|
||||||
<template #value>{{ instance.maintainerEmail }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</FormSplit>
|
|
||||||
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink>
|
|
||||||
<div class="_gaps_s">
|
|
||||||
<MkFolder v-if="instance.serverRules.length > 0">
|
|
||||||
<template #label>{{ i18n.ts.serverRules }}</template>
|
|
||||||
|
|
||||||
<ol class="_gaps_s" :class="$style.rules">
|
|
||||||
<li v-for="item, index in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
|
|
||||||
</ol>
|
|
||||||
</MkFolder>
|
|
||||||
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink>
|
|
||||||
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>{{ i18n.ts.privacyPolicy }}</FormLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSuspense :p="initStats">
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.statistics }}</template>
|
<div class="_gaps_m">
|
||||||
<FormSplit>
|
<MkKeyValue :copy="version">
|
||||||
<MkKeyValue>
|
<template #key>Misskey</template>
|
||||||
<template #key>{{ i18n.ts.users }}</template>
|
<template #value>{{ version }}</template>
|
||||||
<template #value>{{ number(stats.originalUsersCount) }}</template>
|
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkKeyValue>
|
<div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
|
||||||
<template #key>{{ i18n.ts.notes }}</template>
|
</div>
|
||||||
<template #value>{{ number(stats.originalNotesCount) }}</template>
|
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
|
||||||
</MkKeyValue>
|
</div>
|
||||||
</FormSplit>
|
|
||||||
</FormSection>
|
</FormSection>
|
||||||
</FormSuspense>
|
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>Well-known resources</template>
|
<div class="_gaps_m">
|
||||||
<div class="_gaps_s">
|
<FormSplit>
|
||||||
<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
|
<MkKeyValue>
|
||||||
<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
|
<template #key>{{ i18n.ts.administrator }}</template>
|
||||||
<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
|
<template #value>{{ instance.maintainerName }}</template>
|
||||||
<FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
|
</MkKeyValue>
|
||||||
<FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
|
<MkKeyValue>
|
||||||
</div>
|
<template #key>{{ i18n.ts.contact }}</template>
|
||||||
</FormSection>
|
<template #value>{{ instance.maintainerEmail }}</template>
|
||||||
</div>
|
</MkKeyValue>
|
||||||
</MkSpacer>
|
</FormSplit>
|
||||||
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink>
|
||||||
<XEmojis/>
|
<div class="_gaps_s">
|
||||||
</MkSpacer>
|
<MkFolder v-if="instance.serverRules.length > 0">
|
||||||
<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
|
<template #label>{{ i18n.ts.serverRules }}</template>
|
||||||
<XFederation/>
|
|
||||||
</MkSpacer>
|
<ol class="_gaps_s" :class="$style.rules">
|
||||||
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
<li v-for="item, index in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
|
||||||
<MkInstanceStats/>
|
</ol>
|
||||||
</MkSpacer>
|
</MkFolder>
|
||||||
|
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink>
|
||||||
|
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>{{ i18n.ts.privacyPolicy }}</FormLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSuspense :p="initStats">
|
||||||
|
<FormSection>
|
||||||
|
<template #label>{{ i18n.ts.statistics }}</template>
|
||||||
|
<FormSplit>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.users }}</template>
|
||||||
|
<template #value>{{ number(stats.originalUsersCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.notes }}</template>
|
||||||
|
<template #value>{{ number(stats.originalNotesCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</FormSplit>
|
||||||
|
</FormSection>
|
||||||
|
</FormSuspense>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<template #label>Well-known resources</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
|
||||||
|
<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
|
||||||
|
<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
|
||||||
|
<FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
|
||||||
|
<FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
||||||
|
<XEmojis/>
|
||||||
|
</MkSpacer>
|
||||||
|
<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
|
||||||
|
<XFederation/>
|
||||||
|
</MkSpacer>
|
||||||
|
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
||||||
|
<MkInstanceStats/>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -114,6 +116,7 @@ import FormSplit from '@/components/form/split.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';
|
||||||
import MkInstanceStats from '@/components/MkInstanceStats.vue';
|
import MkInstanceStats from '@/components/MkInstanceStats.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -7,34 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
<div :key="tab" class="_gaps">
|
||||||
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
|
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
||||||
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
|
||||||
<div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div>
|
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
||||||
<div :class="$style.header">
|
<div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div>
|
||||||
<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
|
<div :class="$style.header">
|
||||||
<span style="margin-right: 0.5em;">
|
<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
|
||||||
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
<span style="margin-right: 0.5em;">
|
||||||
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
|
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
||||||
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
|
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
|
||||||
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
|
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
|
||||||
</span>
|
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
|
||||||
<span>{{ announcement.title }}</span>
|
</span>
|
||||||
</div>
|
<span>{{ announcement.title }}</span>
|
||||||
<div :class="$style.content">
|
|
||||||
<Mfm :text="announcement.text"/>
|
|
||||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
|
||||||
<div style="opacity: 0.7; font-size: 85%;">
|
|
||||||
<MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div :class="$style.content">
|
||||||
<div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
<Mfm :text="announcement.text"/>
|
||||||
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||||
</div>
|
<div style="opacity: 0.7; font-size: 85%;">
|
||||||
</section>
|
<MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/>
|
||||||
</MkPagination>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
||||||
|
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -44,6 +46,7 @@ import { ref, computed } from 'vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
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 MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -7,53 +7,55 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700" :class="$style.main">
|
<MkSpacer :contentMax="700" :class="$style.main">
|
||||||
<div v-if="channel && tab === 'overview'" class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div class="_panel" :class="$style.bannerContainer">
|
<div v-if="channel && tab === 'overview'" key="overview" class="_gaps">
|
||||||
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
|
<div class="_panel" :class="$style.bannerContainer">
|
||||||
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
|
||||||
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
|
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
||||||
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
|
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
|
||||||
<div :class="$style.bannerStatus">
|
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
|
||||||
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
|
<div :class="$style.bannerStatus">
|
||||||
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
|
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
|
||||||
|
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
|
||||||
|
<div :class="$style.bannerFade"></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="channel.description" :class="$style.description">
|
||||||
|
<Mfm :text="channel.description" :isNote="false"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
|
|
||||||
<div :class="$style.bannerFade"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="channel.description" :class="$style.description">
|
|
||||||
<Mfm :text="channel.description" :isNote="false"/>
|
<MkFoldableSection>
|
||||||
|
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
|
||||||
|
<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
|
||||||
|
<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
|
||||||
|
</div>
|
||||||
|
</MkFoldableSection>
|
||||||
|
</div>
|
||||||
|
<div v-if="channel && tab === 'timeline'" key="timeline" class="_gaps">
|
||||||
|
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
||||||
|
|
||||||
|
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||||
|
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||||
|
|
||||||
|
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab === 'featured'" key="featured">
|
||||||
|
<MkNotes :pagination="featuredPagination"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab === 'search'" key="search">
|
||||||
|
<div class="_gaps">
|
||||||
|
<div>
|
||||||
|
<MkInput v-model="searchQuery" @enter="search()">
|
||||||
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
|
</MkInput>
|
||||||
|
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
||||||
|
</div>
|
||||||
|
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
<MkFoldableSection>
|
|
||||||
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
|
|
||||||
<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
|
|
||||||
<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
|
|
||||||
</div>
|
|
||||||
</MkFoldableSection>
|
|
||||||
</div>
|
|
||||||
<div v-if="channel && tab === 'timeline'" class="_gaps">
|
|
||||||
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
|
||||||
|
|
||||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
|
||||||
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
|
||||||
|
|
||||||
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab === 'featured'">
|
|
||||||
<MkNotes :pagination="featuredPagination"/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab === 'search'">
|
|
||||||
<div class="_gaps">
|
|
||||||
<div>
|
|
||||||
<MkInput v-model="searchQuery" @enter="search()">
|
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
|
||||||
</MkInput>
|
|
||||||
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
|
||||||
</div>
|
|
||||||
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
|
@ -87,6 +89,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
import { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import { isSupportShare } from '@/scripts/navigator.js';
|
import { isSupportShare } from '@/scripts/navigator.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
|
@ -100,6 +103,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('overview');
|
const tab = ref('overview');
|
||||||
|
|
||||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
const channel = ref<Misskey.entities.Channel | null>(null);
|
||||||
const favorited = ref(false);
|
const favorited = ref(false);
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
|
|
|
@ -7,44 +7,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'search'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div class="_gaps">
|
<div v-if="tab === 'search'" key="search">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
<div class="_gaps">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
</MkInput>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
<MkRadios v-model="searchType" @update:modelValue="search()">
|
</MkInput>
|
||||||
<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
|
<MkRadios v-model="searchType" @update:modelValue="search()">
|
||||||
<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
|
<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
|
||||||
</MkRadios>
|
<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
|
||||||
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
</MkRadios>
|
||||||
</div>
|
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MkFoldableSection v-if="channelPagination">
|
<MkFoldableSection v-if="channelPagination">
|
||||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||||
<MkChannelList :key="key" :pagination="channelPagination"/>
|
<MkChannelList :key="key" :pagination="channelPagination"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagination">
|
<MkPagination v-slot="{items}" :pagination="featuredPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'">
|
<div v-else-if="tab === 'favorites'" key="favorites">
|
||||||
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
|
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'following'">
|
<div v-else-if="tab === 'following'" key="following">
|
||||||
<MkPagination v-slot="{items}" :pagination="followingPagination">
|
<MkPagination v-slot="{items}" :pagination="followingPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'owned'">
|
<div v-else-if="tab === 'owned'" key="owned">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="ownedPagination">
|
<MkPagination v-slot="{items}" :pagination="ownedPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -58,6 +60,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
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 { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -9,13 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkSpacer v-if="tab === 'info'" :contentMax="800">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<XFileInfo :fileId="fileId"/>
|
<MkSpacer v-if="tab === 'info'" key="info" :contentMax="800">
|
||||||
</MkSpacer>
|
<XFileInfo :fileId="fileId"/>
|
||||||
|
</MkSpacer>
|
||||||
|
|
||||||
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
|
<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800">
|
||||||
<XNotes :fileId="fileId"/>
|
<XNotes :fileId="fileId"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, ref, defineAsyncComponent } from 'vue';
|
import { computed, ref, defineAsyncComponent } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
|
|
@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<div>
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<XFeatured/>
|
<XFeatured/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'users'">
|
<div v-else-if="tab === 'users'" key="users">
|
||||||
<XUsers/>
|
<XUsers/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'roles'">
|
<div v-else-if="tab === 'roles'" key="roles">
|
||||||
<XRoles/>
|
<XRoles/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import XFeatured from './explore.featured.vue';
|
||||||
import XUsers from './explore.users.vue';
|
import XUsers from './explore.users.vue';
|
||||||
import XRoles from './explore.roles.vue';
|
import XRoles from './explore.roles.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
|
|
@ -7,32 +7,34 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'featured'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<div class="_gaps_s">
|
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
|
||||||
</div>
|
|
||||||
</MkPagination>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'my'">
|
|
||||||
<div class="_gaps">
|
|
||||||
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
|
||||||
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'my'" key="my">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
|
<div class="_gaps">
|
||||||
<div class="_gaps_s">
|
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
|
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,6 +44,7 @@ import { computed, ref } from 'vue';
|
||||||
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-code"></i></template>
|
<template #icon><i class="ti ti-code"></i></template>
|
||||||
<template #label>{{ i18n.ts._play.viewSource }}</template>
|
<template #label>{{ i18n.ts._play.viewSource }}</template>
|
||||||
|
|
||||||
<MkCode :code="flash.script" lang="is" :inline="false" class="_monospace"/>
|
<MkCode :code="flash.script" lang="is" class="_monospace"/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<Mfm :text="`By @${flash.user.username}`"/>
|
<Mfm :text="`By @${flash.user.username}`"/>
|
||||||
|
|
|
@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="1400">
|
<MkSpacer :contentMax="1400">
|
||||||
<div class="_root">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'explore'">
|
<div v-if="tab === 'explore'" key="explore">
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
|
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
|
||||||
|
@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'my'">
|
<div v-else-if="tab === 'my'" key="my">
|
||||||
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -51,6 +51,7 @@ import { watch, ref, computed } from 'vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -7,111 +7,113 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
|
<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
|
||||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div class="fnfelxur">
|
<div v-if="tab === 'overview'" key="overview" class="_gaps_m">
|
||||||
<img :src="faviconUrl" alt="" class="icon"/>
|
<div class="fnfelxur">
|
||||||
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
<img :src="faviconUrl" alt="" class="icon"/>
|
||||||
</div>
|
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
|
||||||
<MkKeyValue :copy="host" oneline>
|
|
||||||
<template #key>Host</template>
|
|
||||||
<template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue oneline>
|
|
||||||
<template #key>{{ i18n.ts.software }}</template>
|
|
||||||
<template #value><span class="_monospace">{{ instance.softwareName || `(${i18n.ts.unknown})` }} / {{ instance.softwareVersion || `(${i18n.ts.unknown})` }}</span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue oneline>
|
|
||||||
<template #key>{{ i18n.ts.administrator }}</template>
|
|
||||||
<template #value>{{ instance.maintainerName || `(${i18n.ts.unknown})` }} ({{ instance.maintainerEmail || `(${i18n.ts.unknown})` }})</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</div>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
|
||||||
<template #value>{{ instance.description }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
|
|
||||||
<FormSection v-if="iAmModerator">
|
|
||||||
<template #label>Moderation</template>
|
|
||||||
<div class="_gaps_s">
|
|
||||||
<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
|
|
||||||
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
|
|
||||||
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
|
||||||
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
|
<MkKeyValue :copy="host" oneline>
|
||||||
<FormSection>
|
<template #key>Host</template>
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
<template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
|
||||||
<template #key>{{ i18n.ts.registeredAt }}</template>
|
</MkKeyValue>
|
||||||
<template #value><MkTime mode="detail" :time="instance.firstRetrievedAt"/></template>
|
<MkKeyValue oneline>
|
||||||
</MkKeyValue>
|
<template #key>{{ i18n.ts.software }}</template>
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
<template #value><span class="_monospace">{{ instance.softwareName || `(${i18n.ts.unknown})` }} / {{ instance.softwareVersion || `(${i18n.ts.unknown})` }}</span></template>
|
||||||
<template #key>{{ i18n.ts.updatedAt }}</template>
|
</MkKeyValue>
|
||||||
<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
|
<MkKeyValue oneline>
|
||||||
</MkKeyValue>
|
<template #key>{{ i18n.ts.administrator }}</template>
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
<template #value>{{ instance.maintainerName || `(${i18n.ts.unknown})` }} ({{ instance.maintainerEmail || `(${i18n.ts.unknown})` }})</template>
|
||||||
<template #key>{{ i18n.ts.latestRequestReceivedAt }}</template>
|
</MkKeyValue>
|
||||||
<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
|
||||||
<template #key>Following (Pub)</template>
|
|
||||||
<template #value>{{ number(instance.followingCount) }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
|
||||||
<template #key>Followers (Sub)</template>
|
|
||||||
<template #value>{{ number(instance.followersCount) }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<template #label>Well-known resources</template>
|
|
||||||
<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
|
|
||||||
<FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
|
|
||||||
<FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
|
|
||||||
<FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
|
|
||||||
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
|
|
||||||
</FormSection>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
|
||||||
<div class="cmhjzshl">
|
|
||||||
<div class="selects">
|
|
||||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
|
||||||
<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
|
|
||||||
<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
|
|
||||||
<option value="instance-users-total">{{ i18n.ts._instanceCharts.usersTotal }}</option>
|
|
||||||
<option value="instance-notes">{{ i18n.ts._instanceCharts.notes }}</option>
|
|
||||||
<option value="instance-notes-total">{{ i18n.ts._instanceCharts.notesTotal }}</option>
|
|
||||||
<option value="instance-ff">{{ i18n.ts._instanceCharts.ff }}</option>
|
|
||||||
<option value="instance-ff-total">{{ i18n.ts._instanceCharts.ffTotal }}</option>
|
|
||||||
<option value="instance-drive-usage">{{ i18n.ts._instanceCharts.cacheSize }}</option>
|
|
||||||
<option value="instance-drive-usage-total">{{ i18n.ts._instanceCharts.cacheSizeTotal }}</option>
|
|
||||||
<option value="instance-drive-files">{{ i18n.ts._instanceCharts.files }}</option>
|
|
||||||
<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="charts">
|
<MkKeyValue>
|
||||||
<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
<template #value>{{ instance.description }}</template>
|
||||||
<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
|
</MkKeyValue>
|
||||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
|
||||||
|
<FormSection v-if="iAmModerator">
|
||||||
|
<template #label>Moderation</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
||||||
|
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
|
<template #key>{{ i18n.ts.registeredAt }}</template>
|
||||||
|
<template #value><MkTime mode="detail" :time="instance.firstRetrievedAt"/></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
|
<template #key>{{ i18n.ts.updatedAt }}</template>
|
||||||
|
<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
|
<template #key>{{ i18n.ts.latestRequestReceivedAt }}</template>
|
||||||
|
<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
|
<template #key>Following (Pub)</template>
|
||||||
|
<template #value>{{ number(instance.followingCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
|
<template #key>Followers (Sub)</template>
|
||||||
|
<template #value>{{ number(instance.followersCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<template #label>Well-known resources</template>
|
||||||
|
<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
|
||||||
|
<FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
|
||||||
|
<FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
|
||||||
|
<FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
|
||||||
|
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
|
||||||
|
</FormSection>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab === 'chart'" key="chart" class="_gaps_m">
|
||||||
|
<div class="cmhjzshl">
|
||||||
|
<div class="selects">
|
||||||
|
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||||
|
<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
|
||||||
|
<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
|
||||||
|
<option value="instance-users-total">{{ i18n.ts._instanceCharts.usersTotal }}</option>
|
||||||
|
<option value="instance-notes">{{ i18n.ts._instanceCharts.notes }}</option>
|
||||||
|
<option value="instance-notes-total">{{ i18n.ts._instanceCharts.notesTotal }}</option>
|
||||||
|
<option value="instance-ff">{{ i18n.ts._instanceCharts.ff }}</option>
|
||||||
|
<option value="instance-ff-total">{{ i18n.ts._instanceCharts.ffTotal }}</option>
|
||||||
|
<option value="instance-drive-usage">{{ i18n.ts._instanceCharts.cacheSize }}</option>
|
||||||
|
<option value="instance-drive-usage-total">{{ i18n.ts._instanceCharts.cacheSizeTotal }}</option>
|
||||||
|
<option value="instance-drive-files">{{ i18n.ts._instanceCharts.files }}</option>
|
||||||
|
<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
</div>
|
||||||
|
<div class="charts">
|
||||||
|
<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
|
||||||
|
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
|
<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
|
||||||
|
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else-if="tab === 'users'" key="users" class="_gaps_m">
|
||||||
<div v-else-if="tab === 'users'" class="_gaps_m">
|
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
|
||||||
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
|
<MkUserCardMini :user="user"/>
|
||||||
<MkUserCardMini :user="user"/>
|
</MkA>
|
||||||
</MkA>
|
</MkPagination>
|
||||||
</MkPagination>
|
</div>
|
||||||
</div>
|
<div v-else-if="tab === 'raw'" key="raw" class="_gaps_m">
|
||||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
<MkObjectView tall :value="instance">
|
||||||
<MkObjectView tall :value="instance">
|
</MkObjectView>
|
||||||
</MkObjectView>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -136,6 +138,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
|
|
||||||
|
@ -144,6 +147,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('overview');
|
const tab = ref('overview');
|
||||||
|
|
||||||
const chartSrc = ref('instance-requests');
|
const chartSrc = ref('instance-requests');
|
||||||
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
||||||
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
||||||
|
|
|
@ -7,20 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'my'" class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<div v-if="tab === 'my'" key="my" class="_gaps">
|
||||||
|
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
|
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
|
||||||
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">
|
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">
|
||||||
|
<MkClipPreview :clip="item"/>
|
||||||
|
</MkA>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab === 'favorites'" key="favorites" class="_gaps">
|
||||||
|
<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
|
||||||
<MkClipPreview :clip="item"/>
|
<MkClipPreview :clip="item"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</MkPagination>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
<div v-else-if="tab === 'favorites'" class="_gaps">
|
|
||||||
<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
|
|
||||||
<MkClipPreview :clip="item"/>
|
|
||||||
</MkA>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { clipsCache } from '@/cache.js';
|
import { clipsCache } from '@/cache.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'clips/list' as const,
|
endpoint: 'clips/list' as const,
|
||||||
|
@ -44,6 +47,7 @@ const pagination = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tab = ref('my');
|
const tab = ref('my');
|
||||||
|
|
||||||
const favorites = ref<Misskey.entities.Clip[] | null>(null);
|
const favorites = ref<Misskey.entities.Clip[] | null>(null);
|
||||||
|
|
||||||
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
|
@ -7,15 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div v-if="tab === 'all'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<XNotifications class="notifications" :excludeTypes="excludeTypes"/>
|
<div v-if="tab === 'all'" key="all">
|
||||||
</div>
|
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||||
<div v-else-if="tab === 'mentions'">
|
</div>
|
||||||
<MkNotes :pagination="mentionsPagination"/>
|
<div v-else-if="tab === 'mentions'" key="mention">
|
||||||
</div>
|
<MkNotes :pagination="mentionsPagination"/>
|
||||||
<div v-else-if="tab === 'directNotes'">
|
</div>
|
||||||
<MkNotes :pagination="directNotesPagination"/>
|
<div v-else-if="tab === 'directNotes'" key="directNotes">
|
||||||
</div>
|
<MkNotes :pagination="directNotesPagination"/>
|
||||||
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import XNotifications from '@/components/MkNotifications.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
@ -96,3 +99,10 @@ definePageMetadata(computed(() => ({
|
||||||
icon: 'ti ti-bell',
|
icon: 'ti ti-bell',
|
||||||
})));
|
})));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.notifications {
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -7,30 +7,32 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'featured'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<div class="_gaps">
|
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
<div class="_gaps">
|
||||||
</div>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
||||||
</MkPagination>
|
</div>
|
||||||
</div>
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'my'" class="_gaps">
|
<div v-else-if="tab === 'my'" key="my" class="_gaps">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
|
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
|
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -40,6 +42,7 @@ import { computed, ref } from 'vue';
|
||||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -7,18 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
|
||||||
<MkSpacer v-if="tab === 'note'" :contentMax="800">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="notesSearchAvailable">
|
<MkSpacer v-if="tab === 'note'" key="note" :contentMax="800">
|
||||||
<XNote/>
|
<div v-if="notesSearchAvailable">
|
||||||
</div>
|
<XNote/>
|
||||||
<div v-else>
|
</div>
|
||||||
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
<div v-else>
|
||||||
</div>
|
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
||||||
</MkSpacer>
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
|
||||||
<MkSpacer v-else-if="tab === 'user'" :contentMax="800">
|
<MkSpacer v-else-if="tab === 'user'" key="user" :contentMax="800">
|
||||||
<XUser/>
|
<XUser/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const XNote = defineAsyncComponent(() => import('./search.note.vue'));
|
const XNote = defineAsyncComponent(() => import('./search.note.vue'));
|
||||||
const XUser = defineAsyncComponent(() => import('./search.user.vue'));
|
const XUser = defineAsyncComponent(() => import('./search.user.vue'));
|
||||||
|
|
|
@ -155,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
|
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
|
||||||
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
|
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
|
||||||
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<MkSelect v-model="serverDisconnectedBehavior">
|
<MkSelect v-model="serverDisconnectedBehavior">
|
||||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||||
|
@ -296,6 +297,7 @@ const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||||
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||||
|
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
miLocalStorage.setItem('lang', lang.value as string);
|
miLocalStorage.setItem('lang', lang.value as string);
|
||||||
|
|
|
@ -7,27 +7,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div ref="rootEl" v-hotkey.global="keymap">
|
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
||||||
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
|
<div :key="src + withRenotes + withReplies + onlyFiles" ref="rootEl" v-hotkey.global="keymap">
|
||||||
{{ i18n.ts._timelineDescription[src] }}
|
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
|
||||||
</MkInfo>
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
</MkInfo>
|
||||||
|
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkTimeline
|
||||||
ref="tlComponent"
|
ref="tlComponent"
|
||||||
:key="src + withRenotes + withReplies + onlyFiles"
|
:key="src + withRenotes + withReplies + onlyFiles"
|
||||||
:src="src.split(':')[0]"
|
:src="src.split(':')[0]"
|
||||||
:list="src.split(':')[1]"
|
:list="src.split(':')[1]"
|
||||||
:withRenotes="withRenotes"
|
:withRenotes="withRenotes"
|
||||||
:withReplies="withReplies"
|
:withReplies="withReplies"
|
||||||
:onlyFiles="onlyFiles"
|
:onlyFiles="onlyFiles"
|
||||||
:sound="true"
|
:sound="true"
|
||||||
@queue="queueUpdated"
|
@queue="queueUpdated"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -38,6 +39,7 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { scroll } from '@/scripts/scroll.js';
|
import { scroll } from '@/scripts/scroll.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
@ -69,7 +71,9 @@ const withRenotes = ref(true);
|
||||||
const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
|
const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
|
||||||
const onlyFiles = ref(false);
|
const onlyFiles = ref(false);
|
||||||
|
|
||||||
watch(src, () => queue.value = 0);
|
watch(src, () => {
|
||||||
|
queue.value = 0;
|
||||||
|
});
|
||||||
|
|
||||||
watch(withReplies, (x) => {
|
watch(withReplies, (x) => {
|
||||||
if ($i) defaultStore.set('tlWithReplies', x);
|
if ($i) defaultStore.set('tlWithReplies', x);
|
||||||
|
|
|
@ -8,19 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="user">
|
<div v-if="user">
|
||||||
<XHome v-if="tab === 'home'" :user="user"/>
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
|
<XHome v-if="tab === 'home'" key="home" :user="user"/>
|
||||||
<XTimeline :user="user"/>
|
<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800" style="padding-top: 0">
|
||||||
</MkSpacer>
|
<XTimeline :user="user"/>
|
||||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
</MkSpacer>
|
||||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
<XActivity v-else-if="tab === 'activity'" key="activity" :user="user"/>
|
||||||
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
<XAchievements v-else-if="tab === 'achievements'" key="achievements" :user="user"/>
|
||||||
<XClips v-else-if="tab === 'clips'" :user="user"/>
|
<XReactions v-else-if="tab === 'reactions'" key="reactions" :user="user"/>
|
||||||
<XLists v-else-if="tab === 'lists'" :user="user"/>
|
<XClips v-else-if="tab === 'clips'" key="clips" :user="user"/>
|
||||||
<XPages v-else-if="tab === 'pages'" :user="user"/>
|
<XLists v-else-if="tab === 'lists'" key="lists" :user="user"/>
|
||||||
<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
|
<XPages v-else-if="tab === 'pages'" key="pages" :user="user"/>
|
||||||
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
|
<XFlashs v-else-if="tab === 'flashs'" key="flashs" :user="user"/>
|
||||||
<XRaw v-else-if="tab === 'raw'" :user="user"/>
|
<XGallery v-else-if="tab === 'gallery'" key="gallery" :user="user"/>
|
||||||
|
<XRaw v-else-if="tab === 'raw'" key="raw" :user="user"/>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchUser()"/>
|
<MkError v-else-if="error" @retry="fetchUser()"/>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
|
@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
||||||
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||||
|
@ -57,6 +60,7 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = ref(props.page);
|
const tab = ref(props.page);
|
||||||
|
|
||||||
const user = ref<null | Misskey.entities.UserDetailed>(null);
|
const user = ref<null | Misskey.entities.UserDetailed>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
|
|
||||||
|
|
|
@ -2,33 +2,114 @@
|
||||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import type { ILocale, ParameterizedString } from '../../../../locales/index.js';
|
||||||
|
|
||||||
export class I18n<T extends Record<string, any>> {
|
type FlattenKeys<T extends ILocale, TPrediction> = keyof {
|
||||||
public ts: T;
|
[K in keyof T as T[K] extends ILocale
|
||||||
|
? FlattenKeys<T[K], TPrediction> extends infer C extends string
|
||||||
|
? `${K & string}.${C}`
|
||||||
|
: never
|
||||||
|
: T[K] extends TPrediction
|
||||||
|
? K
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
constructor(locale: T) {
|
type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString<string>>> = T extends ILocale
|
||||||
this.ts = locale;
|
? TKey extends `${infer K}.${infer C}`
|
||||||
|
// @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString<string>> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
|
||||||
|
? ParametersOf<T[K], C>
|
||||||
|
: TKey extends keyof T
|
||||||
|
? T[TKey] extends ParameterizedString<infer P>
|
||||||
|
? P
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type Ts<T extends ILocale> = {
|
||||||
|
readonly [K in keyof T as T[K] extends ParameterizedString<string> ? never : K]: T[K] extends ILocale ? Ts<T[K]> : string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class I18n<T extends ILocale> {
|
||||||
|
constructor(private locale: T) {
|
||||||
//#region BIND
|
//#region BIND
|
||||||
this.t = this.t.bind(this);
|
this.t = this.t.bind(this);
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
// string にしているのは、ドット区切りでのパス指定を許可するため
|
public get ts(): Ts<T> {
|
||||||
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
|
if (_DEV_) {
|
||||||
public t(key: string, args?: Record<string, string | number>): string {
|
class Handler<TTarget extends object> implements ProxyHandler<TTarget> {
|
||||||
try {
|
get(target: TTarget, p: string | symbol): unknown {
|
||||||
let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
|
const value = target[p as keyof TTarget];
|
||||||
|
|
||||||
if (args) {
|
if (typeof value === 'object') {
|
||||||
for (const [k, v] of Object.entries(args)) {
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。
|
||||||
str = str.replace(`{${k}}`, v.toString());
|
return new Proxy(value!, new Handler<TTarget[keyof TTarget] & object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter);
|
||||||
|
|
||||||
|
if (parameters.length) {
|
||||||
|
console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Unexpected locale key: ${String(p)}`);
|
||||||
|
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str;
|
|
||||||
} catch (err) {
|
return new Proxy(this.locale, new Handler()) as Ts<T>;
|
||||||
console.warn(`missing localization '${key}'`);
|
|
||||||
return key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.locale as Ts<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
|
||||||
|
*/
|
||||||
|
public t<TKey extends FlattenKeys<T, string>>(key: TKey): string;
|
||||||
|
public t<TKey extends FlattenKeys<T, ParameterizedString<string>>>(key: TKey, args: { readonly [_ in ParametersOf<T, TKey>]: string | number }): string;
|
||||||
|
public t(key: string, args?: { readonly [_: string]: string | number }) {
|
||||||
|
let str: string | ParameterizedString<string> | ILocale = this.locale;
|
||||||
|
|
||||||
|
for (const k of key.split('.')) {
|
||||||
|
str = str[k];
|
||||||
|
|
||||||
|
if (_DEV_) {
|
||||||
|
if (typeof str === 'undefined') {
|
||||||
|
console.error(`Unexpected locale key: ${key}`);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
if (_DEV_) {
|
||||||
|
const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter));
|
||||||
|
|
||||||
|
if (missing.length) {
|
||||||
|
console.error(`Missing locale parameters: ${missing.join(', ')} at ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(args)) {
|
||||||
|
const search = `{${k}}`;
|
||||||
|
|
||||||
|
if (_DEV_) {
|
||||||
|
if (!(str as string).includes(search)) {
|
||||||
|
console.error(`Unexpected locale parameter: ${k} at ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = (str as string).replace(search, v.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -427,6 +427,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
sfxVolume: 1,
|
sfxVolume: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
enableHorizontalSwipe: {
|
||||||
|
where: 'device',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
Loading…
Reference in New Issue