mirror of
https://github.com/kokonect-link/cherrypick
synced 2024-11-23 14:46:44 +09:00
feat: 버블 타임라인 (kokonect-link/cherrypick#512, [TransFem-org/Sharkey#154](https://activitypub.software/TransFem-org/Sharkey/-/issues/154), [TransFem-org/Sharkey@2f99c7e9](2f99c7e9dc
))
- 관리자가 설정한 서버의 게시글만 볼 수 있는 타임라인으로, 글로벌 타임라인의 무분별한 내용을 포함하는 게시글을 제한하는 목적으로 사용할 수 있습니다.
This commit is contained in:
parent
eaaa470517
commit
91c07eb81d
@ -28,6 +28,10 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
|
|||||||
기반 Misskey 버전: 2024.x.x<br>
|
기반 Misskey 버전: 2024.x.x<br>
|
||||||
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오.
|
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오.
|
||||||
|
|
||||||
|
### General
|
||||||
|
- Feat: 버블 타임라인 (kokonect-link/cherrypick#512, [TransFem-org/Sharkey#154](https://activitypub.software/TransFem-org/Sharkey/-/issues/154), [TransFem-org/Sharkey@2f99c7e9](https://activitypub.software/TransFem-org/Sharkey/-/commit/2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310))
|
||||||
|
- 관리자가 설정한 서버의 게시글만 볼 수 있는 타임라인으로, 글로벌 타임라인의 무분별한 내용을 포함하는 게시글을 제한하는 목적으로 사용할 수 있습니다.
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: 미디어 그리드 레이아옷 조정
|
- Enhance: 미디어 그리드 레이아옷 조정
|
||||||
- 여러 장의 이미지가 있을 때 표시되는 아이콘을 보다 명확하게 볼 수 있도록 개선됨
|
- 여러 장의 이미지가 있을 때 표시되는 아이콘을 보다 명확하게 볼 수 있도록 개선됨
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
_lang_: "English"
|
_lang_: "English"
|
||||||
|
bubbleTimeline: "Bubble Timeline"
|
||||||
|
bubbleTimelineDescription: "After enabling this option navigate to the Moderation section to configure which servers should be shown."
|
||||||
|
bubbleInstancesDescription: "Set the host names of servers to be displayed in the bubble timeline, separated by line breaks."
|
||||||
selectReaction: "Select reactions to use with the Like button"
|
selectReaction: "Select reactions to use with the Like button"
|
||||||
disableRegistrationWhenInactive: "Disable new user registration when moderator is inactivated"
|
disableRegistrationWhenInactive: "Disable new user registration when moderator is inactivated"
|
||||||
disablePublicNoteWhenInactive: "Disable 'Can send public notes' when moderator is inactivated"
|
disablePublicNoteWhenInactive: "Disable 'Can send public notes' when moderator is inactivated"
|
||||||
@ -1667,6 +1670,7 @@ _timelineDescription:
|
|||||||
local: "In the Local timeline, you can see notes from all users on this server."
|
local: "In the Local timeline, you can see notes from all users on this server."
|
||||||
social: "The Social timeline displays notes from both the Home and Local timelines."
|
social: "The Social timeline displays notes from both the Home and Local timelines."
|
||||||
global: "In the Global timeline, you can see notes from all connected servers."
|
global: "In the Global timeline, you can see notes from all connected servers."
|
||||||
|
bubble: "In the Bubble timeline, you can see notes from servers set up by the administrator."
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
||||||
_event:
|
_event:
|
||||||
@ -2020,6 +2024,7 @@ _role:
|
|||||||
_options:
|
_options:
|
||||||
gtlAvailable: "Can view the global timeline"
|
gtlAvailable: "Can view the global timeline"
|
||||||
ltlAvailable: "Can view the local timeline"
|
ltlAvailable: "Can view the local timeline"
|
||||||
|
btlAvailable: "Can view the bubble timeline"
|
||||||
canPublicNote: "Can send public notes"
|
canPublicNote: "Can send public notes"
|
||||||
canEditNote: "Note editing"
|
canEditNote: "Note editing"
|
||||||
scheduleNoteMax: "Maximum number of scheduled notes"
|
scheduleNoteMax: "Maximum number of scheduled notes"
|
||||||
@ -2695,6 +2700,7 @@ _timelines:
|
|||||||
local: "Local"
|
local: "Local"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
|
bubble: "Bubble"
|
||||||
_play:
|
_play:
|
||||||
new: "Create Play"
|
new: "Create Play"
|
||||||
edit: "Edit Play"
|
edit: "Edit Play"
|
||||||
|
24
locales/index.d.ts
vendored
24
locales/index.d.ts
vendored
@ -13,6 +13,18 @@ export interface Locale extends ILocale {
|
|||||||
* 日本語
|
* 日本語
|
||||||
*/
|
*/
|
||||||
"_lang_": string;
|
"_lang_": string;
|
||||||
|
/**
|
||||||
|
* バブルタイムライン
|
||||||
|
*/
|
||||||
|
"bubbleTimeline": string;
|
||||||
|
/**
|
||||||
|
* このオプションを有効にし、モデレーションに移動して、表示するサーバを設定します。
|
||||||
|
*/
|
||||||
|
"bubbleTimelineDescription": string;
|
||||||
|
/**
|
||||||
|
* バブルタイムラインに表示するサーバのホスト名を改行で区切って設定します。
|
||||||
|
*/
|
||||||
|
"bubbleInstancesDescription": string;
|
||||||
/**
|
/**
|
||||||
* いいねボタンで使うリアクションを選択
|
* いいねボタンで使うリアクションを選択
|
||||||
*/
|
*/
|
||||||
@ -6639,6 +6651,10 @@ export interface Locale extends ILocale {
|
|||||||
* グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
|
* グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
|
||||||
*/
|
*/
|
||||||
"global": string;
|
"global": string;
|
||||||
|
/**
|
||||||
|
* バブルタイムラインでは、管理者が設定したサーバーの投稿を見ることができます。
|
||||||
|
*/
|
||||||
|
"bubble": string;
|
||||||
};
|
};
|
||||||
"_serverRules": {
|
"_serverRules": {
|
||||||
/**
|
/**
|
||||||
@ -7898,6 +7914,10 @@ export interface Locale extends ILocale {
|
|||||||
* ローカルタイムラインの閲覧
|
* ローカルタイムラインの閲覧
|
||||||
*/
|
*/
|
||||||
"ltlAvailable": string;
|
"ltlAvailable": string;
|
||||||
|
/**
|
||||||
|
* バブルタイムラインの閲覧
|
||||||
|
*/
|
||||||
|
"btlAvailable": string;
|
||||||
/**
|
/**
|
||||||
* パブリック投稿の許可
|
* パブリック投稿の許可
|
||||||
*/
|
*/
|
||||||
@ -10503,6 +10523,10 @@ export interface Locale extends ILocale {
|
|||||||
* グローバル
|
* グローバル
|
||||||
*/
|
*/
|
||||||
"global": string;
|
"global": string;
|
||||||
|
/**
|
||||||
|
* バブル
|
||||||
|
*/
|
||||||
|
"bubble": string;
|
||||||
};
|
};
|
||||||
"_play": {
|
"_play": {
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
_lang_: "日本語"
|
_lang_: "日本語"
|
||||||
|
|
||||||
|
bubbleTimeline: "バブルタイムライン"
|
||||||
|
bubbleTimelineDescription: "このオプションを有効にし、モデレーションに移動して、表示するサーバを設定します。"
|
||||||
|
bubbleInstancesDescription: "バブルタイムラインに表示するサーバのホスト名を改行で区切って設定します。"
|
||||||
selectReaction: "いいねボタンで使うリアクションを選択"
|
selectReaction: "いいねボタンで使うリアクションを選択"
|
||||||
disableRegistrationWhenInactive: "モデレーターが一定期間非アクティブになったとき、新規登録を無効化"
|
disableRegistrationWhenInactive: "モデレーターが一定期間非アクティブになったとき、新規登録を無効化"
|
||||||
disablePublicNoteWhenInactive: "モデレーターが一定期間非アクティブになったとき、「パブリック投稿の許可」を無効化"
|
disablePublicNoteWhenInactive: "モデレーターが一定期間非アクティブになったとき、「パブリック投稿の許可」を無効化"
|
||||||
@ -1684,6 +1687,7 @@ _timelineDescription:
|
|||||||
local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
|
local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
|
||||||
social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
||||||
global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
|
global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
|
||||||
|
bubble: "バブルタイムラインでは、管理者が設定したサーバーの投稿を見ることができます。"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||||
@ -2045,6 +2049,7 @@ _role:
|
|||||||
_options:
|
_options:
|
||||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||||
|
btlAvailable: "バブルタイムラインの閲覧"
|
||||||
canPublicNote: "パブリック投稿の許可"
|
canPublicNote: "パブリック投稿の許可"
|
||||||
canEditNote: "ノートの編集"
|
canEditNote: "ノートの編集"
|
||||||
scheduleNoteMax: "予約投稿の最大数"
|
scheduleNoteMax: "予約投稿の最大数"
|
||||||
@ -2764,6 +2769,7 @@ _timelines:
|
|||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
bubble: "バブル"
|
||||||
|
|
||||||
_play:
|
_play:
|
||||||
new: "Playの作成"
|
new: "Playの作成"
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
_lang_: "한국어"
|
_lang_: "한국어"
|
||||||
|
bubbleTimeline: "버블 타임라인"
|
||||||
|
bubbleTimelineDescription: "이 옵션을 활성화하고 모더레이션으로 이동해 버블 타임라인에 표시할 서버를 구성해 주세요."
|
||||||
|
bubbleInstancesDescription: "버블 타임라인에 표시할 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정해요."
|
||||||
selectReaction: "'좋아요!' 버튼으로 사용할 리액션 선택"
|
selectReaction: "'좋아요!' 버튼으로 사용할 리액션 선택"
|
||||||
disableRegistrationWhenInactive: "모더레이터 부재 시 신규 회원가입 비활성화"
|
disableRegistrationWhenInactive: "모더레이터 부재 시 신규 회원가입 비활성화"
|
||||||
disablePublicNoteWhenInactive: "모더레이터 부재 시 '공개 노트 허용' 비활성화"
|
disablePublicNoteWhenInactive: "모더레이터 부재 시 '공개 노트 허용' 비활성화"
|
||||||
@ -1668,6 +1671,7 @@ _timelineDescription:
|
|||||||
local: "로컬 타임라인에서는, 이 서버의 모든 사용자의 게시물을 볼 수 있어요."
|
local: "로컬 타임라인에서는, 이 서버의 모든 사용자의 게시물을 볼 수 있어요."
|
||||||
social: "소셜 타임라인에서는, 홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있어요."
|
social: "소셜 타임라인에서는, 홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있어요."
|
||||||
global: "글로벌 타임라인에서는, 여기와 연결된 다른 모든 서버의 게시물을 볼 수 있어요."
|
global: "글로벌 타임라인에서는, 여기와 연결된 다른 모든 서버의 게시물을 볼 수 있어요."
|
||||||
|
bubble: "버블 타임라인에서는, 관리자가 설정한 서버의 게시물을 볼 수 있어요."
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "회원 가입 이전에 간단하게 표시할 서버 규칙이에요. 이용 약관의 요약으로 구성하는 것을 추천해요."
|
description: "회원 가입 이전에 간단하게 표시할 서버 규칙이에요. 이용 약관의 요약으로 구성하는 것을 추천해요."
|
||||||
_event:
|
_event:
|
||||||
@ -2023,6 +2027,7 @@ _role:
|
|||||||
_options:
|
_options:
|
||||||
gtlAvailable: "글로벌 타임라인 보이기"
|
gtlAvailable: "글로벌 타임라인 보이기"
|
||||||
ltlAvailable: "로컬 타임라인 보이기"
|
ltlAvailable: "로컬 타임라인 보이기"
|
||||||
|
btlAvailable: "버블 타임라인 보이기"
|
||||||
canPublicNote: "공개 노트 허용"
|
canPublicNote: "공개 노트 허용"
|
||||||
canEditNote: "노트 편집 허용"
|
canEditNote: "노트 편집 허용"
|
||||||
scheduleNoteMax: "게시를 예약한 노트의 최대 수"
|
scheduleNoteMax: "게시를 예약한 노트의 최대 수"
|
||||||
@ -2698,6 +2703,7 @@ _timelines:
|
|||||||
local: "로컬"
|
local: "로컬"
|
||||||
social: "소셜"
|
social: "소셜"
|
||||||
global: "글로벌"
|
global: "글로벌"
|
||||||
|
bubble: "버블"
|
||||||
_play:
|
_play:
|
||||||
new: "Play 만들기"
|
new: "Play 만들기"
|
||||||
edit: "Play 편집하기"
|
edit: "Play 편집하기"
|
||||||
|
16
packages/backend/migration/1701647674000-BubbleInstances.js
Normal file
16
packages/backend/migration/1701647674000-BubbleInstances.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: marie and other Sharkey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class BubbleInstances1701647674000 {
|
||||||
|
name = 'BubbleInstances1701647674000'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "bubbleInstances" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bubbleInstances"`);
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
|||||||
export type RolePolicies = {
|
export type RolePolicies = {
|
||||||
gtlAvailable: boolean;
|
gtlAvailable: boolean;
|
||||||
ltlAvailable: boolean;
|
ltlAvailable: boolean;
|
||||||
|
btlAvailable: boolean;
|
||||||
canPublicNote: boolean;
|
canPublicNote: boolean;
|
||||||
canEditNote: boolean;
|
canEditNote: boolean;
|
||||||
scheduleNoteMax: number;
|
scheduleNoteMax: number;
|
||||||
@ -71,6 +72,7 @@ export type RolePolicies = {
|
|||||||
export const DEFAULT_POLICIES: RolePolicies = {
|
export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
gtlAvailable: true,
|
gtlAvailable: true,
|
||||||
ltlAvailable: true,
|
ltlAvailable: true,
|
||||||
|
btlAvailable: false,
|
||||||
canPublicNote: true,
|
canPublicNote: true,
|
||||||
canEditNote: true,
|
canEditNote: true,
|
||||||
scheduleNoteMax: 5,
|
scheduleNoteMax: 5,
|
||||||
@ -381,6 +383,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
return {
|
return {
|
||||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||||
|
btlAvailable: calc('btlAvailable', vs => vs.some(v => v === true)),
|
||||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||||
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
||||||
scheduleNoteMax: calc('scheduleNoteMax', vs => Math.max(...vs)),
|
scheduleNoteMax: calc('scheduleNoteMax', vs => Math.max(...vs)),
|
||||||
|
@ -823,4 +823,11 @@ export class MiMeta {
|
|||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
public disablePublicNoteWhenInactive: boolean;
|
public disablePublicNoteWhenInactive: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256,
|
||||||
|
array: true,
|
||||||
|
default: '{}',
|
||||||
|
})
|
||||||
|
public bubbleInstances: string[];
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ export class NodeinfoServerService {
|
|||||||
disableRegistration: meta.disableRegistration,
|
disableRegistration: meta.disableRegistration,
|
||||||
disableLocalTimeline: !basePolicies.ltlAvailable,
|
disableLocalTimeline: !basePolicies.ltlAvailable,
|
||||||
disableGlobalTimeline: !basePolicies.gtlAvailable,
|
disableGlobalTimeline: !basePolicies.gtlAvailable,
|
||||||
|
disableBubbleTimeline: !basePolicies.btlAvailable,
|
||||||
emailRequiredForSignup: meta.emailRequiredForSignup,
|
emailRequiredForSignup: meta.emailRequiredForSignup,
|
||||||
enableHcaptcha: meta.enableHcaptcha,
|
enableHcaptcha: meta.enableHcaptcha,
|
||||||
enableRecaptcha: meta.enableRecaptcha,
|
enableRecaptcha: meta.enableRecaptcha,
|
||||||
|
@ -49,6 +49,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
|
|||||||
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
||||||
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
||||||
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
|
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
|
||||||
|
import { BubbleTimelineChannelService } from './api/stream/channels/bubble-timeline.js';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -98,6 +99,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
|||||||
UserListChannelService,
|
UserListChannelService,
|
||||||
OpenApiServerService,
|
OpenApiServerService,
|
||||||
OAuth2ProviderService,
|
OAuth2ProviderService,
|
||||||
|
BubbleTimelineChannelService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ServerService,
|
ServerService,
|
||||||
|
@ -303,6 +303,7 @@ import * as ep___notes_update from './endpoints/notes/update.js';
|
|||||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||||
|
import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
|
||||||
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
||||||
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
||||||
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
||||||
@ -726,6 +727,7 @@ const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___not
|
|||||||
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
|
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
|
||||||
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
|
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
|
||||||
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
|
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
|
||||||
|
const $notes_bubbleTimeline: Provider = { provide: 'ep:notes/bubble-timeline', useClass: ep___notes_bubbleTimeline.default };
|
||||||
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
|
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
|
||||||
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
|
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
|
||||||
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
|
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
|
||||||
@ -1154,6 +1156,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$notes_favorites_create,
|
$notes_favorites_create,
|
||||||
$notes_favorites_delete,
|
$notes_favorites_delete,
|
||||||
$notes_featured,
|
$notes_featured,
|
||||||
|
$notes_bubbleTimeline,
|
||||||
$notes_globalTimeline,
|
$notes_globalTimeline,
|
||||||
$notes_hybridTimeline,
|
$notes_hybridTimeline,
|
||||||
$notes_localTimeline,
|
$notes_localTimeline,
|
||||||
@ -1573,6 +1576,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$notes_favorites_create,
|
$notes_favorites_create,
|
||||||
$notes_favorites_delete,
|
$notes_favorites_delete,
|
||||||
$notes_featured,
|
$notes_featured,
|
||||||
|
$notes_bubbleTimeline,
|
||||||
$notes_globalTimeline,
|
$notes_globalTimeline,
|
||||||
$notes_hybridTimeline,
|
$notes_hybridTimeline,
|
||||||
$notes_localTimeline,
|
$notes_localTimeline,
|
||||||
|
@ -308,6 +308,7 @@ import * as ep___notes_update from './endpoints/notes/update.js';
|
|||||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||||
|
import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
|
||||||
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
||||||
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
||||||
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
||||||
@ -729,6 +730,7 @@ const eps = [
|
|||||||
['notes/favorites/create', ep___notes_favorites_create],
|
['notes/favorites/create', ep___notes_favorites_create],
|
||||||
['notes/favorites/delete', ep___notes_favorites_delete],
|
['notes/favorites/delete', ep___notes_favorites_delete],
|
||||||
['notes/featured', ep___notes_featured],
|
['notes/featured', ep___notes_featured],
|
||||||
|
['notes/bubble-timeline', ep___notes_bubbleTimeline],
|
||||||
['notes/global-timeline', ep___notes_globalTimeline],
|
['notes/global-timeline', ep___notes_globalTimeline],
|
||||||
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
||||||
['notes/local-timeline', ep___notes_localTimeline],
|
['notes/local-timeline', ep___notes_localTimeline],
|
||||||
|
@ -625,6 +625,13 @@ export const meta = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
bubbleInstances: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@ -798,6 +805,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
customSplashText: instance.customSplashText,
|
customSplashText: instance.customSplashText,
|
||||||
disableRegistrationWhenInactive: instance.disableRegistrationWhenInactive,
|
disableRegistrationWhenInactive: instance.disableRegistrationWhenInactive,
|
||||||
disablePublicNoteWhenInactive: instance.disablePublicNoteWhenInactive,
|
disablePublicNoteWhenInactive: instance.disablePublicNoteWhenInactive,
|
||||||
|
bubbleInstances: instance.bubbleInstances,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,7 @@ export const paramDef = {
|
|||||||
},
|
},
|
||||||
disableRegistrationWhenInactive: { type: 'boolean', nullable: true },
|
disableRegistrationWhenInactive: { type: 'boolean', nullable: true },
|
||||||
disablePublicNoteWhenInactive: { type: 'boolean', nullable: true },
|
disablePublicNoteWhenInactive: { type: 'boolean', nullable: true },
|
||||||
|
bubbleInstances: { type: 'array', items: { type: 'string' } },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
@ -833,6 +834,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
set.disablePublicNoteWhenInactive = ps.disablePublicNoteWhenInactive;
|
set.disablePublicNoteWhenInactive = ps.disablePublicNoteWhenInactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.bubbleInstances !== undefined) {
|
||||||
|
set.bubbleInstances = ps.bubbleInstances;
|
||||||
|
}
|
||||||
|
|
||||||
const before = await this.metaService.fetch(true);
|
const before = await this.metaService.fetch(true);
|
||||||
|
|
||||||
await this.metaService.update(set);
|
await this.metaService.update(set);
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Brackets } from 'typeorm';
|
||||||
|
import type { NotesRepository, MiMeta } from '@/models/_.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notes'],
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
btlDisabled: {
|
||||||
|
message: 'Bubble timeline has been disabled.',
|
||||||
|
code: 'BTL_DISABLED',
|
||||||
|
id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
withFiles: { type: 'boolean', default: false },
|
||||||
|
withCats: { type: 'boolean', default: false },
|
||||||
|
withBots: { type: 'boolean', default: true },
|
||||||
|
withRenotes: { type: 'boolean', default: true },
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
sinceDate: { type: 'integer' },
|
||||||
|
untilDate: { type: 'integer' },
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
private noteEntityService: NoteEntityService,
|
||||||
|
private queryService: QueryService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private activeUsersChart: ActiveUsersChart,
|
||||||
|
private cacheService: CacheService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
|
||||||
|
if (!policies.btlAvailable) {
|
||||||
|
throw new ApiError(meta.errors.btlDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
followings,
|
||||||
|
] = me ? await Promise.all([
|
||||||
|
this.cacheService.userFollowingsCache.fetch(me.id),
|
||||||
|
]) : [undefined];
|
||||||
|
|
||||||
|
//#region Construct query
|
||||||
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||||
|
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
|
.andWhere('note.visibility = \'public\'')
|
||||||
|
.andWhere('note.channelId IS NULL')
|
||||||
|
.andWhere('note.userHost IN (:...hosts)', { hosts: this.serverSettings.bubbleInstances })
|
||||||
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
|
.leftJoinAndSelect('note.renote', 'renote')
|
||||||
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
|
if (me) {
|
||||||
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.withFiles) {
|
||||||
|
query.andWhere('note.fileIds != \'{}\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.withRenotes === false) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.where('note.renoteId IS NULL');
|
||||||
|
qb.orWhere(new Brackets(qb => {
|
||||||
|
qb.where('note.text IS NOT NULL');
|
||||||
|
qb.orWhere('note.fileIds != \'{}\'');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.withCats) {
|
||||||
|
query.andWhere('(select "isCat" from "user" where id = note."userId")');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ps.withBots) {
|
||||||
|
query.andWhere('user.isBot = FALSE');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
let timeline = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
|
timeline = timeline.filter(note => {
|
||||||
|
return !(note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
if (me) {
|
||||||
|
this.activeUsersChart.read(me);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { HybridTimelineChannelService } from './channels/hybrid-timeline.js';
|
|||||||
import { LocalTimelineChannelService } from './channels/local-timeline.js';
|
import { LocalTimelineChannelService } from './channels/local-timeline.js';
|
||||||
import { HomeTimelineChannelService } from './channels/home-timeline.js';
|
import { HomeTimelineChannelService } from './channels/home-timeline.js';
|
||||||
import { GlobalTimelineChannelService } from './channels/global-timeline.js';
|
import { GlobalTimelineChannelService } from './channels/global-timeline.js';
|
||||||
|
import { BubbleTimelineChannelService } from './channels/bubble-timeline.js';
|
||||||
import { MainChannelService } from './channels/main.js';
|
import { MainChannelService } from './channels/main.js';
|
||||||
import { ChannelChannelService } from './channels/channel.js';
|
import { ChannelChannelService } from './channels/channel.js';
|
||||||
import { AdminChannelService } from './channels/admin.js';
|
import { AdminChannelService } from './channels/admin.js';
|
||||||
@ -33,6 +34,7 @@ export class ChannelsService {
|
|||||||
private localTimelineChannelService: LocalTimelineChannelService,
|
private localTimelineChannelService: LocalTimelineChannelService,
|
||||||
private hybridTimelineChannelService: HybridTimelineChannelService,
|
private hybridTimelineChannelService: HybridTimelineChannelService,
|
||||||
private globalTimelineChannelService: GlobalTimelineChannelService,
|
private globalTimelineChannelService: GlobalTimelineChannelService,
|
||||||
|
private bubbleTimelineChannelService: BubbleTimelineChannelService,
|
||||||
private userListChannelService: UserListChannelService,
|
private userListChannelService: UserListChannelService,
|
||||||
private hashtagChannelService: HashtagChannelService,
|
private hashtagChannelService: HashtagChannelService,
|
||||||
private roleTimelineChannelService: RoleTimelineChannelService,
|
private roleTimelineChannelService: RoleTimelineChannelService,
|
||||||
@ -57,6 +59,7 @@ export class ChannelsService {
|
|||||||
case 'localTimeline': return this.localTimelineChannelService;
|
case 'localTimeline': return this.localTimelineChannelService;
|
||||||
case 'hybridTimeline': return this.hybridTimelineChannelService;
|
case 'hybridTimeline': return this.hybridTimelineChannelService;
|
||||||
case 'globalTimeline': return this.globalTimelineChannelService;
|
case 'globalTimeline': return this.globalTimelineChannelService;
|
||||||
|
case 'bubbleTimeline': return this.bubbleTimelineChannelService;
|
||||||
case 'userList': return this.userListChannelService;
|
case 'userList': return this.userListChannelService;
|
||||||
case 'hashtag': return this.hashtagChannelService;
|
case 'hashtag': return this.hashtagChannelService;
|
||||||
case 'roleTimeline': return this.roleTimelineChannelService;
|
case 'roleTimeline': return this.roleTimelineChannelService;
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
|
import Channel, { MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
class BubbleTimelineChannel extends Channel {
|
||||||
|
public readonly chName = 'bubbleTimeline';
|
||||||
|
public static shouldShare = false;
|
||||||
|
public static requireCredential = false as const;
|
||||||
|
private withRenotes: boolean;
|
||||||
|
private withFiles: boolean;
|
||||||
|
private withCats: boolean;
|
||||||
|
private withBots: boolean;
|
||||||
|
private instance: MiMeta;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private metaService: MetaService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private noteEntityService: NoteEntityService,
|
||||||
|
|
||||||
|
id: string,
|
||||||
|
connection: Channel['connection'],
|
||||||
|
) {
|
||||||
|
super(id, connection);
|
||||||
|
//this.onNote = this.onNote.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async init(params: JsonObject) {
|
||||||
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
|
if (!policies.btlAvailable) return;
|
||||||
|
|
||||||
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
this.withCats = !!(params.withCats ?? false);
|
||||||
|
this.withBots = !!(params.withBots ?? true);
|
||||||
|
this.instance = await this.metaService.fetch();
|
||||||
|
|
||||||
|
// Subscribe events
|
||||||
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onNote(note: Packed<'Note'>) {
|
||||||
|
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
||||||
|
if (this.withCats && (note.user.isCat == null || note.user.isCat === false)) return;
|
||||||
|
if (!this.withBots && note.user.isBot) return;
|
||||||
|
|
||||||
|
if (!(note.user.host != null && this.instance.bubbleInstances.includes(note.user.host) && note.visibility === 'public' )) return;
|
||||||
|
|
||||||
|
if (note.channelId != null) return;
|
||||||
|
|
||||||
|
if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return;
|
||||||
|
|
||||||
|
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
|
||||||
|
|
||||||
|
if (this.isNoteMutedOrBlocked(note)) return;
|
||||||
|
|
||||||
|
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||||
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.cacheNote(note);
|
||||||
|
|
||||||
|
this.send('note', note);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose() {
|
||||||
|
// Unsubscribe events
|
||||||
|
this.subscriber.off('notesStream', this.onNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BubbleTimelineChannelService implements MiChannelService<false> {
|
||||||
|
public readonly shouldShare = BubbleTimelineChannel.shouldShare;
|
||||||
|
public readonly requireCredential = BubbleTimelineChannel.requireCredential;
|
||||||
|
public readonly kind = BubbleTimelineChannel.kind;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private metaService: MetaService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private noteEntityService: NoteEntityService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public create(id: string, connection: Channel['connection']): BubbleTimelineChannel {
|
||||||
|
return new BubbleTimelineChannel(
|
||||||
|
this.metaService,
|
||||||
|
this.roleService,
|
||||||
|
this.noteEntityService,
|
||||||
|
id,
|
||||||
|
connection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1726,6 +1726,8 @@ declare namespace entities {
|
|||||||
NotesFavoritesDeleteRequest,
|
NotesFavoritesDeleteRequest,
|
||||||
NotesFeaturedRequest,
|
NotesFeaturedRequest,
|
||||||
NotesFeaturedResponse,
|
NotesFeaturedResponse,
|
||||||
|
NotesBubbleTimelineRequest,
|
||||||
|
NotesBubbleTimelineResponse,
|
||||||
NotesGlobalTimelineRequest,
|
NotesGlobalTimelineRequest,
|
||||||
NotesGlobalTimelineResponse,
|
NotesGlobalTimelineResponse,
|
||||||
NotesHybridTimelineRequest,
|
NotesHybridTimelineRequest,
|
||||||
@ -2806,6 +2808,12 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type NoteReaction = components['schemas']['NoteReaction'];
|
type NoteReaction = components['schemas']['NoteReaction'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesBubbleTimelineRequest = operations['notes___bubble-timeline']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesBubbleTimelineResponse = operations['notes___bubble-timeline']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json'];
|
type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -3310,6 +3310,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
request<E extends 'notes/bubble-timeline', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -441,6 +441,8 @@ import type {
|
|||||||
NotesFavoritesDeleteRequest,
|
NotesFavoritesDeleteRequest,
|
||||||
NotesFeaturedRequest,
|
NotesFeaturedRequest,
|
||||||
NotesFeaturedResponse,
|
NotesFeaturedResponse,
|
||||||
|
NotesBubbleTimelineRequest,
|
||||||
|
NotesBubbleTimelineResponse,
|
||||||
NotesGlobalTimelineRequest,
|
NotesGlobalTimelineRequest,
|
||||||
NotesGlobalTimelineResponse,
|
NotesGlobalTimelineResponse,
|
||||||
NotesHybridTimelineRequest,
|
NotesHybridTimelineRequest,
|
||||||
@ -929,6 +931,7 @@ export type Endpoints = {
|
|||||||
'notes/favorites/create': { req: NotesFavoritesCreateRequest; res: EmptyResponse };
|
'notes/favorites/create': { req: NotesFavoritesCreateRequest; res: EmptyResponse };
|
||||||
'notes/favorites/delete': { req: NotesFavoritesDeleteRequest; res: EmptyResponse };
|
'notes/favorites/delete': { req: NotesFavoritesDeleteRequest; res: EmptyResponse };
|
||||||
'notes/featured': { req: NotesFeaturedRequest; res: NotesFeaturedResponse };
|
'notes/featured': { req: NotesFeaturedRequest; res: NotesFeaturedResponse };
|
||||||
|
'notes/bubble-timeline': { req: NotesBubbleTimelineRequest; res: NotesBubbleTimelineResponse };
|
||||||
'notes/global-timeline': { req: NotesGlobalTimelineRequest; res: NotesGlobalTimelineResponse };
|
'notes/global-timeline': { req: NotesGlobalTimelineRequest; res: NotesGlobalTimelineResponse };
|
||||||
'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
|
'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
|
||||||
'notes/local-timeline': { req: NotesLocalTimelineRequest; res: NotesLocalTimelineResponse };
|
'notes/local-timeline': { req: NotesLocalTimelineRequest; res: NotesLocalTimelineResponse };
|
||||||
|
@ -444,6 +444,8 @@ export type NotesFavoritesCreateRequest = operations['notes___favorites___create
|
|||||||
export type NotesFavoritesDeleteRequest = operations['notes___favorites___delete']['requestBody']['content']['application/json'];
|
export type NotesFavoritesDeleteRequest = operations['notes___favorites___delete']['requestBody']['content']['application/json'];
|
||||||
export type NotesFeaturedRequest = operations['notes___featured']['requestBody']['content']['application/json'];
|
export type NotesFeaturedRequest = operations['notes___featured']['requestBody']['content']['application/json'];
|
||||||
export type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json'];
|
export type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json'];
|
||||||
|
export type NotesBubbleTimelineRequest = operations['notes___bubble-timeline']['requestBody']['content']['application/json'];
|
||||||
|
export type NotesBubbleTimelineResponse = operations['notes___bubble-timeline']['responses']['200']['content']['application/json'];
|
||||||
export type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json'];
|
export type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json'];
|
||||||
export type NotesGlobalTimelineResponse = operations['notes___global-timeline']['responses']['200']['content']['application/json'];
|
export type NotesGlobalTimelineResponse = operations['notes___global-timeline']['responses']['200']['content']['application/json'];
|
||||||
export type NotesHybridTimelineRequest = operations['notes___hybrid-timeline']['requestBody']['content']['application/json'];
|
export type NotesHybridTimelineRequest = operations['notes___hybrid-timeline']['requestBody']['content']['application/json'];
|
||||||
|
@ -2860,6 +2860,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['notes___featured'];
|
post: operations['notes___featured'];
|
||||||
};
|
};
|
||||||
|
'/notes/bubble-timeline': {
|
||||||
|
/**
|
||||||
|
* notes/bubble-timeline
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
post: operations['notes___bubble-timeline'];
|
||||||
|
};
|
||||||
'/notes/global-timeline': {
|
'/notes/global-timeline': {
|
||||||
/**
|
/**
|
||||||
* notes/global-timeline
|
* notes/global-timeline
|
||||||
@ -5644,6 +5653,7 @@ export type operations = {
|
|||||||
customSplashText: string[];
|
customSplashText: string[];
|
||||||
disableRegistrationWhenInactive: boolean;
|
disableRegistrationWhenInactive: boolean;
|
||||||
disablePublicNoteWhenInactive: boolean;
|
disablePublicNoteWhenInactive: boolean;
|
||||||
|
bubbleInstances: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -10469,6 +10479,7 @@ export type operations = {
|
|||||||
customSplashText?: string[] | null;
|
customSplashText?: string[] | null;
|
||||||
disableRegistrationWhenInactive?: boolean | null;
|
disableRegistrationWhenInactive?: boolean | null;
|
||||||
disablePublicNoteWhenInactive?: boolean | null;
|
disablePublicNoteWhenInactive?: boolean | null;
|
||||||
|
bubbleInstances?: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -23305,6 +23316,74 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* notes/bubble-timeline
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
'notes___bubble-timeline': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** @default false */
|
||||||
|
withFiles?: boolean;
|
||||||
|
/** @default false */
|
||||||
|
withCats?: boolean;
|
||||||
|
/** @default true */
|
||||||
|
withBots?: boolean;
|
||||||
|
/** @default true */
|
||||||
|
withRenotes?: boolean;
|
||||||
|
/** @default 10 */
|
||||||
|
limit?: number;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
sinceId?: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
untilId?: string;
|
||||||
|
sinceDate?: number;
|
||||||
|
untilDate?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Note'][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* notes/global-timeline
|
* notes/global-timeline
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -78,6 +78,7 @@ export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as con
|
|||||||
export const ROLE_POLICIES = [
|
export const ROLE_POLICIES = [
|
||||||
'gtlAvailable',
|
'gtlAvailable',
|
||||||
'ltlAvailable',
|
'ltlAvailable',
|
||||||
|
'btlAvailable',
|
||||||
'canPublicNote',
|
'canPublicNote',
|
||||||
'canEditNote',
|
'canEditNote',
|
||||||
'scheduleNoteMax',
|
'scheduleNoteMax',
|
||||||
|
@ -135,6 +135,12 @@ function connectChannel() {
|
|||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
withCats: props.onlyCats,
|
withCats: props.onlyCats,
|
||||||
});
|
});
|
||||||
|
} else if (props.src === 'bubble') {
|
||||||
|
connection = stream.useChannel('bubbleTimeline', {
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
withCats: props.onlyCats,
|
||||||
|
});
|
||||||
} else if (props.src === 'mentions') {
|
} else if (props.src === 'mentions') {
|
||||||
connection = stream.useChannel('main');
|
connection = stream.useChannel('main');
|
||||||
connection.on('mention', prepend);
|
connection.on('mention', prepend);
|
||||||
@ -212,6 +218,13 @@ function updatePaginationQuery() {
|
|||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
withCats: props.onlyCats,
|
withCats: props.onlyCats,
|
||||||
};
|
};
|
||||||
|
} else if (props.src === 'bubble') {
|
||||||
|
endpoint = 'notes/bubble-timeline';
|
||||||
|
query = {
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
withCats: props.onlyCats,
|
||||||
|
};
|
||||||
} else if (props.src === 'mentions') {
|
} else if (props.src === 'mentions') {
|
||||||
endpoint = 'notes/mentions';
|
endpoint = 'notes/mentions';
|
||||||
query = null;
|
query = null;
|
||||||
|
@ -13,8 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
|
<MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
|
||||||
<template #label>{{ i18n.ts._serverSettings.openRegistration }}</template>
|
<template #label>{{ i18n.ts._serverSettings.openRegistration }}</template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div v-if="(enableRegistration && disableRegistrationWhenInactive) || disableRegistrationWhenInactive">{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</div>
|
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.openRegistrationWarning }}</div>
|
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.openRegistrationWarning }}</div>
|
||||||
|
<div v-if="(enableRegistration && disableRegistrationWhenInactive) || disableRegistrationWhenInactive" style="margin-top: 8px;">{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</div>
|
||||||
</template>
|
</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
@ -139,6 +139,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
|
<MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-droplet"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.bubbleTimeline }}</template>
|
||||||
|
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkTextarea v-if="bubbleTimelineEnabled" v-model="bubbleTimeline">
|
||||||
|
<template #caption>{{ i18n.ts.bubbleInstancesDescription }}</template>
|
||||||
|
</MkTextarea>
|
||||||
|
<MkButton primary @click="save_bubbleTimeline">{{ i18n.ts.save }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
@ -175,6 +187,8 @@ const blockedHosts = ref<string>('');
|
|||||||
const silencedHosts = ref<string>('');
|
const silencedHosts = ref<string>('');
|
||||||
const mediaSilencedHosts = ref<string>('');
|
const mediaSilencedHosts = ref<string>('');
|
||||||
const trustedLinkUrlPatterns = ref<string>('');
|
const trustedLinkUrlPatterns = ref<string>('');
|
||||||
|
const bubbleTimelineEnabled = ref<boolean>(false);
|
||||||
|
const bubbleTimeline = ref<string>('');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
@ -191,6 +205,8 @@ async function init() {
|
|||||||
silencedHosts.value = meta.silencedHosts?.join('\n') ?? '';
|
silencedHosts.value = meta.silencedHosts?.join('\n') ?? '';
|
||||||
mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
|
mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
|
||||||
trustedLinkUrlPatterns.value = meta.trustedLinkUrlPatterns.join('\n');
|
trustedLinkUrlPatterns.value = meta.trustedLinkUrlPatterns.join('\n');
|
||||||
|
bubbleTimelineEnabled.value = meta.policies.btlAvailable;
|
||||||
|
bubbleTimeline.value = meta.bubbleInstances.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onChange_enableRegistration(value: boolean) {
|
async function onChange_enableRegistration(value: boolean) {
|
||||||
@ -307,6 +323,14 @@ function save_mediaSilencedHosts() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function save_bubbleTimeline() {
|
||||||
|
os.apiWithDialog('admin/update-meta', {
|
||||||
|
bubbleInstances: bubbleTimeline.value.split('\n') || [],
|
||||||
|
}).then(() => {
|
||||||
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
|
@ -140,6 +140,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.btlAvailable, 'btlAvailable'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.btlAvailable }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.btlAvailable.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.btlAvailable.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.btlAvailable)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.btlAvailable.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.btlAvailable.value" :disabled="role.policies.btlAvailable.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.btlAvailable.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
@ -43,6 +43,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.btlAvailable, 'btlAvailable'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.btlAvailable }}</template>
|
||||||
|
<template #suffix>{{ policies.btlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkInfo :warn="true">{{ i18n.ts.bubbleTimelineDescription }}</MkInfo>
|
||||||
|
<MkSwitch v-model="policies.btlAvailable">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
||||||
<template #suffix>{{ policies.canPublicNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.canPublicNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
@ -311,6 +322,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
|||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.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';
|
||||||
|
@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSwitch v-model="enableLocalTimeline"><i class="ti ti-planet"></i> {{ i18n.ts._timelines.local }}</MkSwitch>
|
<MkSwitch v-model="enableLocalTimeline"><i class="ti ti-planet"></i> {{ i18n.ts._timelines.local }}</MkSwitch>
|
||||||
<MkSwitch v-model="enableSocialTimeline"><i class="ti ti-universe"></i> {{ i18n.ts._timelines.social }}</MkSwitch>
|
<MkSwitch v-model="enableSocialTimeline"><i class="ti ti-universe"></i> {{ i18n.ts._timelines.social }}</MkSwitch>
|
||||||
<MkSwitch v-model="enableGlobalTimeline"><i class="ti ti-world"></i> {{ i18n.ts._timelines.global }}</MkSwitch>
|
<MkSwitch v-model="enableGlobalTimeline"><i class="ti ti-world"></i> {{ i18n.ts._timelines.global }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableBubbleTimeline"><i class="ti ti-droplet"></i> {{ i18n.ts._timelines.bubble }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ const enableHomeTimeline = computed(defaultStore.makeGetterSetter('enableHomeTim
|
|||||||
const enableLocalTimeline = computed(defaultStore.makeGetterSetter('enableLocalTimeline'));
|
const enableLocalTimeline = computed(defaultStore.makeGetterSetter('enableLocalTimeline'));
|
||||||
const enableSocialTimeline = computed(defaultStore.makeGetterSetter('enableSocialTimeline'));
|
const enableSocialTimeline = computed(defaultStore.makeGetterSetter('enableSocialTimeline'));
|
||||||
const enableGlobalTimeline = computed(defaultStore.makeGetterSetter('enableGlobalTimeline'));
|
const enableGlobalTimeline = computed(defaultStore.makeGetterSetter('enableGlobalTimeline'));
|
||||||
|
const enableBubbleTimeline = computed(defaultStore.makeGetterSetter('enableBubbleTimeline'));
|
||||||
const enableListTimeline = computed(defaultStore.makeGetterSetter('enableListTimeline'));
|
const enableListTimeline = computed(defaultStore.makeGetterSetter('enableListTimeline'));
|
||||||
const enableAntennaTimeline = computed(defaultStore.makeGetterSetter('enableAntennaTimeline'));
|
const enableAntennaTimeline = computed(defaultStore.makeGetterSetter('enableAntennaTimeline'));
|
||||||
const enableChannelTimeline = computed(defaultStore.makeGetterSetter('enableChannelTimeline'));
|
const enableChannelTimeline = computed(defaultStore.makeGetterSetter('enableChannelTimeline'));
|
||||||
|
@ -175,6 +175,7 @@ const enableHomeTimeline = ref(defaultStore.state.enableHomeTimeline);
|
|||||||
const enableLocalTimeline = ref(defaultStore.state.enableLocalTimeline);
|
const enableLocalTimeline = ref(defaultStore.state.enableLocalTimeline);
|
||||||
const enableSocialTimeline = ref(defaultStore.state.enableSocialTimeline);
|
const enableSocialTimeline = ref(defaultStore.state.enableSocialTimeline);
|
||||||
const enableGlobalTimeline = ref(defaultStore.state.enableGlobalTimeline);
|
const enableGlobalTimeline = ref(defaultStore.state.enableGlobalTimeline);
|
||||||
|
const enableBubbleTimeline = ref(defaultStore.state.enableBubbleTimeline);
|
||||||
const enableListTimeline = ref(defaultStore.state.enableListTimeline);
|
const enableListTimeline = ref(defaultStore.state.enableListTimeline);
|
||||||
const enableAntennaTimeline = ref(defaultStore.state.enableAntennaTimeline);
|
const enableAntennaTimeline = ref(defaultStore.state.enableAntennaTimeline);
|
||||||
const enableChannelTimeline = ref(defaultStore.state.enableChannelTimeline);
|
const enableChannelTimeline = ref(defaultStore.state.enableChannelTimeline);
|
||||||
@ -220,6 +221,11 @@ watch(enableGlobalTimeline, (x) => {
|
|||||||
reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(enableBubbleTimeline, (x) => {
|
||||||
|
defaultStore.set('enableBubbleTimeline', x);
|
||||||
|
reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
|
});
|
||||||
|
|
||||||
watch(enableListTimeline, (x) => {
|
watch(enableListTimeline, (x) => {
|
||||||
defaultStore.set('enableListTimeline', x);
|
defaultStore.set('enableListTimeline', x);
|
||||||
reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
@ -468,6 +474,11 @@ const headerActions = computed(() => {
|
|||||||
text: i18n.ts._timelines.global,
|
text: i18n.ts._timelines.global,
|
||||||
icon: 'ti ti-world',
|
icon: 'ti ti-world',
|
||||||
ref: enableGlobalTimeline,
|
ref: enableGlobalTimeline,
|
||||||
|
}, {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts._timelines.bubble,
|
||||||
|
icon: 'ti ti-droplet',
|
||||||
|
ref: enableBubbleTimeline,
|
||||||
}, { type: 'divider' }, {
|
}, { type: 'divider' }, {
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
text: i18n.ts.lists,
|
text: i18n.ts.lists,
|
||||||
|
@ -748,6 +748,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
enableBubbleTimeline: {
|
||||||
|
where: 'device',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
enableListTimeline: {
|
enableListTimeline: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -12,6 +12,7 @@ export const basicTimelineTypes = [
|
|||||||
'local',
|
'local',
|
||||||
'social',
|
'social',
|
||||||
'global',
|
'global',
|
||||||
|
'bubble',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type BasicTimelineType = typeof basicTimelineTypes[number];
|
export type BasicTimelineType = typeof basicTimelineTypes[number];
|
||||||
@ -30,6 +31,8 @@ export function basicTimelineIconClass(timeline: BasicTimelineType): string {
|
|||||||
return 'ti ti-universe';
|
return 'ti ti-universe';
|
||||||
case 'global':
|
case 'global':
|
||||||
return 'ti ti-world';
|
return 'ti ti-world';
|
||||||
|
case 'bubble':
|
||||||
|
return 'ti ti-bubble';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +46,8 @@ export function isAvailableBasicTimeline(timeline: BasicTimelineType | undefined
|
|||||||
return $i != null && $i.policies.ltlAvailable && defaultStore.state.enableSocialTimeline;
|
return $i != null && $i.policies.ltlAvailable && defaultStore.state.enableSocialTimeline;
|
||||||
case 'global':
|
case 'global':
|
||||||
return ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable && defaultStore.state.enableGlobalTimeline);
|
return ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable && defaultStore.state.enableGlobalTimeline);
|
||||||
|
case 'bubble':
|
||||||
|
return ($i == null && instance.policies.btlAvailable) || ($i != null && $i.policies.btlAvailable && defaultStore.state.enableBubbleTimeline);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,8 @@ async function setType() {
|
|||||||
value: 'social' as const, text: i18n.ts._timelines.social,
|
value: 'social' as const, text: i18n.ts._timelines.social,
|
||||||
}, {
|
}, {
|
||||||
value: 'global' as const, text: i18n.ts._timelines.global,
|
value: 'global' as const, text: i18n.ts._timelines.global,
|
||||||
|
}, {
|
||||||
|
value: 'bubble' as const, text: i18n.ts._timelines.bubble,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
|
Loading…
Reference in New Issue
Block a user