mirror of
https://github.com/misskey-dev/misskey.git
synced 2025-01-10 00:49:22 +09:00
Compare commits
20 Commits
691d9b01b0
...
c58744078a
Author | SHA1 | Date | |
---|---|---|---|
|
c58744078a | ||
|
fb9c87a5b5 | ||
|
3c81926f71 | ||
|
f123be38b9 | ||
|
0804092426 | ||
|
3e0fcaeca8 | ||
|
5a2b29a3b4 | ||
|
1af944cbbb | ||
|
f655f25c04 | ||
|
a6dc59a9da | ||
|
6acc27687d | ||
|
0ac6e05008 | ||
|
eb0a2f4b9f | ||
|
e0fcc30163 | ||
|
d5156dbfb9 | ||
|
1d837c0e64 | ||
|
c54ffd2878 | ||
|
88a7d19160 | ||
|
92bc2e71ca | ||
|
3978383d8f |
@ -4,11 +4,16 @@
|
|||||||
-
|
-
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
- Enhance: PC画面でチャンネルが複数列で表示されるように
|
||||||
|
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
|
||||||
|
- Enhance: 照会に失敗した場合、その理由を表示するように
|
||||||
|
- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正
|
||||||
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
||||||
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
|
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
|
||||||
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
|
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
|
||||||
- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正
|
- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
|
||||||
|
- Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )
|
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )
|
||||||
|
67
locales/index.d.ts
vendored
67
locales/index.d.ts
vendored
@ -5222,6 +5222,14 @@ export interface Locale extends ILocale {
|
|||||||
* 注意事項を理解した上でオンにします。
|
* 注意事項を理解した上でオンにします。
|
||||||
*/
|
*/
|
||||||
"acknowledgeNotesAndEnable": string;
|
"acknowledgeNotesAndEnable": string;
|
||||||
|
/**
|
||||||
|
* このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。
|
||||||
|
*/
|
||||||
|
"federationSpecified": string;
|
||||||
|
/**
|
||||||
|
* このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。
|
||||||
|
*/
|
||||||
|
"federationDisabled": string;
|
||||||
"_accountSettings": {
|
"_accountSettings": {
|
||||||
/**
|
/**
|
||||||
* コンテンツの表示にログインを必須にする
|
* コンテンツの表示にログインを必須にする
|
||||||
@ -10601,6 +10609,65 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"sent": string;
|
"sent": string;
|
||||||
};
|
};
|
||||||
|
"_remoteLookupErrors": {
|
||||||
|
"_federationNotAllowed": {
|
||||||
|
/**
|
||||||
|
* このサーバーとは通信できません
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。
|
||||||
|
* サーバー管理者にお問い合わせください。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_uriInvalid": {
|
||||||
|
/**
|
||||||
|
* URIが不正です
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* 入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_requestFailed": {
|
||||||
|
/**
|
||||||
|
* リクエストに失敗しました
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_responseInvalid": {
|
||||||
|
/**
|
||||||
|
* レスポンスが不正です
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* このサーバーと通信することはできましたが、得られたデータが不正なものでした。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_responseInvalidIdHostNotMatch": {
|
||||||
|
/**
|
||||||
|
* 入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
"_noSuchObject": {
|
||||||
|
/**
|
||||||
|
* 見つかりません
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* 要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。
|
||||||
|
*/
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
@ -1301,6 +1301,8 @@ lockdown: "ロックダウン"
|
|||||||
pleaseSelectAccount: "アカウントを選択してください"
|
pleaseSelectAccount: "アカウントを選択してください"
|
||||||
availableRoles: "利用可能なロール"
|
availableRoles: "利用可能なロール"
|
||||||
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
|
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
|
||||||
|
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
|
||||||
|
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
|
||||||
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||||
@ -2826,3 +2828,22 @@ _selfXssPrevention:
|
|||||||
_followRequest:
|
_followRequest:
|
||||||
recieved: "受け取った申請"
|
recieved: "受け取った申請"
|
||||||
sent: "送った申請"
|
sent: "送った申請"
|
||||||
|
|
||||||
|
_remoteLookupErrors:
|
||||||
|
_federationNotAllowed:
|
||||||
|
title: "このサーバーとは通信できません"
|
||||||
|
description: "このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。\nサーバー管理者にお問い合わせください。"
|
||||||
|
_uriInvalid:
|
||||||
|
title: "URIが不正です"
|
||||||
|
description: "入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。"
|
||||||
|
_requestFailed:
|
||||||
|
title: "リクエストに失敗しました"
|
||||||
|
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
|
||||||
|
_responseInvalid:
|
||||||
|
title: "レスポンスが不正です"
|
||||||
|
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。"
|
||||||
|
_responseInvalidIdHostNotMatch:
|
||||||
|
description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
|
||||||
|
_noSuchObject:
|
||||||
|
title: "見つかりません"
|
||||||
|
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"
|
||||||
|
@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
|
|||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
@ -66,7 +67,7 @@ export class Resolver {
|
|||||||
if (isCollectionOrOrderedCollection(collection)) {
|
if (isCollectionOrOrderedCollection(collection)) {
|
||||||
return collection;
|
return collection;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unrecognized collection type: ${collection.type}`);
|
throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,15 +81,15 @@ export class Resolver {
|
|||||||
// URLs with fragment parts cannot be resolved correctly because
|
// URLs with fragment parts cannot be resolved correctly because
|
||||||
// the fragment part does not get transmitted over HTTP(S).
|
// the fragment part does not get transmitted over HTTP(S).
|
||||||
// Avoid strange behaviour by not trying to resolve these at all.
|
// Avoid strange behaviour by not trying to resolve these at all.
|
||||||
throw new Error(`cannot resolve URL with fragment: ${value}`);
|
throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.history.has(value)) {
|
if (this.history.has(value)) {
|
||||||
throw new Error('cannot resolve already resolved one');
|
throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', 'cannot resolve already resolved one');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.history.size > this.recursionLimit) {
|
if (this.history.size > this.recursionLimit) {
|
||||||
throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.history.add(value);
|
this.history.add(value);
|
||||||
@ -99,7 +100,7 @@ export class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.utilityService.isFederationAllowedHost(host)) {
|
if (!this.utilityService.isFederationAllowedHost(host)) {
|
||||||
throw new Error('Instance is blocked');
|
throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.signToActivityPubGet && !this.user) {
|
if (this.config.signToActivityPubGet && !this.user) {
|
||||||
@ -115,7 +116,7 @@ export class Resolver {
|
|||||||
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
||||||
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
||||||
) {
|
) {
|
||||||
throw new Error('invalid response');
|
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpRequestService / ApRequestService have already checked that
|
// HttpRequestService / ApRequestService have already checked that
|
||||||
@ -123,11 +124,11 @@ export class Resolver {
|
|||||||
// object after redirects; here we double-check that no redirects
|
// object after redirects; here we double-check that no redirects
|
||||||
// bounced between hosts
|
// bounced between hosts
|
||||||
if (object.id == null) {
|
if (object.id == null) {
|
||||||
throw new Error('invalid AP object: missing id');
|
throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
|
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
|
||||||
throw new Error(`invalid AP object ${value}: id ${object.id} has different host`);
|
throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -136,7 +137,7 @@ export class Resolver {
|
|||||||
@bindThis
|
@bindThis
|
||||||
private resolveLocal(url: string): Promise<IObject> {
|
private resolveLocal(url: string): Promise<IObject> {
|
||||||
const parsed = this.apDbResolverService.parseUri(url);
|
const parsed = this.apDbResolverService.parseUri(url);
|
||||||
if (!parsed.local) throw new Error('resolveLocal: not local');
|
if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', 'resolveLocal: not local');
|
||||||
|
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
case 'notes':
|
case 'notes':
|
||||||
@ -165,7 +166,7 @@ export class Resolver {
|
|||||||
case 'follows':
|
case 'follows':
|
||||||
return this.followRequestsRepository.findOneBy({ id: parsed.id })
|
return this.followRequestsRepository.findOneBy({ id: parsed.id })
|
||||||
.then(async followRequest => {
|
.then(async followRequest => {
|
||||||
if (followRequest == null) throw new Error('resolveLocal: invalid follow request ID');
|
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', 'resolveLocal: invalid follow request ID');
|
||||||
const [follower, followee] = await Promise.all([
|
const [follower, followee] = await Promise.all([
|
||||||
this.usersRepository.findOneBy({
|
this.usersRepository.findOneBy({
|
||||||
id: followRequest.followerId,
|
id: followRequest.followerId,
|
||||||
@ -177,12 +178,12 @@ export class Resolver {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
if (follower == null || followee == null) {
|
if (follower == null || followee == null) {
|
||||||
throw new Error('resolveLocal: follower or followee does not exist');
|
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', 'resolveLocal: follower or followee does not exist');
|
||||||
}
|
}
|
||||||
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
|
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
|
throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +132,7 @@ export class MetaEntityService {
|
|||||||
enableUrlPreview: instance.urlPreviewEnabled,
|
enableUrlPreview: instance.urlPreviewEnabled,
|
||||||
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
|
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
|
||||||
maxFileSize: this.config.maxFileSize,
|
maxFileSize: this.config.maxFileSize,
|
||||||
|
federation: this.meta.federation,
|
||||||
};
|
};
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
|
@ -261,6 +261,11 @@ export const packedMetaLiteSchema = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
federation: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['all', 'specified', 'none'],
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
@ -32,6 +33,31 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
federationNotAllowed: {
|
||||||
|
message: 'Federation for this host is not allowed.',
|
||||||
|
code: 'FEDERATION_NOT_ALLOWED',
|
||||||
|
id: '974b799e-1a29-4889-b706-18d4dd93e266',
|
||||||
|
},
|
||||||
|
uriInvalid: {
|
||||||
|
message: 'URI is invalid.',
|
||||||
|
code: 'URI_INVALID',
|
||||||
|
id: '1a5eab56-e47b-48c2-8d5e-217b897d70db',
|
||||||
|
},
|
||||||
|
requestFailed: {
|
||||||
|
message: 'Request failed.',
|
||||||
|
code: 'REQUEST_FAILED',
|
||||||
|
id: '81b539cf-4f57-4b29-bc98-032c33c0792e',
|
||||||
|
},
|
||||||
|
responseInvalid: {
|
||||||
|
message: 'Response from remote server is invalid.',
|
||||||
|
code: 'RESPONSE_INVALID',
|
||||||
|
id: '70193c39-54f3-4813-82f0-70a680f7495b',
|
||||||
|
},
|
||||||
|
responseInvalidIdHostNotMatch: {
|
||||||
|
message: 'Requested URI and response URI host does not match.',
|
||||||
|
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
|
||||||
|
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
|
||||||
|
},
|
||||||
noSuchObject: {
|
noSuchObject: {
|
||||||
message: 'No such object.',
|
message: 'No such object.',
|
||||||
code: 'NO_SUCH_OBJECT',
|
code: 'NO_SUCH_OBJECT',
|
||||||
@ -110,7 +136,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||||
if (!this.utilityService.isFederationAllowedUri(uri)) return null;
|
if (!this.utilityService.isFederationAllowedUri(uri)) {
|
||||||
|
throw new ApiError(meta.errors.federationNotAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
let local = await this.mergePack(me, ...await Promise.all([
|
let local = await this.mergePack(me, ...await Promise.all([
|
||||||
this.apDbResolverService.getUserFromApId(uri),
|
this.apDbResolverService.getUserFromApId(uri),
|
||||||
@ -125,7 +153,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
|
|
||||||
// リモートから一旦オブジェクトフェッチ
|
// リモートから一旦オブジェクトフェッチ
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
const object = await resolver.resolve(uri) as any;
|
const object = await resolver.resolve(uri).catch((err) => {
|
||||||
|
if (err instanceof IdentifiableError) {
|
||||||
|
switch (err.id) {
|
||||||
|
// resolve
|
||||||
|
case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
|
||||||
|
throw new ApiError(meta.errors.uriInvalid);
|
||||||
|
case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
|
||||||
|
case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
|
||||||
|
throw new ApiError(meta.errors.requestFailed);
|
||||||
|
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
||||||
|
throw new ApiError(meta.errors.federationNotAllowed);
|
||||||
|
case '72180409-793c-4973-868e-5a118eb5519b':
|
||||||
|
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
|
||||||
|
throw new ApiError(meta.errors.responseInvalid);
|
||||||
|
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
|
||||||
|
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
|
||||||
|
|
||||||
|
// resolveLocal
|
||||||
|
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
||||||
|
throw new ApiError(meta.errors.uriInvalid);
|
||||||
|
case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
|
||||||
|
case '06ae3170-1796-4d93-a697-2611ea6d83b6':
|
||||||
|
throw new ApiError(meta.errors.noSuchObject);
|
||||||
|
case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
|
||||||
|
throw new ApiError(meta.errors.responseInvalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(meta.errors.requestFailed);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (object.id == null) {
|
||||||
|
throw new ApiError(meta.errors.responseInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
|
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
|
||||||
// これはDBに存在する可能性があるため再度DB検索
|
// これはDBに存在する可能性があるため再度DB検索
|
||||||
|
@ -131,11 +131,7 @@ describe('Note', () => {
|
|||||||
rejects(
|
rejects(
|
||||||
async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
|
async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
/**
|
strictEqual(err.code, 'REQUEST_FAILED');
|
||||||
* FIXME: this error is not handled
|
|
||||||
* @see https://github.com/misskey-dev/misskey/issues/12736
|
|
||||||
*/
|
|
||||||
strictEqual(err.code, 'INTERNAL_ERROR');
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -125,7 +125,9 @@ const bannerStyle = computed(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.chart">
|
<div :class="$style.chart">
|
||||||
<div class="selects">
|
<div class="selects">
|
||||||
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
|
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
|
||||||
<optgroup :label="i18n.ts.federation">
|
<optgroup v-if="shouldShowFederation" :label="i18n.ts.federation">
|
||||||
<option value="federation">{{ i18n.ts._charts.federation }}</option>
|
<option value="federation">{{ i18n.ts._charts.federation }}</option>
|
||||||
<option value="ap-request">{{ i18n.ts._charts.apRequest }}</option>
|
<option value="ap-request">{{ i18n.ts._charts.apRequest }}</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<optgroup :label="i18n.ts.notes">
|
<optgroup :label="i18n.ts.notes">
|
||||||
<option value="notes">{{ i18n.ts._charts.notesIncDec }}</option>
|
<option value="notes">{{ i18n.ts._charts.notesIncDec }}</option>
|
||||||
<option value="local-notes">{{ i18n.ts._charts.localNotesIncDec }}</option>
|
<option value="local-notes">{{ i18n.ts._charts.localNotesIncDec }}</option>
|
||||||
<option value="remote-notes">{{ i18n.ts._charts.remoteNotesIncDec }}</option>
|
<option v-if="shouldShowFederation" value="remote-notes">{{ i18n.ts._charts.remoteNotesIncDec }}</option>
|
||||||
<option value="notes-total">{{ i18n.ts._charts.notesTotal }}</option>
|
<option value="notes-total">{{ i18n.ts._charts.notesTotal }}</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup :label="i18n.ts.drive">
|
<optgroup :label="i18n.ts.drive">
|
||||||
@ -46,9 +46,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;">
|
<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;">
|
||||||
<option value="active-users">Active users</option>
|
<option value="active-users">Active users</option>
|
||||||
<option value="notes">Notes</option>
|
<option value="notes">Notes</option>
|
||||||
<option value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
|
<option v-if="shouldShowFederation" value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
|
||||||
<option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
|
<option v-if="shouldShowFederation" value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
|
||||||
<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
|
<option v-if="shouldShowFederation" value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<div class="_panel" :class="$style.heatmap">
|
<div class="_panel" :class="$style.heatmap">
|
||||||
<MkHeatmap :src="heatmapSrc" :label="'Read & Write'"/>
|
<MkHeatmap :src="heatmapSrc" :label="'Read & Write'"/>
|
||||||
@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
|
|
||||||
<MkFoldableSection class="item">
|
<MkFoldableSection v-if="shouldShowFederation" class="item">
|
||||||
<template #header>Federation</template>
|
<template #header>Federation</template>
|
||||||
<div :class="$style.federation">
|
<div :class="$style.federation">
|
||||||
<div class="pies">
|
<div class="pies">
|
||||||
@ -84,13 +84,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, shallowRef } from 'vue';
|
import { onMounted, ref, computed, shallowRef } from 'vue';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkChart from '@/components/MkChart.vue';
|
import MkChart from '@/components/MkChart.vue';
|
||||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||||
|
import { $i } from '@/account.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
|
import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
@ -100,6 +102,8 @@ import { initChart } from '@/scripts/init-chart.js';
|
|||||||
|
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
|
const shouldShowFederation = computed(() => instance.federation !== 'none' || $i?.isModerator);
|
||||||
|
|
||||||
const chartLimit = 500;
|
const chartLimit = 500;
|
||||||
const chartSpan = ref<'hour' | 'day'>('hour');
|
const chartSpan = ref<'hour' | 'day'>('hour');
|
||||||
const chartSrc = ref('active-users');
|
const chartSrc = ref('active-users');
|
||||||
|
@ -10,8 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div v-if="instance.disableRegistration">
|
<div v-if="instance.disableRegistration || instance.federation !== 'all'" class="_gaps_s">
|
||||||
<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
|
<MkInfo v-if="instance.disableRegistration" warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
|
||||||
|
<MkInfo v-if="instance.federation === 'specified'" warn>{{ i18n.ts.federationSpecified }}</MkInfo>
|
||||||
|
<MkInfo v-else-if="instance.federation === 'none'" warn>{{ i18n.ts.federationDisabled }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header>{{ i18n.ts.selectUser }}</template>
|
<template #header>{{ i18n.ts.selectUser }}</template>
|
||||||
<div>
|
<div>
|
||||||
<div :class="$style.form">
|
<div :class="$style.form">
|
||||||
<MkInput v-if="localOnly" v-model="username" :autofocus="true" @update:modelValue="search">
|
<MkInput v-if="computedLocalOnly" v-model="username" :autofocus="true" @update:modelValue="search">
|
||||||
<template #label>{{ i18n.ts.username }}</template>
|
<template #label>{{ i18n.ts.username }}</template>
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, shallowRef } from 'vue';
|
import { onMounted, ref, computed, shallowRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
@ -70,6 +70,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
|||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { host as currentHost, hostname } from '@@/js/config.js';
|
import { host as currentHost, hostname } from '@@/js/config.js';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -86,6 +87,8 @@ const props = withDefaults(defineProps<{
|
|||||||
localOnly: false,
|
localOnly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const computedLocalOnly = computed(() => props.localOnly || instance.federation === 'none');
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const users = ref<Misskey.entities.UserLite[]>([]);
|
const users = ref<Misskey.entities.UserLite[]>([]);
|
||||||
@ -100,7 +103,7 @@ function search() {
|
|||||||
}
|
}
|
||||||
misskeyApi('users/search-by-username-and-host', {
|
misskeyApi('users/search-by-username-and-host', {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
host: props.localOnly ? '.' : host.value,
|
host: computedLocalOnly.value ? '.' : host.value,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
detail: false,
|
detail: false,
|
||||||
}).then(_users => {
|
}).then(_users => {
|
||||||
@ -142,7 +145,7 @@ onMounted(() => {
|
|||||||
}).then(foundUsers => {
|
}).then(foundUsers => {
|
||||||
let _users = foundUsers;
|
let _users = foundUsers;
|
||||||
_users = _users.filter((u) => {
|
_users = _users.filter((u) => {
|
||||||
if (props.localOnly) {
|
if (computedLocalOnly.value) {
|
||||||
return u.host == null;
|
return u.host == null;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -18,8 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div v-html="instance.description || i18n.ts.headlineMisskey"></div>
|
<div v-html="instance.description || i18n.ts.headlineMisskey"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="instance.disableRegistration" :class="$style.mainWarn">
|
<div v-if="instance.disableRegistration || instance.federation !== 'all'" :class="$style.mainWarn" class="_gaps_s">
|
||||||
<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
|
<MkInfo v-if="instance.disableRegistration" warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
|
||||||
|
<MkInfo v-if="instance.federation === 'specified'" warn>{{ i18n.ts.federationSpecified }}</MkInfo>
|
||||||
|
<MkInfo v-else-if="instance.federation === 'none'" warn>{{ i18n.ts.federationDisabled }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_s" :class="$style.mainActions">
|
<div class="_gaps_s" :class="$style.mainActions">
|
||||||
<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
|
<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
|
||||||
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<header :class="$style.editHeader">
|
<header :class="$style.editHeader">
|
||||||
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--MI-margin)" data-cy-widget-select>
|
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--MI-margin)" data-cy-widget-select>
|
||||||
<template #label>{{ i18n.ts.selectWidget }}</template>
|
<template #label>{{ i18n.ts.selectWidget }}</template>
|
||||||
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
|
<option v-for="widget in _widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
|
<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
|
||||||
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
</Sortable>
|
</Sortable>
|
||||||
</template>
|
</template>
|
||||||
<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
<component :is="`widget-${widget.name}`" v-for="widget in _widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -50,13 +50,14 @@ export type DefaultStoredWidget = {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue';
|
import { defineAsyncComponent, ref, computed } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { widgets as widgetDefs } from '@/widgets/index.js';
|
import { widgets as widgetDefs, federationWidgets } from '@/widgets/index.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
|
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||||
@ -66,6 +67,16 @@ const props = defineProps<{
|
|||||||
edit: boolean;
|
edit: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const _widgetDefs = computed(() => {
|
||||||
|
if (instance.federation === 'none') {
|
||||||
|
return widgetDefs.filter(x => !federationWidgets.includes(x));
|
||||||
|
} else {
|
||||||
|
return widgetDefs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const _widgets = computed(() => props.widgets.filter(x => _widgetDefs.value.includes(x.name)));
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'updateWidgets', widgets: Widget[]): void;
|
(ev: 'updateWidgets', widgets: Widget[]): void;
|
||||||
(ev: 'addWidget', widget: Widget): void;
|
(ev: 'addWidget', widget: Widget): void;
|
||||||
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
||||||
<XEmojis/>
|
<XEmojis/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-else-if="instance.federation !== 'none' && tab === 'federation'" :contentMax="1000" :marginMin="20">
|
||||||
<XFederation/>
|
<XFederation/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
||||||
@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
@ -51,22 +52,34 @@ watch(tab, () => {
|
|||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
items.push({
|
||||||
key: 'overview',
|
key: 'overview',
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.overview,
|
||||||
}, {
|
}, {
|
||||||
key: 'emojis',
|
key: 'emojis',
|
||||||
title: i18n.ts.customEmojis,
|
title: i18n.ts.customEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
}, {
|
});
|
||||||
|
|
||||||
|
if (instance.federation !== 'none') {
|
||||||
|
items.push({
|
||||||
key: 'federation',
|
key: 'federation',
|
||||||
title: i18n.ts.federation,
|
title: i18n.ts.federation,
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
}, {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
key: 'charts',
|
key: 'charts',
|
||||||
title: i18n.ts.charts,
|
title: i18n.ts.charts,
|
||||||
icon: 'ti ti-chart-line',
|
icon: 'ti ti-chart-line',
|
||||||
}]);
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
title: i18n.ts.instanceInfo,
|
title: i18n.ts.instanceInfo,
|
||||||
|
@ -6,9 +6,9 @@ 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 :contentMax="700">
|
<MkSpacer :contentMax="1200">
|
||||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'search'" key="search">
|
<div v-if="tab === 'search'" key="search" :class="$style.searchRoot">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
@ -27,23 +27,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="tab === 'featured'" key="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"/>
|
<div :class="$style.root">
|
||||||
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'" key="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"/>
|
<div :class="$style.root">
|
||||||
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'following'" key="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"/>
|
<div :class="$style.root">
|
||||||
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'owned'" key="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"/>
|
<div :class="$style.root">
|
||||||
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</MkHorizontalSwipe>
|
</MkHorizontalSwipe>
|
||||||
@ -85,6 +93,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const featuredPagination = {
|
const featuredPagination = {
|
||||||
endpoint: 'channels/featured' as const,
|
endpoint: 'channels/featured' as const,
|
||||||
|
limit: 10,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
};
|
||||||
const favoritesPagination = {
|
const favoritesPagination = {
|
||||||
@ -157,3 +166,17 @@ definePageMetadata(() => ({
|
|||||||
icon: 'ti ti-device-tv',
|
icon: 'ti ti-device-tv',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.searchRoot {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||||
|
gap: var(--MI-margin);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -46,9 +46,10 @@ import { clipsCache } from '@/cache.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';
|
||||||
import { genEmbedCode } from '@/scripts/get-embed-code.js';
|
import { genEmbedCode } from '@/scripts/get-embed-code.js';
|
||||||
import { getServerContext } from '@/server-context.js';
|
import { assertServerContext, serverContext } from '@/server-context.js';
|
||||||
|
|
||||||
const CTX_CLIP = getServerContext('clip');
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
|
const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
clipId: string,
|
clipId: string,
|
||||||
|
@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="ldhfsamy">
|
<div class="ldhfsamy">
|
||||||
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
|
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
|
||||||
<img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
|
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="name _monospace">{{ emoji.name }}</div>
|
<div class="name _monospace">{{ emoji.name }}</div>
|
||||||
<div class="info">{{ emoji.category }}</div>
|
<div class="info">{{ emoji.category }}</div>
|
||||||
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="ldhfsamy">
|
<div class="ldhfsamy">
|
||||||
<div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
|
<div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
|
||||||
<img :src="`/emoji/${emoji.name}@${emoji.host}.webp`" class="img" :alt="emoji.name"/>
|
<img :src="getProxiedImageUrl(emoji.url, 'emoji')" class="img" :alt="emoji.name"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="name _monospace">{{ emoji.name }}</div>
|
<div class="name _monospace">{{ emoji.name }}</div>
|
||||||
<div class="info">{{ emoji.host }}</div>
|
<div class="info">{{ emoji.host }}</div>
|
||||||
@ -83,6 +83,7 @@ import FormSplit from '@/components/form/split.vue';
|
|||||||
import { selectFile } from '@/scripts/select-file.js';
|
import { selectFile } from '@/scripts/select-file.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';
|
||||||
|
import { getProxiedImageUrl } from '@/scripts/media-proxy.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';
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
|
|||||||
rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? props.emoji.url : null);
|
||||||
|
|
||||||
async function changeImage(ev: Event) {
|
async function changeImage(ev: Event) {
|
||||||
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
|
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
|
||||||
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="1200">
|
<MkSpacer :contentMax="1200">
|
||||||
<MkTab v-model="origin" style="margin-bottom: var(--MI-margin);">
|
<MkTab v-if="instance.federation !== 'none'" v-model="origin" style="margin-bottom: var(--MI-margin);">
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
@ -69,6 +69,7 @@ import MkUserList from '@/components/MkUserList.vue';
|
|||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
import MkTab from '@/components/MkTab.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -63,9 +63,11 @@ import { dateString } from '@/filters/date.js';
|
|||||||
import MkClipPreview from '@/components/MkClipPreview.vue';
|
import MkClipPreview from '@/components/MkClipPreview.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import { getServerContext } from '@/server-context.js';
|
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||||
|
import { $i } from '@/account.js';
|
||||||
|
|
||||||
const CTX_NOTE = getServerContext('note');
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
|
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
|
@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header>{{ i18n.ts.options }}</template>
|
<template #header>{{ i18n.ts.options }}</template>
|
||||||
|
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
|
<template v-if="instance.federation !== 'none'">
|
||||||
<MkRadios v-model="hostSelect">
|
<MkRadios v-model="hostSelect">
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
<option value="all" default>{{ i18n.ts.all }}</option>
|
<option value="all" default>{{ i18n.ts.all }}</option>
|
||||||
@ -22,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkInput v-if="noteSearchableScope === 'global'" v-model="hostInput" :disabled="hostSelect !== 'specified'" :large="true" type="search">
|
<MkInput v-if="noteSearchableScope === 'global'" v-model="hostInput" :disabled="hostSelect !== 'specified'" :large="true" type="search">
|
||||||
<template #prefix><i class="ti ti-server"></i></template>
|
<template #prefix><i class="ti ti-server"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts.specifyUser }}</template>
|
<template #label>{{ i18n.ts.specifyUser }}</template>
|
||||||
@ -102,7 +104,7 @@ setHostSelectWithInput(hostInput.value, undefined);
|
|||||||
watch(hostInput, setHostSelectWithInput);
|
watch(hostInput, setHostSelectWithInput);
|
||||||
|
|
||||||
const searchHost = computed(() => {
|
const searchHost = computed(() => {
|
||||||
if (hostSelect.value === 'local') return '.';
|
if (hostSelect.value === 'local' || instance.federation === 'none') return '.';
|
||||||
if (hostSelect.value === 'specified') return hostInput.value;
|
if (hostSelect.value === 'specified') return hostInput.value;
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkRadios v-model="searchOrigin" @update:modelValue="search()">
|
<MkRadios v-if="instance.federation !== 'none'" v-model="searchOrigin" @update:modelValue="search()">
|
||||||
<option value="combined">{{ i18n.ts.all }}</option>
|
<option value="combined">{{ i18n.ts.all }}</option>
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
@ -33,6 +33,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 { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
@ -113,7 +114,7 @@ async function search() {
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
query: query,
|
query: query,
|
||||||
origin: searchOrigin.value,
|
origin: instance.federation === 'none' ? 'local' : searchOrigin.value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkSelect v-model="instanceTicker">
|
<MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker">
|
||||||
<template #label>{{ i18n.ts.instanceTicker }}</template>
|
<template #label>{{ i18n.ts.instanceTicker }}</template>
|
||||||
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
||||||
<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
|
<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
|
||||||
@ -263,6 +263,7 @@ import MkLink from '@/components/MkLink.vue';
|
|||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
|
<XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder>
|
<MkFolder v-if="instance.federation !== 'none'">
|
||||||
<template #icon><i class="ti ti-planet-off"></i></template>
|
<template #icon><i class="ti ti-planet-off"></i></template>
|
||||||
<template #label>{{ i18n.ts.instanceMute }}</template>
|
<template #label>{{ i18n.ts.instanceMute }}</template>
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.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 { infoImageUrl } from '@/instance.js';
|
import { instance, infoImageUrl } from '@/instance.js';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
|
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
|
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
|
||||||
</template>
|
</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
|
<div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||||
</template>
|
</template>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
|
<div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||||
</template>
|
</template>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</div>
|
</div>
|
||||||
@ -167,6 +167,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
|||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
@ -219,7 +220,7 @@ watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function update_requireSigninToViewContents(value: boolean) {
|
async function update_requireSigninToViewContents(value: boolean) {
|
||||||
if (value) {
|
if (value === true && instance.federation !== 'none') {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts.acknowledgeNotesAndEnable,
|
text: i18n.ts.acknowledgeNotesAndEnable,
|
||||||
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSelect v-model="statusbar.type" placeholder="Please select">
|
<MkSelect v-model="statusbar.type" placeholder="Please select">
|
||||||
<template #label>{{ i18n.ts.type }}</template>
|
<template #label>{{ i18n.ts.type }}</template>
|
||||||
<option value="rss">RSS</option>
|
<option value="rss">RSS</option>
|
||||||
<option value="federation">Federation</option>
|
<option v-if="instance.federation !== 'none'" value="federation">Federation</option>
|
||||||
<option value="userList">User list timeline</option>
|
<option value="userList">User list timeline</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
@ -96,6 +96,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { deepClone } from '@/scripts/clone.js';
|
import { deepClone } from '@/scripts/clone.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -39,7 +39,7 @@ 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';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { getServerContext } from '@/server-context.js';
|
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||||
|
|
||||||
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'));
|
||||||
@ -53,7 +53,8 @@ const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
|
|||||||
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
|
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
|
||||||
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
|
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
|
||||||
|
|
||||||
const CTX_USER = getServerContext('user');
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
|
const CTX_USER = !$i && assertServerContext(serverContext, 'user') ? serverContext.user : null;
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
acct: string;
|
acct: string;
|
||||||
|
@ -33,7 +33,43 @@ export async function lookup(router?: Router) {
|
|||||||
uri: query,
|
uri: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
os.promiseDialog(promise, null, (err) => {
|
||||||
|
let title = i18n.ts.somethingHappened;
|
||||||
|
let text = err.message + '\n' + err.id;
|
||||||
|
|
||||||
|
switch (err.id) {
|
||||||
|
case '974b799e-1a29-4889-b706-18d4dd93e266':
|
||||||
|
title = i18n.ts._remoteLookupErrors._federationNotAllowed.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._federationNotAllowed.description;
|
||||||
|
break;
|
||||||
|
case '1a5eab56-e47b-48c2-8d5e-217b897d70db':
|
||||||
|
title = i18n.ts._remoteLookupErrors._uriInvalid.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._uriInvalid.description;
|
||||||
|
break;
|
||||||
|
case '81b539cf-4f57-4b29-bc98-032c33c0792e':
|
||||||
|
title = i18n.ts._remoteLookupErrors._requestFailed.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._requestFailed.description;
|
||||||
|
break;
|
||||||
|
case '70193c39-54f3-4813-82f0-70a680f7495b':
|
||||||
|
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._responseInvalid.description;
|
||||||
|
break;
|
||||||
|
case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a':
|
||||||
|
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description;
|
||||||
|
break;
|
||||||
|
case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
|
||||||
|
title = i18n.ts._remoteLookupErrors._noSuchObject.title;
|
||||||
|
text = i18n.ts._remoteLookupErrors._noSuchObject.description;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
}, i18n.ts.fetchingAsApObject);
|
||||||
|
|
||||||
const res = await promise;
|
const res = await promise;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
|
|
||||||
@ -51,10 +52,17 @@ export function pleaseLogin(opts: {
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
if ($i) return;
|
if ($i) return;
|
||||||
|
|
||||||
|
let _openOnRemote: OpenOnRemoteOptions | undefined = undefined;
|
||||||
|
|
||||||
|
// 連合できる場合と、(連合ができなくても)共有する場合は外部連携オプションを設定
|
||||||
|
if (opts.openOnRemote != null && (instance.federation !== 'none' || opts.openOnRemote.type === 'share')) {
|
||||||
|
_openOnRemote = opts.openOnRemote;
|
||||||
|
}
|
||||||
|
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
|
||||||
autoSet: true,
|
autoSet: true,
|
||||||
message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
|
message: opts.message ?? (_openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
|
||||||
openOnRemote: opts.openOnRemote,
|
openOnRemote: _openOnRemote,
|
||||||
}, {
|
}, {
|
||||||
cancelled: () => {
|
cancelled: () => {
|
||||||
if (opts.path) {
|
if (opts.path) {
|
||||||
|
@ -2,22 +2,20 @@
|
|||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { $i } from '@/account.js';
|
|
||||||
|
|
||||||
const providedContextEl = document.getElementById('misskey_clientCtx');
|
const providedContextEl = document.getElementById('misskey_clientCtx');
|
||||||
|
|
||||||
export type ServerContext = {
|
export type ServerContext = {
|
||||||
clip?: Misskey.entities.Clip;
|
clip?: Misskey.entities.Clip;
|
||||||
note?: Misskey.entities.Note;
|
note?: Misskey.entities.Note;
|
||||||
user?: Misskey.entities.UserLite;
|
user?: Misskey.entities.UserDetailed;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
|
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
|
||||||
|
|
||||||
export function getServerContext<K extends keyof NonNullable<ServerContext>>(entity: K): Required<Pick<NonNullable<ServerContext>, K>> | null {
|
export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
|
||||||
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
if (ctx == null) return false;
|
||||||
if ($i) return null;
|
return entity in ctx && ctx[entity] != null;
|
||||||
|
|
||||||
return serverContext ? (serverContext[entity] ?? null) : null;
|
|
||||||
}
|
}
|
||||||
|
@ -56,12 +56,18 @@ export function openInstanceMenu(ev: MouseEvent) {
|
|||||||
text: i18n.ts.customEmojis,
|
text: i18n.ts.customEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
to: '/about#emojis',
|
to: '/about#emojis',
|
||||||
}, {
|
});
|
||||||
|
|
||||||
|
if (instance.federation !== 'none') {
|
||||||
|
menuItems.push({
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: i18n.ts.federation,
|
text: i18n.ts.federation,
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
to: '/about#federation',
|
to: '/about#federation',
|
||||||
}, {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.push({
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: i18n.ts.charts,
|
text: i18n.ts.charts,
|
||||||
icon: 'ti ti-chart-line',
|
icon: 'ti ti-chart-line',
|
||||||
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
>
|
>
|
||||||
<span :class="$style.name">{{ x.name }}</span>
|
<span :class="$style.name">{{ x.name }}</span>
|
||||||
<XRss v-if="x.type === 'rss'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/>
|
<XRss v-if="x.type === 'rss'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/>
|
||||||
<XFederation v-else-if="x.type === 'federation'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
|
<XFederation v-else-if="x.type === 'federation' && instance.federation !== 'none'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
|
||||||
<XUserList v-else-if="x.type === 'userList'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :userListId="x.props.userListId"/>
|
<XUserList v-else-if="x.type === 'userList'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :userListId="x.props.userListId"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
const XRss = defineAsyncComponent(() => import('./statusbar-rss.vue'));
|
const XRss = defineAsyncComponent(() => import('./statusbar-rss.vue'));
|
||||||
const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vue'));
|
const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vue'));
|
||||||
|
@ -36,6 +36,12 @@ export default function(app: App) {
|
|||||||
app.component('WidgetBirthdayFollowings', defineAsyncComponent(() => import('./WidgetBirthdayFollowings.vue')));
|
app.component('WidgetBirthdayFollowings', defineAsyncComponent(() => import('./WidgetBirthdayFollowings.vue')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 連合関連のウィジェット(連合無効時に隠す)
|
||||||
|
export const federationWidgets = [
|
||||||
|
'federation',
|
||||||
|
'instanceCloud',
|
||||||
|
];
|
||||||
|
|
||||||
export const widgets = [
|
export const widgets = [
|
||||||
'profile',
|
'profile',
|
||||||
'instanceInfo',
|
'instanceInfo',
|
||||||
@ -51,8 +57,6 @@ export const widgets = [
|
|||||||
'photos',
|
'photos',
|
||||||
'digitalClock',
|
'digitalClock',
|
||||||
'unixClock',
|
'unixClock',
|
||||||
'federation',
|
|
||||||
'instanceCloud',
|
|
||||||
'postForm',
|
'postForm',
|
||||||
'slideshow',
|
'slideshow',
|
||||||
'serverMetric',
|
'serverMetric',
|
||||||
@ -65,4 +69,6 @@ export const widgets = [
|
|||||||
'userList',
|
'userList',
|
||||||
'clicker',
|
'clicker',
|
||||||
'birthdayFollowings',
|
'birthdayFollowings',
|
||||||
|
|
||||||
|
...federationWidgets,
|
||||||
];
|
];
|
||||||
|
@ -5029,6 +5029,8 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
noteSearchableScope: 'local' | 'global';
|
noteSearchableScope: 'local' | 'global';
|
||||||
maxFileSize: number;
|
maxFileSize: number;
|
||||||
|
/** @enum {string} */
|
||||||
|
federation: 'all' | 'specified' | 'none';
|
||||||
};
|
};
|
||||||
MetaDetailedOnly: {
|
MetaDetailedOnly: {
|
||||||
features?: {
|
features?: {
|
||||||
|
Loading…
Reference in New Issue
Block a user