mirror of
https://github.com/kokonect-link/cherrypick
synced 2025-01-19 08:13:14 +09:00
feat: キャットタイムラインを追加
This commit is contained in:
parent
2654f6013d
commit
5e02db855e
@ -30,6 +30,7 @@
|
|||||||
- 리액션 수신의 기본값을 전체로 설정
|
- 리액션 수신의 기본값을 전체로 설정
|
||||||
- 제어판 메인 화면에 서버 통계 추가
|
- 제어판 메인 화면에 서버 통계 추가
|
||||||
- 노트의 시간을 일자로 표시하는 기능
|
- 노트의 시간을 일자로 표시하는 기능
|
||||||
|
- 고양이 타임라인 추가
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- 리노트 전 확인 팝업을 띄움
|
- 리노트 전 확인 팝업을 띄움
|
||||||
|
@ -1484,6 +1484,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"
|
||||||
|
ctlAvailable: "Can view the cat timeline"
|
||||||
canPublicNote: "Can send public notes"
|
canPublicNote: "Can send public notes"
|
||||||
canInvite: "Can create instance invite codes"
|
canInvite: "Can create instance invite codes"
|
||||||
canManageCustomEmojis: "Can manage custom emojis"
|
canManageCustomEmojis: "Can manage custom emojis"
|
||||||
@ -2053,6 +2054,7 @@ _timelines:
|
|||||||
local: "Local"
|
local: "Local"
|
||||||
media: "Media"
|
media: "Media"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
|
cat: "Cat"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
_play:
|
_play:
|
||||||
new: "Create Play"
|
new: "Create Play"
|
||||||
|
2
locales/index.d.ts
vendored
2
locales/index.d.ts
vendored
@ -1572,6 +1572,7 @@ export interface Locale {
|
|||||||
"_options": {
|
"_options": {
|
||||||
"gtlAvailable": string;
|
"gtlAvailable": string;
|
||||||
"ltlAvailable": string;
|
"ltlAvailable": string;
|
||||||
|
"ctlAvailable": string;
|
||||||
"canPublicNote": string;
|
"canPublicNote": string;
|
||||||
"canInvite": string;
|
"canInvite": string;
|
||||||
"canManageCustomEmojis": string;
|
"canManageCustomEmojis": string;
|
||||||
@ -2192,6 +2193,7 @@ export interface Locale {
|
|||||||
"local": string;
|
"local": string;
|
||||||
"media": string;
|
"media": string;
|
||||||
"social": string;
|
"social": string;
|
||||||
|
"cat": string;
|
||||||
"global": string;
|
"global": string;
|
||||||
};
|
};
|
||||||
"_play": {
|
"_play": {
|
||||||
|
@ -1494,6 +1494,7 @@ _role:
|
|||||||
_options:
|
_options:
|
||||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||||
|
ctlAvailable: "キャットタイムラインの閲覧"
|
||||||
canPublicNote: "パブリック投稿の許可"
|
canPublicNote: "パブリック投稿の許可"
|
||||||
canInvite: "サーバー招待コードの発行"
|
canInvite: "サーバー招待コードの発行"
|
||||||
canManageCustomEmojis: "カスタム絵文字の管理"
|
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||||
@ -2106,6 +2107,7 @@ _timelines:
|
|||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
media: "メディア"
|
media: "メディア"
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
|
cat: "キャット"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
|
||||||
_play:
|
_play:
|
||||||
|
@ -1484,6 +1484,7 @@ _role:
|
|||||||
_options:
|
_options:
|
||||||
gtlAvailable: "글로벌 타임라인 보이기"
|
gtlAvailable: "글로벌 타임라인 보이기"
|
||||||
ltlAvailable: "로컬 타임라인 보이기"
|
ltlAvailable: "로컬 타임라인 보이기"
|
||||||
|
ctlAvailable: "고양이 타임라인 보이기"
|
||||||
canPublicNote: "공개 노트 허용"
|
canPublicNote: "공개 노트 허용"
|
||||||
canInvite: "서버 초대 코드 발행"
|
canInvite: "서버 초대 코드 발행"
|
||||||
canManageCustomEmojis: "커스텀 이모지 관리"
|
canManageCustomEmojis: "커스텀 이모지 관리"
|
||||||
@ -2053,6 +2054,7 @@ _timelines:
|
|||||||
local: "로컬"
|
local: "로컬"
|
||||||
media: "미디어"
|
media: "미디어"
|
||||||
social: "소셜"
|
social: "소셜"
|
||||||
|
cat: "고양이"
|
||||||
global: "글로벌"
|
global: "글로벌"
|
||||||
_play:
|
_play:
|
||||||
new: "Play 만들기"
|
new: "Play 만들기"
|
||||||
|
@ -19,6 +19,7 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
|||||||
export type RolePolicies = {
|
export type RolePolicies = {
|
||||||
gtlAvailable: boolean;
|
gtlAvailable: boolean;
|
||||||
ltlAvailable: boolean;
|
ltlAvailable: boolean;
|
||||||
|
ctlAvailable: boolean;
|
||||||
canPublicNote: boolean;
|
canPublicNote: boolean;
|
||||||
canInvite: boolean;
|
canInvite: boolean;
|
||||||
canManageCustomEmojis: boolean;
|
canManageCustomEmojis: boolean;
|
||||||
@ -40,6 +41,7 @@ export type RolePolicies = {
|
|||||||
export const DEFAULT_POLICIES: RolePolicies = {
|
export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
gtlAvailable: true,
|
gtlAvailable: true,
|
||||||
ltlAvailable: true,
|
ltlAvailable: true,
|
||||||
|
ctlAvailable: true,
|
||||||
canPublicNote: true,
|
canPublicNote: true,
|
||||||
canInvite: false,
|
canInvite: false,
|
||||||
canManageCustomEmojis: false,
|
canManageCustomEmojis: false,
|
||||||
@ -275,6 +277,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||||||
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)),
|
||||||
|
ctlAvailable: calc('ctlAvailable', vs => vs.some(v => v === true)),
|
||||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||||
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
||||||
|
@ -105,6 +105,7 @@ export class NodeinfoServerService {
|
|||||||
feedbackUrl: meta.feedbackUrl,
|
feedbackUrl: meta.feedbackUrl,
|
||||||
disableRegistration: meta.disableRegistration,
|
disableRegistration: meta.disableRegistration,
|
||||||
disableLocalTimeline: !basePolicies.ltlAvailable,
|
disableLocalTimeline: !basePolicies.ltlAvailable,
|
||||||
|
disableCatTimeline: !basePolicies.ctlAvailable,
|
||||||
disableGlobalTimeline: !basePolicies.gtlAvailable,
|
disableGlobalTimeline: !basePolicies.gtlAvailable,
|
||||||
emailRequiredForSignup: meta.emailRequiredForSignup,
|
emailRequiredForSignup: meta.emailRequiredForSignup,
|
||||||
enableHcaptcha: meta.enableHcaptcha,
|
enableHcaptcha: meta.enableHcaptcha,
|
||||||
|
@ -31,6 +31,7 @@ import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.
|
|||||||
import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js';
|
import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js';
|
||||||
import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js';
|
import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js';
|
||||||
import { MediaTimelineChannelService } from './api/stream/channels/media-timeline.js';
|
import { MediaTimelineChannelService } from './api/stream/channels/media-timeline.js';
|
||||||
|
import { CatTimelineChannelService } from './api/stream/channels/cat-timeline.js';
|
||||||
import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js';
|
import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js';
|
||||||
import { MessagingChannelService } from './api/stream/channels/messaging.js';
|
import { MessagingChannelService } from './api/stream/channels/messaging.js';
|
||||||
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
||||||
@ -78,6 +79,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
|
|||||||
HybridTimelineChannelService,
|
HybridTimelineChannelService,
|
||||||
LocalTimelineChannelService,
|
LocalTimelineChannelService,
|
||||||
MediaTimelineChannelService,
|
MediaTimelineChannelService,
|
||||||
|
CatTimelineChannelService,
|
||||||
MessagingIndexChannelService,
|
MessagingIndexChannelService,
|
||||||
MessagingChannelService,
|
MessagingChannelService,
|
||||||
QueueStatsChannelService,
|
QueueStatsChannelService,
|
||||||
|
@ -262,6 +262,7 @@ 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';
|
||||||
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
|
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
|
||||||
|
import * as ep___notes_catTimeline from './endpoints/notes/cat-timeline.js';
|
||||||
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
||||||
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
||||||
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
||||||
@ -626,6 +627,7 @@ const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', u
|
|||||||
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 };
|
||||||
const $notes_mediaTimeline: Provider = { provide: 'ep:notes/media-timeline', useClass: ep___notes_mediaTimeline.default };
|
const $notes_mediaTimeline: Provider = { provide: 'ep:notes/media-timeline', useClass: ep___notes_mediaTimeline.default };
|
||||||
|
const $notes_catTimeline: Provider = { provide: 'ep:notes/cat-timeline', useClass: ep___notes_catTimeline.default };
|
||||||
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
|
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
|
||||||
const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
|
const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
|
||||||
const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
|
const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
|
||||||
@ -994,6 +996,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||||||
$notes_hybridTimeline,
|
$notes_hybridTimeline,
|
||||||
$notes_localTimeline,
|
$notes_localTimeline,
|
||||||
$notes_mediaTimeline,
|
$notes_mediaTimeline,
|
||||||
|
$notes_catTimeline,
|
||||||
$notes_mentions,
|
$notes_mentions,
|
||||||
$notes_polls_recommendation,
|
$notes_polls_recommendation,
|
||||||
$notes_polls_vote,
|
$notes_polls_vote,
|
||||||
@ -1355,6 +1358,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||||||
$notes_hybridTimeline,
|
$notes_hybridTimeline,
|
||||||
$notes_localTimeline,
|
$notes_localTimeline,
|
||||||
$notes_mediaTimeline,
|
$notes_mediaTimeline,
|
||||||
|
$notes_catTimeline,
|
||||||
$notes_mentions,
|
$notes_mentions,
|
||||||
$notes_polls_recommendation,
|
$notes_polls_recommendation,
|
||||||
$notes_polls_vote,
|
$notes_polls_vote,
|
||||||
|
@ -262,6 +262,7 @@ 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';
|
||||||
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
|
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
|
||||||
|
import * as ep___notes_catTimeline from './endpoints/notes/cat-timeline.js';
|
||||||
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
||||||
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
||||||
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
||||||
@ -624,6 +625,7 @@ const eps = [
|
|||||||
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
||||||
['notes/local-timeline', ep___notes_localTimeline],
|
['notes/local-timeline', ep___notes_localTimeline],
|
||||||
['notes/media-timeline', ep___notes_mediaTimeline],
|
['notes/media-timeline', ep___notes_mediaTimeline],
|
||||||
|
['notes/cat-timeline', ep___notes_catTimeline],
|
||||||
['notes/mentions', ep___notes_mentions],
|
['notes/mentions', ep___notes_mentions],
|
||||||
['notes/polls/recommendation', ep___notes_polls_recommendation],
|
['notes/polls/recommendation', ep___notes_polls_recommendation],
|
||||||
['notes/polls/vote', ep___notes_polls_vote],
|
['notes/polls/vote', ep___notes_polls_vote],
|
||||||
|
150
packages/backend/src/server/api/endpoints/notes/cat-timeline.ts
Normal file
150
packages/backend/src/server/api/endpoints/notes/cat-timeline.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { Brackets } from 'typeorm';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { NotesRepository, FollowingsRepository } from '@/models/index.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
ctlDisabled: {
|
||||||
|
message: 'Hybrid timeline has been disabled.',
|
||||||
|
code: 'CTL_DISABLED',
|
||||||
|
id: '620763f4-f621-4533-ab33-0577a1a3c342',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
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' },
|
||||||
|
includeMyRenotes: { type: 'boolean', default: true },
|
||||||
|
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||||
|
includeLocalRenotes: { type: 'boolean', default: true },
|
||||||
|
withFiles: { type: 'boolean', default: false },
|
||||||
|
withReplies: { type: 'boolean', default: false },
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.followingsRepository)
|
||||||
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
|
private noteEntityService: NoteEntityService,
|
||||||
|
private queryService: QueryService,
|
||||||
|
private metaService: MetaService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private activeUsersChart: ActiveUsersChart,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const policies = await this.roleService.getUserPolicies(me.id);
|
||||||
|
if (!policies.ctlAvailable) {
|
||||||
|
throw new ApiError(meta.errors.ctlDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Construct query
|
||||||
|
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
||||||
|
.select('following.followeeId')
|
||||||
|
.where('following.followerId = :followerId', { followerId: me.id });
|
||||||
|
|
||||||
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||||
|
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
|
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
|
||||||
|
.andWhere(new Brackets(qb => {
|
||||||
|
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id })
|
||||||
|
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
||||||
|
}))
|
||||||
|
.andWhere('(select "isCat" from "user" where id = note."userId")')
|
||||||
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
|
.leftJoinAndSelect('note.renote', 'renote')
|
||||||
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
|
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||||
|
.setParameters(followingQuery.getParameters());
|
||||||
|
|
||||||
|
this.queryService.generateChannelQuery(query, me);
|
||||||
|
this.queryService.generateRepliesQuery(query, ps.withReplies, me);
|
||||||
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
|
||||||
|
if (ps.includeMyRenotes === false) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('note.userId != :meId', { meId: me.id });
|
||||||
|
qb.orWhere('note.renoteId IS NULL');
|
||||||
|
qb.orWhere('note.text IS NOT NULL');
|
||||||
|
qb.orWhere('note.fileIds != \'{}\'');
|
||||||
|
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.includeRenotedMyNotes === false) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
|
||||||
|
qb.orWhere('note.renoteId IS NULL');
|
||||||
|
qb.orWhere('note.text IS NOT NULL');
|
||||||
|
qb.orWhere('note.fileIds != \'{}\'');
|
||||||
|
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.includeLocalRenotes === false) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('note.renoteUserHost IS NOT NULL');
|
||||||
|
qb.orWhere('note.renoteId IS NULL');
|
||||||
|
qb.orWhere('note.text IS NOT NULL');
|
||||||
|
qb.orWhere('note.fileIds != \'{}\'');
|
||||||
|
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.withFiles) {
|
||||||
|
query.andWhere('note.fileIds != \'{}\'');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
const timeline = await query.take(ps.limit).getMany();
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
this.activeUsersChart.read(me);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import { HybridTimelineChannelService } from './channels/hybrid-timeline.js';
|
import { HybridTimelineChannelService } from './channels/hybrid-timeline.js';
|
||||||
import { LocalTimelineChannelService } from './channels/local-timeline.js';
|
import { LocalTimelineChannelService } from './channels/local-timeline.js';
|
||||||
import { MediaTimelineChannelService } from './channels/media-timeline.js';
|
import { MediaTimelineChannelService } from './channels/media-timeline.js';
|
||||||
|
import { CatTimelineChannelService } from './channels/cat-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 { MainChannelService } from './channels/main.js';
|
import { MainChannelService } from './channels/main.js';
|
||||||
@ -25,6 +26,7 @@ export class ChannelsService {
|
|||||||
private homeTimelineChannelService: HomeTimelineChannelService,
|
private homeTimelineChannelService: HomeTimelineChannelService,
|
||||||
private localTimelineChannelService: LocalTimelineChannelService,
|
private localTimelineChannelService: LocalTimelineChannelService,
|
||||||
private mediaTimelineChannelService: MediaTimelineChannelService,
|
private mediaTimelineChannelService: MediaTimelineChannelService,
|
||||||
|
private catTimelineChannelService: CatTimelineChannelService,
|
||||||
private hybridTimelineChannelService: HybridTimelineChannelService,
|
private hybridTimelineChannelService: HybridTimelineChannelService,
|
||||||
private globalTimelineChannelService: GlobalTimelineChannelService,
|
private globalTimelineChannelService: GlobalTimelineChannelService,
|
||||||
private userListChannelService: UserListChannelService,
|
private userListChannelService: UserListChannelService,
|
||||||
@ -49,6 +51,7 @@ export class ChannelsService {
|
|||||||
case 'localTimeline': return this.localTimelineChannelService;
|
case 'localTimeline': return this.localTimelineChannelService;
|
||||||
case 'mediaTimeline': return this.mediaTimelineChannelService;
|
case 'mediaTimeline': return this.mediaTimelineChannelService;
|
||||||
case 'hybridTimeline': return this.hybridTimelineChannelService;
|
case 'hybridTimeline': return this.hybridTimelineChannelService;
|
||||||
|
case 'catTimeline': return this.catTimelineChannelService;
|
||||||
case 'globalTimeline': return this.globalTimelineChannelService;
|
case 'globalTimeline': return this.globalTimelineChannelService;
|
||||||
case 'userList': return this.userListChannelService;
|
case 'userList': return this.userListChannelService;
|
||||||
case 'hashtag': return this.hashtagChannelService;
|
case 'hashtag': return this.hashtagChannelService;
|
||||||
|
135
packages/backend/src/server/api/stream/channels/cat-timeline.ts
Normal file
135
packages/backend/src/server/api/stream/channels/cat-timeline.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { checkWordMute } from '@/misc/check-word-mute.js';
|
||||||
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
|
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||||
|
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 Channel from '../channel.js';
|
||||||
|
|
||||||
|
class CatTimelineChannel extends Channel {
|
||||||
|
public readonly chName = 'catTimeline';
|
||||||
|
public static shouldShare = true;
|
||||||
|
public static requireCredential = true;
|
||||||
|
private withReplies: boolean;
|
||||||
|
|
||||||
|
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: any): Promise<void> {
|
||||||
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
|
if (!policies.ctlAvailable) return;
|
||||||
|
|
||||||
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
|
||||||
|
// Subscribe events
|
||||||
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onNote(note: Packed<'Note'>) {
|
||||||
|
// チャンネルの投稿ではなく、自分自身の投稿 または
|
||||||
|
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
|
||||||
|
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
|
||||||
|
// フォローしているチャンネルの投稿 の場合だけ
|
||||||
|
if (!(
|
||||||
|
(note.channelId == null && this.user!.id === note.userId) ||
|
||||||
|
(note.channelId == null && this.following.has(note.userId)) ||
|
||||||
|
(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
|
||||||
|
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||||
|
)) return;
|
||||||
|
|
||||||
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
|
note = await this.noteEntityService.pack(note.id, this.user!, {
|
||||||
|
detail: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note.isHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// リプライなら再pack
|
||||||
|
if (note.replyId != null) {
|
||||||
|
note.reply = await this.noteEntityService.pack(note.replyId, this.user!, {
|
||||||
|
detail: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Renoteなら再pack
|
||||||
|
if (note.renoteId != null) {
|
||||||
|
note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, {
|
||||||
|
detail: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore notes from instances the user has muted
|
||||||
|
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
|
// 関係ない返信は除外
|
||||||
|
if (note.reply && !this.withReplies) {
|
||||||
|
const reply = note.reply;
|
||||||
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||||
|
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||||
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
|
if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
|
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||||
|
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||||
|
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||||
|
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||||
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||||
|
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||||
|
|
||||||
|
this.connection.cacheNote(note);
|
||||||
|
|
||||||
|
this.send('note', note);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
// Unsubscribe events
|
||||||
|
this.subscriber.off('notesStream', this.onNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CatTimelineChannelService {
|
||||||
|
public readonly shouldShare = CatTimelineChannel.shouldShare;
|
||||||
|
public readonly requireCredential = CatTimelineChannel.requireCredential;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private metaService: MetaService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private noteEntityService: NoteEntityService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public create(id: string, connection: Channel['connection']): CatTimelineChannel {
|
||||||
|
return new CatTimelineChannel(
|
||||||
|
this.metaService,
|
||||||
|
this.roleService,
|
||||||
|
this.noteEntityService,
|
||||||
|
id,
|
||||||
|
connection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,13 @@ export type Channels = {
|
|||||||
};
|
};
|
||||||
receives: null;
|
receives: null;
|
||||||
};
|
};
|
||||||
|
catTimeline: {
|
||||||
|
params: null;
|
||||||
|
events: {
|
||||||
|
note: (payload: Note) => void;
|
||||||
|
};
|
||||||
|
receives: null;
|
||||||
|
};
|
||||||
globalTimeline: {
|
globalTimeline: {
|
||||||
params: null;
|
params: null;
|
||||||
events: {
|
events: {
|
||||||
|
@ -107,6 +107,15 @@ if (props.src === 'antenna') {
|
|||||||
withReplies: defaultStore.state.showTimelineReplies,
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
});
|
});
|
||||||
connection.on('note', prepend);
|
connection.on('note', prepend);
|
||||||
|
} else if (props.src === 'cat') {
|
||||||
|
endpoint = 'notes/cat-timeline';
|
||||||
|
query = {
|
||||||
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
|
};
|
||||||
|
connection = stream.useChannel('catTimeline', {
|
||||||
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
|
});
|
||||||
|
connection.on('note', prepend);
|
||||||
} else if (props.src === 'global') {
|
} else if (props.src === 'global') {
|
||||||
endpoint = 'notes/global-timeline';
|
endpoint = 'notes/global-timeline';
|
||||||
query = {
|
query = {
|
||||||
|
@ -55,6 +55,7 @@ export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as con
|
|||||||
export const ROLE_POLICIES = [
|
export const ROLE_POLICIES = [
|
||||||
'gtlAvailable',
|
'gtlAvailable',
|
||||||
'ltlAvailable',
|
'ltlAvailable',
|
||||||
|
'ctlAvailable',
|
||||||
'canPublicNote',
|
'canPublicNote',
|
||||||
'canInvite',
|
'canInvite',
|
||||||
'canManageCustomEmojis',
|
'canManageCustomEmojis',
|
||||||
|
@ -131,6 +131,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.ctlAvailable, 'ctlAvailable'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.ctlAvailable }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.ctlAvailable.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.ctlAvailable.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.ctlAvailable)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.ctlAvailable.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.ctlAvailable.value" :disabled="role.policies.ctlAvailable.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.ctlAvailable.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>
|
||||||
|
@ -35,6 +35,14 @@
|
|||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.ctlAvailable, 'ctlAvailable'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.ctlAvailable }}</template>
|
||||||
|
<template #suffix>{{ policies.ctlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.ctlAvailable">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</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>
|
||||||
|
@ -72,6 +72,7 @@ const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
|
|||||||
|
|
||||||
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
||||||
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
||||||
|
const isCatTimelineAvailable = ($i == null && instance.policies.ctlAvailable) || ($i != null && $i.policies.ctlAvailable);
|
||||||
const keymap = {
|
const keymap = {
|
||||||
't': focus,
|
't': focus,
|
||||||
};
|
};
|
||||||
@ -201,7 +202,12 @@ const headerTabs = $computed(() => [{
|
|||||||
title: i18n.ts._timelines.social,
|
title: i18n.ts._timelines.social,
|
||||||
icon: 'ti ti-rocket',
|
icon: 'ti ti-rocket',
|
||||||
iconOnly: true,
|
iconOnly: true,
|
||||||
}] : []), ...(isGlobalTimelineAvailable ? [{
|
}, ...(isCatTimelineAvailable ? [{
|
||||||
|
key: 'cat',
|
||||||
|
title: i18n.ts._timelines.cat,
|
||||||
|
icon: 'ti ti-cat',
|
||||||
|
iconOnly: true,
|
||||||
|
}] : []), ] : []), ...(isGlobalTimelineAvailable ? [{
|
||||||
key: 'global',
|
key: 'global',
|
||||||
title: i18n.ts._timelines.global,
|
title: i18n.ts._timelines.global,
|
||||||
icon: 'ti ti-world',
|
icon: 'ti ti-world',
|
||||||
|
@ -24,7 +24,7 @@ export type Column = {
|
|||||||
channelId?: string;
|
channelId?: string;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
includingTypes?: typeof notificationTypes[number][];
|
includingTypes?: typeof notificationTypes[number][];
|
||||||
tl?: 'home' | 'local' | 'media' | 'social' | 'global';
|
tl?: 'home' | 'local' | 'media' | 'social' | 'cat' | 'global';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deckStore = markRaw(new Storage('deck', {
|
export const deckStore = markRaw(new Storage('deck', {
|
||||||
|
@ -5,11 +5,12 @@
|
|||||||
<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
|
<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
|
||||||
<i v-else-if="column.tl === 'social'" class="ti ti-rocket"></i>
|
<i v-else-if="column.tl === 'social'" class="ti ti-rocket"></i>
|
||||||
<i v-else-if="column.tl === 'media'" class="ti ti-photo"></i>
|
<i v-else-if="column.tl === 'media'" class="ti ti-photo"></i>
|
||||||
|
<i v-else-if="column.tl === 'cat'" class="ti cat"></i>
|
||||||
<i v-else-if="column.tl === 'global'" class="ti ti-world"></i>
|
<i v-else-if="column.tl === 'global'" class="ti ti-world"></i>
|
||||||
<span style="margin-left: 8px;">{{ column.name }}</span>
|
<span style="margin-left: 8px;">{{ column.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="(((column.tl === 'local' || column.tl === 'social') && !isLocalTimelineAvailable) || (column.tl === 'global' && !isGlobalTimelineAvailable))" :class="$style.disabled">
|
<div v-if="(((column.tl === 'local' || column.tl === 'social' || column.tl === 'cat') && !isLocalTimelineAvailable) || (column.tl === 'cat' && !isCatTimelineAvailable) || (column.tl === 'global' && !isGlobalTimelineAvailable))" :class="$style.disabled">
|
||||||
<p :class="$style.disabledTitle">
|
<p :class="$style.disabledTitle">
|
||||||
<i class="ti ti-circle-minus"></i>
|
<i class="ti ti-circle-minus"></i>
|
||||||
{{ i18n.ts._disabledTimeline.title }}
|
{{ i18n.ts._disabledTimeline.title }}
|
||||||
@ -39,13 +40,15 @@ let disabled = $ref(false);
|
|||||||
|
|
||||||
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
||||||
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
||||||
|
const isCatTimelineAvailable = (($i == null && instance.policies.ctlAvailable) || ($i != null && $i.policies.ctlAvailable));
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.column.tl == null) {
|
if (props.column.tl == null) {
|
||||||
setType();
|
setType();
|
||||||
} else if ($i) {
|
} else if ($i) {
|
||||||
disabled = (
|
disabled = (
|
||||||
(!((instance.policies.ltlAvailable) || ($i.policies.ltlAvailable)) && ['local', 'social'].includes(props.column.tl)) ||
|
(!((instance.policies.ltlAvailable) || ($i.policies.ltlAvailable)) && ['local', 'social', 'cat'].includes(props.column.tl)) ||
|
||||||
|
(!((instance.policies.ctlAvailable) || ($i.policies.ctlAvailable)) && ['cat'].includes(props.column.tl)) ||
|
||||||
(!((instance.policies.gtlAvailable) || ($i.policies.gtlAvailable)) && ['global'].includes(props.column.tl)));
|
(!((instance.policies.gtlAvailable) || ($i.policies.gtlAvailable)) && ['global'].includes(props.column.tl)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -61,6 +64,8 @@ async function setType() {
|
|||||||
value: 'media' as const, text: i18n.ts._timelines.media,
|
value: 'media' as const, text: i18n.ts._timelines.media,
|
||||||
}, {
|
}, {
|
||||||
value: 'social' as const, text: i18n.ts._timelines.social,
|
value: 'social' as const, text: i18n.ts._timelines.social,
|
||||||
|
}, {
|
||||||
|
value: 'cat' as const, text: i18n.ts._timelines.cat,
|
||||||
}, {
|
}, {
|
||||||
value: 'global' as const, text: i18n.ts._timelines.global,
|
value: 'global' as const, text: i18n.ts._timelines.global,
|
||||||
}],
|
}],
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<i v-if="widgetProps.src === 'home'" class="ti ti-home"></i>
|
<i v-if="widgetProps.src === 'home'" class="ti ti-home"></i>
|
||||||
<i v-else-if="widgetProps.src === 'local'" class="ti ti-planet"></i>
|
<i v-else-if="widgetProps.src === 'local'" class="ti ti-planet"></i>
|
||||||
<i v-else-if="widgetProps.src === 'social'" class="ti ti-rocket"></i>
|
<i v-else-if="widgetProps.src === 'social'" class="ti ti-rocket"></i>
|
||||||
|
<i v-else-if="widgetProps.src === 'cat'" class="ti ti-cat"></i>
|
||||||
<i v-else-if="widgetProps.src === 'global'" class="ti ti-world"></i>
|
<i v-else-if="widgetProps.src === 'global'" class="ti ti-world"></i>
|
||||||
<i v-else-if="widgetProps.src === 'list'" class="ti ti-list"></i>
|
<i v-else-if="widgetProps.src === 'list'" class="ti ti-list"></i>
|
||||||
<i v-else-if="widgetProps.src === 'antenna'" class="ti ti-antenna"></i>
|
<i v-else-if="widgetProps.src === 'antenna'" class="ti ti-antenna"></i>
|
||||||
@ -15,7 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="(((widgetProps.src === 'local' || widgetProps.src === 'social') && !isLocalTimelineAvailable) || (widgetProps.src === 'global' && !isGlobalTimelineAvailable))" :class="$style.disabled">
|
<div v-if="(((widgetProps.src === 'local' || widgetProps.src === 'social' || widgetProps.src === 'cat') && !isLocalTimelineAvailable) || (widgetProps.src === 'cat' && !isCatTimelineAvailable) || (widgetProps.src === 'global' && !isGlobalTimelineAvailable))" :class="$style.disabled">
|
||||||
<p :class="$style.disabledTitle">
|
<p :class="$style.disabledTitle">
|
||||||
<i class="ti ti-minus"></i>
|
<i class="ti ti-minus"></i>
|
||||||
{{ i18n.ts._disabledTimeline.title }}
|
{{ i18n.ts._disabledTimeline.title }}
|
||||||
@ -42,6 +43,7 @@ import { instance } from '@/instance';
|
|||||||
const name = 'timeline';
|
const name = 'timeline';
|
||||||
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
||||||
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
||||||
|
const isCatTimelineAvailable = (($i == null && instance.policies.ctlAvailable) || ($i != null && $i.policies.ctlAvailable));
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
@ -125,6 +127,10 @@ const choose = async (ev) => {
|
|||||||
text: i18n.ts._timelines.social,
|
text: i18n.ts._timelines.social,
|
||||||
icon: 'ti ti-rocket',
|
icon: 'ti ti-rocket',
|
||||||
action: () => { setSrc('social'); },
|
action: () => { setSrc('social'); },
|
||||||
|
}, {
|
||||||
|
text: i18n.ts._timelines.cat,
|
||||||
|
icon: 'ti ti-cat',
|
||||||
|
action: () => { setSrc('cat'); },
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts._timelines.global,
|
text: i18n.ts._timelines.global,
|
||||||
icon: 'ti ti-world',
|
icon: 'ti ti-world',
|
||||||
|
Loading…
Reference in New Issue
Block a user