From c7e607ea5f58cd9d79c629e0a995139eae853537 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 13 Apr 2023 19:31:27 -0400 Subject: [PATCH 001/308] Add event source code to models --- packages/backend/src/models/entities/Event.ts | 33 +++++++++++++++++++ packages/backend/src/models/entities/Note.ts | 17 +++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/models/entities/Event.ts diff --git a/packages/backend/src/models/entities/Event.ts b/packages/backend/src/models/entities/Event.ts new file mode 100644 index 0000000000..bf15838651 --- /dev/null +++ b/packages/backend/src/models/entities/Event.ts @@ -0,0 +1,33 @@ +import { Entity, Index, Column, PrimaryColumn } from 'typeorm'; +import { id } from '../id.js'; + +@Entity() +export class Event { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The start time of the event', + }) + public start: Date; + + @Column('timestamp with time zone', { + comment: 'The end of the event', + nullable: true, + }) + public end: Date; + + @Column({ + type: 'varchar', + length: 128, + comment: 'short name of event', + }) + public title: string; + + @Column('jsonb', { + default: {}, + comment: 'metadata mapping for event with more user configurable optional information', + }) + public metadata: Record; +} diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index df508b4dca..11cc6be34b 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -1,8 +1,9 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; +import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne, OneToOne } from 'typeorm'; import { id } from '../id.js'; import { noteVisibilities } from '../../types.js'; import { User } from './User.js'; import { Channel } from './Channel.js'; +import { Event } from './Event.js'; import type { DriveFile } from './DriveFile.js'; @Entity() @@ -53,6 +54,20 @@ export class Note { }) public threadId: string | null; + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'id of child event', + }) + public noteId: Event['id'] | null; + + @OneToOne(type => Event, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public note: Event | null; + // TODO: varcharにしたい @Column('text', { nullable: true, From 10e134a98001e4283e9aff4efa2654065f76baf9 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 13 Apr 2023 19:55:50 -0400 Subject: [PATCH 002/308] entity typo --- packages/backend/src/models/entities/Note.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index 11cc6be34b..ad776b73b0 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -58,15 +58,15 @@ export class Note { @Column({ ...id(), nullable: true, - comment: 'id of child event', + comment: 'The ID of child event', }) - public noteId: Event['id'] | null; + public eventId: Event['id'] | null; @OneToOne(type => Event, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Event | null; + public event: Event | null; // TODO: varcharにしたい @Column('text', { From 4e6a4915d8de37de7b81036618f37fb0c352693f Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 13 Apr 2023 20:00:58 -0400 Subject: [PATCH 003/308] Event migration --- .../backend/migration/1681429921400-Event.js | 95 +++++++++++++++++++ packages/backend/src/postgres.ts | 2 + 2 files changed, 97 insertions(+) create mode 100644 packages/backend/migration/1681429921400-Event.js diff --git a/packages/backend/migration/1681429921400-Event.js b/packages/backend/migration/1681429921400-Event.js new file mode 100644 index 0000000000..29054b99e4 --- /dev/null +++ b/packages/backend/migration/1681429921400-Event.js @@ -0,0 +1,95 @@ +export class Event1681429921400 { + name = 'Event1681429921400' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_2cd3b2a6b4cf0b910b260afe08"`); + await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_createdAt"`); + await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muteeId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muterId"`); + await queryRunner.query(`CREATE TABLE "event" ("id" character varying(32) NOT NULL, "start" TIMESTAMP WITH TIME ZONE NOT NULL, "end" TIMESTAMP WITH TIME ZONE, "title" character varying(128) NOT NULL, "metadata" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" PRIMARY KEY ("id")); COMMENT ON COLUMN "event"."start" IS 'The start time of the event'; COMMENT ON COLUMN "event"."end" IS 'The end of the event'; COMMENT ON COLUMN "event"."title" IS 'short name of event'; COMMENT ON COLUMN "event"."metadata" IS 'metadata mapping for event with more user configurable optional information'`); + await queryRunner.query(`CREATE INDEX "IDX_785ee5fc1ea38a1b9b38ff88e5" ON "event" ("start") `); + await queryRunner.query(`ALTER TABLE "note" ADD "eventId" character varying(32)`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "UQ_3af9380f266b7046cce9c992197" UNIQUE ("eventId")`); + await queryRunner.query(`COMMENT ON COLUMN "note"."eventId" IS 'The ID of child event'`); + await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the root.'`); + await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS 'The expired date of the Ad.'`); + await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "mascotImageUrl" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "errorImageUrl" DROP DEFAULT`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS 'The mutee user ID.'`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS 'The muter user ID.'`); + await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`); + await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`); + await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`); + await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`); + await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`); + await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`); + await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`); + await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`); + await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`); + await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`); + await queryRunner.query(`CREATE INDEX "IDX_3fcc2c589eaefc205e0714b99c" ON "ad" ("startsAt") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c71faf11f0a28a5c0bb506203c" ON "channel_favorite" ("userId", "channelId") `); + await queryRunner.query(`CREATE INDEX "IDX_3af9380f266b7046cce9c99219" ON "note" ("eventId") `); + await queryRunner.query(`CREATE INDEX "IDX_f7b9d338207e40e768e4a5265a" ON "instance" ("firstRetrievedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_7eac97594bcac5ffcf2068089b" ON "renote_muting" ("muteeId") `); + await queryRunner.query(`CREATE INDEX "IDX_7aa72a5fe76019bfe8e5e0e8b7" ON "renote_muting" ("muterId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0d801c609cec4e9eb4b6b4490c" ON "renote_muting" ("muterId", "muteeId") `); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_3af9380f266b7046cce9c992197" FOREIGN KEY ("eventId") REFERENCES "event"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`); + await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`); + await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`); + await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`); + await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`); + await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d"`); + await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_3af9380f266b7046cce9c992197"`); + await queryRunner.query(`DROP INDEX "public"."IDX_0d801c609cec4e9eb4b6b4490c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_7aa72a5fe76019bfe8e5e0e8b7"`); + await queryRunner.query(`DROP INDEX "public"."IDX_7eac97594bcac5ffcf2068089b"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f7b9d338207e40e768e4a5265a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_3af9380f266b7046cce9c99219"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c71faf11f0a28a5c0bb506203c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_3fcc2c589eaefc205e0714b99c"`); + await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`); + await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`); + await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`); + await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`); + await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`); + await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS NULL`); + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "errorImageUrl" SET DEFAULT 'https://xn--931a.moe/aiart/yubitun.png'`); + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "mascotImageUrl" SET DEFAULT '/assets/ai.png'`); + await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" SET DEFAULT '2023-04-13 18:46:24.168209-04'`); + await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the admin.'`); + await queryRunner.query(`COMMENT ON COLUMN "note"."eventId" IS 'The ID of child event'`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "UQ_3af9380f266b7046cce9c992197"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "eventId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_785ee5fc1ea38a1b9b38ff88e5"`); + await queryRunner.query(`DROP TABLE "event"`); + await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `); + await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `); + await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_2cd3b2a6b4cf0b910b260afe08" ON "instance" ("firstRetrievedAt") `); + } +} diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index bb21ed827e..4bbf1d0f4d 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -24,6 +24,7 @@ import { ClipFavorite } from '@/models/entities/ClipFavorite.js'; import { DriveFile } from '@/models/entities/DriveFile.js'; import { DriveFolder } from '@/models/entities/DriveFolder.js'; import { Emoji } from '@/models/entities/Emoji.js'; +import { Event } from '@/models/entities/Event.js'; import { Following } from '@/models/entities/Following.js'; import { FollowRequest } from '@/models/entities/FollowRequest.js'; import { GalleryLike } from '@/models/entities/GalleryLike.js'; @@ -155,6 +156,7 @@ export const entities = [ Poll, PollVote, Emoji, + Event, Hashtag, SwSubscription, AbuseUserReport, From 8ab1d62444ece59d36fb83a7632b04f32806d3be Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 15:25:54 -0400 Subject: [PATCH 004/308] Changed DB to have events behave like polls --- packages/backend/src/models/entities/Event.ts | 11 +++++++++-- packages/backend/src/models/entities/Note.ts | 18 ++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/models/entities/Event.ts b/packages/backend/src/models/entities/Event.ts index bf15838651..779c491cca 100644 --- a/packages/backend/src/models/entities/Event.ts +++ b/packages/backend/src/models/entities/Event.ts @@ -1,10 +1,17 @@ -import { Entity, Index, Column, PrimaryColumn } from 'typeorm'; +import { Entity, Index, Column, PrimaryColumn, OneToOne, JoinColumn } from 'typeorm'; import { id } from '../id.js'; +import { Note } from './Note.js'; @Entity() export class Event { @PrimaryColumn(id()) - public id: string; + public noteId: Note['id']; + + @OneToOne(type => Note, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public note: Note | null; @Index() @Column('timestamp with time zone', { diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index ad776b73b0..4af007abc3 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -1,9 +1,8 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne, OneToOne } from 'typeorm'; +import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; import { id } from '../id.js'; import { noteVisibilities } from '../../types.js'; import { User } from './User.js'; import { Channel } from './Channel.js'; -import { Event } from './Event.js'; import type { DriveFile } from './DriveFile.js'; @Entity() @@ -54,19 +53,10 @@ export class Note { }) public threadId: string | null; - @Index() - @Column({ - ...id(), - nullable: true, - comment: 'The ID of child event', - }) - public eventId: Event['id'] | null; - - @OneToOne(type => Event, { - onDelete: 'CASCADE', + @Column('boolean', { + default: false, }) - @JoinColumn() - public event: Event | null; + public isEvent: boolean; // TODO: varcharにしたい @Column('text', { From a56561f8aa6a08c3bc2e2d9b86ce88f3eee7905d Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 15:32:39 -0400 Subject: [PATCH 005/308] add new migration --- .../backend/migration/1681673280586-event.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/backend/migration/1681673280586-event.js diff --git a/packages/backend/migration/1681673280586-event.js b/packages/backend/migration/1681673280586-event.js new file mode 100644 index 0000000000..7e464dbcf9 --- /dev/null +++ b/packages/backend/migration/1681673280586-event.js @@ -0,0 +1,29 @@ +export class Event1681673280586 { + name = 'Event1681673280586' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_3af9380f266b7046cce9c992197"`); + await queryRunner.query(`DROP INDEX "public"."IDX_3af9380f266b7046cce9c99219"`); + await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "eventId" TO "isEvent"`); + await queryRunner.query(`ALTER TABLE "note" RENAME CONSTRAINT "UQ_3af9380f266b7046cce9c992197" TO "UQ_16484b50d1ee91555d4b8821ac3"`); + await queryRunner.query(`ALTER TABLE "event" RENAME COLUMN "id" TO "noteId"`); + await queryRunner.query(`ALTER TABLE "event" RENAME CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" TO "PK_2b481f231cd035e84390072bf7b"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "isEvent"`); + await queryRunner.query(`ALTER TABLE "note" ADD "isEvent" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "event" ADD CONSTRAINT "FK_2b481f231cd035e84390072bf7b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "event" DROP CONSTRAINT "FK_2b481f231cd035e84390072bf7b"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "isEvent"`); + await queryRunner.query(`ALTER TABLE "note" ADD "isEvent" character varying(32)`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3" UNIQUE ("isEvent")`); + await queryRunner.query(`ALTER TABLE "event" RENAME CONSTRAINT "PK_2b481f231cd035e84390072bf7b" TO "PK_30c2f3bbaf6d34a55f8ae6e4614"`); + await queryRunner.query(`ALTER TABLE "event" RENAME COLUMN "noteId" TO "id"`); + await queryRunner.query(`ALTER TABLE "note" RENAME CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3" TO "UQ_3af9380f266b7046cce9c992197"`); + await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "isEvent" TO "eventId"`); + await queryRunner.query(`CREATE INDEX "IDX_3af9380f266b7046cce9c99219" ON "note" ("eventId") `); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_3af9380f266b7046cce9c992197" FOREIGN KEY ("eventId") REFERENCES "event"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} From 173a901eee2b1952e11a20b23f5b3c26abde83e5 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 16:13:11 -0400 Subject: [PATCH 006/308] denormalize Events/Notes relation --- .../backend/migration/1681675881633-event.js | 29 ++++++++++++++ packages/backend/src/models/entities/Event.ts | 39 +++++++++++++++++++ packages/backend/src/models/entities/Note.ts | 2 +- packages/misskey-js/src/entities.ts | 6 +++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1681675881633-event.js diff --git a/packages/backend/migration/1681675881633-event.js b/packages/backend/migration/1681675881633-event.js new file mode 100644 index 0000000000..562d04be0d --- /dev/null +++ b/packages/backend/migration/1681675881633-event.js @@ -0,0 +1,29 @@ +export class Event1681675881633 { + name = 'Event1681675881633' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "isEvent" TO "hasEvent"`); + await queryRunner.query(`CREATE TYPE "public"."event_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`); + await queryRunner.query(`ALTER TABLE "event" ADD "noteVisibility" "public"."event_notevisibility_enum" NOT NULL`); + await queryRunner.query(`COMMENT ON COLUMN "event"."noteVisibility" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "event" ADD "userId" character varying(32) NOT NULL`); + await queryRunner.query(`COMMENT ON COLUMN "event"."userId" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "event" ADD "userHost" character varying(128)`); + await queryRunner.query(`COMMENT ON COLUMN "event"."userHost" IS '[Denormalized]'`); + await queryRunner.query(`CREATE INDEX "IDX_01cd2b829e0263917bf570cb67" ON "event" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_f6ba57dff679ccbcfe004698ec" ON "event" ("userHost") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_f6ba57dff679ccbcfe004698ec"`); + await queryRunner.query(`DROP INDEX "public"."IDX_01cd2b829e0263917bf570cb67"`); + await queryRunner.query(`COMMENT ON COLUMN "event"."userHost" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "userHost"`); + await queryRunner.query(`COMMENT ON COLUMN "event"."userId" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "userId"`); + await queryRunner.query(`COMMENT ON COLUMN "event"."noteVisibility" IS '[Denormalized]'`); + await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "noteVisibility"`); + await queryRunner.query(`DROP TYPE "public"."event_notevisibility_enum"`); + await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "hasEvent" TO "isEvent"`); + } +} diff --git a/packages/backend/src/models/entities/Event.ts b/packages/backend/src/models/entities/Event.ts index 779c491cca..9d986b7c85 100644 --- a/packages/backend/src/models/entities/Event.ts +++ b/packages/backend/src/models/entities/Event.ts @@ -1,6 +1,8 @@ import { Entity, Index, Column, PrimaryColumn, OneToOne, JoinColumn } from 'typeorm'; import { id } from '../id.js'; +import { noteVisibilities } from '../../types.js'; import { Note } from './Note.js'; +import type { User } from './User.js'; @Entity() export class Event { @@ -37,4 +39,41 @@ export class Event { comment: 'metadata mapping for event with more user configurable optional information', }) public metadata: Record; + + //#region Denormalized fields + @Column('enum', { + enum: noteVisibilities, + comment: '[Denormalized]', + }) + public noteVisibility: typeof noteVisibilities[number]; + + @Index() + @Column({ + ...id(), + comment: '[Denormalized]', + }) + public userId: User['id']; + + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]', + }) + public userHost: string | null; + //#endregion + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } +} + +export type IEvent = { + start: Date; + end: Date | null + title: string; + metadata: Record; } diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index 4af007abc3..51339fd7ba 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -56,7 +56,7 @@ export class Note { @Column('boolean', { default: false, }) - public isEvent: boolean; + public hasEvent: boolean; // TODO: varcharにしたい @Column('text', { diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 0c90e44494..5656e6b8cf 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -150,6 +150,12 @@ export type Note = { replyId: Note['id']; renote?: Note; renoteId: Note['id']; + event?: { + title: string, + start: DateString, + end: DateString | null, + metadata: Record, + }; files: DriveFile[]; fileIds: DriveFile['id'][]; visibility: 'public' | 'home' | 'followers' | 'specified'; From 453d25ff3769c786444713458fb3165085a7e76d Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 16:13:43 -0400 Subject: [PATCH 007/308] Add Event to create api --- .../backend/src/core/NoteCreateService.ts | 44 ++++++++++++++----- .../src/server/api/endpoints/notes/create.ts | 16 +++++++ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 32e4fe7f8a..5d6d88e271 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -8,6 +8,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import { Note } from '@/models/entities/Note.js'; +import { Event, IEvent } from '@/models/entities/Event.js'; import type { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { App } from '@/models/entities/App.js'; @@ -126,6 +127,7 @@ type Option = { renote?: Note | null; files?: DriveFile[] | null; poll?: IPoll | null; + event?: IEvent | null; localOnly?: boolean | null; reactionAcceptance?: Note['reactionAcceptance']; cw?: string | null; @@ -358,6 +360,7 @@ export class NoteCreateService implements OnApplicationShutdown { name: data.name, text: data.text, hasPoll: data.poll != null, + hasEvent: data.event != null, cw: data.cw == null ? null : data.cw, tags: tags.map(tag => normalizeForSearch(tag)), emojis, @@ -402,23 +405,40 @@ export class NoteCreateService implements OnApplicationShutdown { // 投稿を作成 try { - if (insert.hasPoll) { + if (insert.hasPoll || insert.hasEvent) { // Start transaction await this.db.transaction(async transactionalEntityManager => { await transactionalEntityManager.insert(Note, insert); - const poll = new Poll({ - noteId: insert.id, - choices: data.poll!.choices, - expiresAt: data.poll!.expiresAt, - multiple: data.poll!.multiple, - votes: new Array(data.poll!.choices.length).fill(0), - noteVisibility: insert.visibility, - userId: user.id, - userHost: user.host, - }); + if (insert.hasPoll) { + const poll = new Poll({ + noteId: insert.id, + choices: data.poll!.choices, + expiresAt: data.poll!.expiresAt, + multiple: data.poll!.multiple, + votes: new Array(data.poll!.choices.length).fill(0), + noteVisibility: insert.visibility, + userId: user.id, + userHost: user.host, + }); + + await transactionalEntityManager.insert(Poll, poll); + } - await transactionalEntityManager.insert(Poll, poll); + if (insert.hasEvent) { + const event = new Event({ + noteId: insert.id, + start: data.event!.start, + end: data.event!.end ?? undefined, + title: data.event!.title, + metadata: data.event!.metadata, + noteVisibility: insert.visibility, + userId: user.id, + userHost: user.host, + }); + + await transactionalEntityManager.insert(Event, event); + } }); } else { await this.notesRepository.insert(insert); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 69fafcb9c7..7141c37fcd 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -144,6 +144,16 @@ export const paramDef = { }, required: ['choices'], }, + event: { + type: 'object', + nullable: true, + properties: { + title: { type: 'string', minLength: 1, maxLength: 128, nullable: false }, + start: { type: 'integer', nullable: false }, + end: { type: 'integer', nullable: true }, + metadata: { type: 'object' }, + }, + }, }, // (re)note with text, files and poll are optional anyOf: [ @@ -279,6 +289,12 @@ export default class extends Endpoint { text: ps.text ?? undefined, reply, renote, + event: ps.event ? { + start: new Date(ps.event.start!), + end: ps.event.end ? new Date(ps.event.end) : null, + title: ps.event.title!, + metadata: ps.event.metadata ?? {}, + } : undefined, cw: ps.cw, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, From 44d05d58c4553df82d1b78adf35b03409d74ea66 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 17:27:34 -0400 Subject: [PATCH 008/308] Add event to repositories (getters) --- .../src/core/entities/NoteEntityService.ts | 17 ++++++++++++++++- packages/backend/src/di-symbols.ts | 1 + packages/backend/src/models/RepositoryModule.ts | 10 +++++++++- packages/backend/src/models/index.ts | 3 +++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 26debd6adc..ed93c7b146 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -9,7 +9,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository, EventsRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -43,6 +43,9 @@ export class NoteEntityService implements OnModuleInit { @Inject(DI.pollsRepository) private pollsRepository: PollsRepository, + @Inject(DI.eventsRepository) + private eventsRepository: EventsRepository, + @Inject(DI.pollVotesRepository) private pollVotesRepository: PollVotesRepository, @@ -169,6 +172,17 @@ export class NoteEntityService implements OnModuleInit { }; } + @bindThis + private async populateEvent(note: Note) { + const event = await this.eventsRepository.findOneByOrFail({ noteId: note.id }); + return { + title: event.title, + start: event.start, + end: event.end, + metadata: event.metadata, + }; + } + @bindThis private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { myReactions: Map; @@ -352,6 +366,7 @@ export class NoteEntityService implements OnModuleInit { }) : undefined, poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, + event: note.hasEvent ? this.populateEvent(note) : undefined, ...(meId ? { myReaction: this.populateMyReaction(note, meId, options?._hint_), diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index d4b1fb31b1..44cb1a4121 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -32,6 +32,7 @@ export const DI = { followRequestsRepository: Symbol('followRequestsRepository'), instancesRepository: Symbol('instancesRepository'), emojisRepository: Symbol('emojisRepository'), + eventsRepository: Symbol('eventsRepository'), driveFilesRepository: Symbol('driveFilesRepository'), driveFoldersRepository: Symbol('driveFoldersRepository'), metasRepository: Symbol('metasRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 7be7b81904..7d2655f499 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, Event, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -160,6 +160,12 @@ const $emojisRepository: Provider = { inject: [DI.db], }; +const $eventsRepository: Provider = { + provide: DI.eventsRepository, + useFactory: (db: DataSource) => db.getRepository(Event), + inject: [DI.db], +}; + const $driveFilesRepository: Provider = { provide: DI.driveFilesRepository, useFactory: (db: DataSource) => db.getRepository(DriveFile), @@ -418,6 +424,7 @@ const $roleAssignmentsRepository: Provider = { $followRequestsRepository, $instancesRepository, $emojisRepository, + $eventsRepository, $driveFilesRepository, $driveFoldersRepository, $metasRepository, @@ -484,6 +491,7 @@ const $roleAssignmentsRepository: Provider = { $followRequestsRepository, $instancesRepository, $emojisRepository, + $eventsRepository, $driveFilesRepository, $driveFoldersRepository, $metasRepository, diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 48d6e15f2a..bd60d4e576 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -16,6 +16,7 @@ import { ClipFavorite } from '@/models/entities/ClipFavorite.js'; import { DriveFile } from '@/models/entities/DriveFile.js'; import { DriveFolder } from '@/models/entities/DriveFolder.js'; import { Emoji } from '@/models/entities/Emoji.js'; +import { Event } from '@/models/entities/Event.js'; import { Following } from '@/models/entities/Following.js'; import { FollowRequest } from '@/models/entities/FollowRequest.js'; import { GalleryLike } from '@/models/entities/GalleryLike.js'; @@ -83,6 +84,7 @@ export { DriveFile, DriveFolder, Emoji, + Event, Following, FollowRequest, GalleryLike, @@ -149,6 +151,7 @@ export type ClipFavoritesRepository = Repository; export type DriveFilesRepository = Repository; export type DriveFoldersRepository = Repository; export type EmojisRepository = Repository; +export type EventsRepository = Repository; export type FollowingsRepository = Repository; export type FollowRequestsRepository = Repository; export type GalleryLikesRepository = Repository; From 1a797eee358a1db5abbcc0b93614e9ed78d7c977 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 16 Apr 2023 21:59:18 -0400 Subject: [PATCH 009/308] Added notes/events/search endpoint logic --- .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 2 + .../api/endpoints/notes/events/search.ts | 134 ++ packages/misskey-js/etc/misskey-js.api.md | 1792 +++++++++-------- packages/misskey-js/src/api.types.ts | 1 + 5 files changed, 1047 insertions(+), 886 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/notes/events/search.ts diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index ca89d82853..dcf09de83b 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -254,6 +254,7 @@ import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.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_vote from './endpoints/notes/polls/vote.js'; +import * as ep___notes_events_search from './endpoints/notes/events/search.js'; import * as ep___notes_reactions from './endpoints/notes/reactions.js'; import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; @@ -588,6 +589,7 @@ const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', use 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_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default }; +const $notes_events_search: Provider = { provide: 'ep:notes/events/search', useClass: ep___notes_events_search.default }; const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default }; const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default }; const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default }; @@ -926,6 +928,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_mentions, $notes_polls_recommendation, $notes_polls_vote, + $notes_events_search, $notes_reactions, $notes_reactions_create, $notes_reactions_delete, @@ -1258,6 +1261,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_mentions, $notes_polls_recommendation, $notes_polls_vote, + $notes_events_search, $notes_reactions, $notes_reactions_create, $notes_reactions_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index dab897117d..292f18a880 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -254,6 +254,7 @@ import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.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_vote from './endpoints/notes/polls/vote.js'; +import * as ep___notes_events_search from './endpoints/notes/events/search.js'; import * as ep___notes_reactions from './endpoints/notes/reactions.js'; import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; @@ -586,6 +587,7 @@ const eps = [ ['notes/mentions', ep___notes_mentions], ['notes/polls/recommendation', ep___notes_polls_recommendation], ['notes/polls/vote', ep___notes_polls_vote], + ['notes/events/search', ep___notes_events_search], ['notes/reactions', ep___notes_reactions], ['notes/reactions/create', ep___notes_reactions_create], ['notes/reactions/delete', ep___notes_reactions_delete], diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts new file mode 100644 index 0000000000..51c192714d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -0,0 +1,134 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import { Event } from '@/models/entities/Event.js'; +import type { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, + + errors: { + unavailable: { + message: 'Search of notes unavailable.', + code: 'UNAVAILABLE', + id: '0b44998d-77aa-4427-80d0-d2c9b8523011', + }, + invalidParam: { + message: 'Invalid Parameter', + code: 'INVALID_PARAM', + id: 'e70903d3-0aa2-44d5-a955-4de5723c603d', + } + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + users: { type: 'array', nullable: true, items: { type: 'object', format: 'misskey:id' } }, + sinceDate: { type: 'integer', nullable: true }, + untilDate: { type: 'integer', nullable: true }, + filters: { + type: 'object', + nullable: true, + description: 'mapping of string -> [string] that filters events based on metadata', + }, + sortBy: { type: 'string', nullable: true, default: 'startDate', enum: ['startDate', 'createdAt'] }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const policies = await this.roleService.getUserPolicies(me ? me.id : null); + if (!policies.canSearchNotes) { + throw new ApiError(meta.errors.unavailable); + } + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); + + if (ps.users) { + if (ps.users.length < 1) throw new ApiError(meta.errors.invalidParam); + query.andWhere('note.userId IN (:...users)', { users: ps.users }); + } + + query + .innerJoinAndSelect(Event, 'event', 'event.noteId = note.id') + .innerJoinAndSelect('note.user', 'user'); + + if (ps.filters) { + const filters: Record = ps.filters; + + Object.keys(filters).forEach(f => { + const matches = filters[f].filter(x => x !== null); + if (matches.length < 1) throw new ApiError(meta.errors.invalidParam); + query.andWhere(new Brackets((qb) => { + qb.where('event.metadata ->> :key IN (:...values)', { key: f, values: filters[f].filter(x => x !== null) }); + if (filters[f].filter(x => x === null).length > 0) { + qb.orWhere('event.metadata ->> :key IS NULL', { key: f }); + } + })); + }); + } + + if (ps.sinceDate && ps.untilDate && ps.sinceDate > ps.untilDate) throw new ApiError(meta.errors.invalidParam); + const sinceDate = ps.sinceDate ? new Date(ps.sinceDate) : new Date(); + query.andWhere('event.start > :sinceDate', { sinceDate: sinceDate }) + .andWhere('(event.end IS NULL OR event.end > :sinceDate)', { sinceDate: sinceDate }); + if (ps.untilDate) { + query.andWhere('event.start < :untilDate', { untilDate: new Date(ps.untilDate) }); + } + + if (ps.sortBy === 'createdAt') { + query.orderBy('note.createdAt', 'ASC'); + } else { + query.orderBy('event.start', 'ASC'); + } + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 67d12000b8..1936d8fb50 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1757,971 +1757,991 @@ export type Endpoints = { }; res: null; }; - 'notes/reactions': { - req: { - noteId: Note['id']; - type?: string | null; - limit?: number; + 'notes/events/search': { + req: { + sinceId?: Note['id']; + untilId?: Note['id']; + limit?: number; + offset?: number; + host?: string; + users?: User['id'][]; + sinceDate?: number; + untilDate?: number; + filters?: Record; + sortBy?: 'startDate' | 'createAt' + res: Note[]; + }; + 'notes/reactions': { + req: { + noteId: Note['id']; + type?: string | null; + limit?: number; + }; + res: NoteReaction[]; }; - res: NoteReaction[]; - }; - 'notes/reactions/create': { - req: { - noteId: Note['id']; - reaction: string; + 'notes/reactions/create': { + req: { + noteId: Note['id']; + reaction: string; + }; + res: null; }; - res: null; - }; - 'notes/reactions/delete': { - req: { - noteId: Note['id']; + 'notes/reactions/delete': { + req: { + noteId: Note['id']; + }; + res: null; + }; + 'notes/renotes': { + req: { + limit?: number; + sinceId?: Note['id']; + untilId?: Note['id']; + noteId: Note['id']; + }; + res: Note[]; + }; + 'notes/replies': { + req: { + limit?: number; + sinceId?: Note['id']; + untilId?: Note['id']; + noteId: Note['id']; + }; + res: Note[]; }; - res: null; - }; - 'notes/renotes': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - noteId: Note['id']; + 'notes/search-by-tag': { + req: TODO; + res: TODO; }; - res: Note[]; - }; - 'notes/replies': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - noteId: Note['id']; + 'notes/search': { + req: TODO; + res: TODO; }; - res: Note[]; - }; - 'notes/search-by-tag': { - req: TODO; - res: TODO; - }; - 'notes/search': { - req: TODO; - res: TODO; - }; - 'notes/show': { - req: { - noteId: Note['id']; + 'notes/show': { + req: { + noteId: Note['id']; + }; + res: Note; + }; + 'notes/state': { + req: TODO; + res: TODO; + }; + 'notes/timeline': { + req: { + limit?: number; + sinceId?: Note['id']; + untilId?: Note['id']; + sinceDate?: number; + untilDate?: number; + }; + res: Note[]; }; - res: Note; - }; - 'notes/state': { - req: TODO; - res: TODO; - }; - 'notes/timeline': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; + 'notes/unrenote': { + req: { + noteId: Note['id']; + }; + res: null; + }; + 'notes/user-list-timeline': { + req: { + listId: UserList['id']; + limit?: number; + sinceId?: Note['id']; + untilId?: Note['id']; + sinceDate?: number; + untilDate?: number; + }; + res: Note[]; }; - res: Note[]; - }; - 'notes/unrenote': { - req: { - noteId: Note['id']; + 'notes/watching/create': { + req: TODO; + res: TODO; }; - res: null; - }; - 'notes/user-list-timeline': { - req: { - listId: UserList['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; + 'notes/watching/delete': { + req: { + noteId: Note['id']; + }; + res: null; }; - res: Note[]; - }; - 'notes/watching/create': { - req: TODO; - res: TODO; - }; - 'notes/watching/delete': { - req: { - noteId: Note['id']; + 'notifications/create': { + req: { + body: string; + header?: string | null; + icon?: string | null; + }; + res: null; }; - res: null; - }; - 'notifications/create': { - req: { - body: string; - header?: string | null; - icon?: string | null; + 'notifications/mark-all-as-read': { + req: NoParams; + res: null; }; - res: null; - }; - 'notifications/mark-all-as-read': { - req: NoParams; - res: null; - }; - 'page-push': { - req: { - pageId: Page['id']; - event: string; - var?: any; + 'page-push': { + req: { + pageId: Page['id']; + event: string; + var?: any; + }; + res: null; }; - res: null; - }; - 'pages/create': { - req: TODO; - res: Page; - }; - 'pages/delete': { - req: { - pageId: Page['id']; + 'pages/create': { + req: TODO; + res: Page; }; - res: null; - }; - 'pages/featured': { - req: NoParams; - res: Page[]; - }; - 'pages/like': { - req: { - pageId: Page['id']; + 'pages/delete': { + req: { + pageId: Page['id']; + }; + res: null; }; - res: null; - }; - 'pages/show': { - req: { - pageId?: Page['id']; - name?: string; - username?: string; + 'pages/featured': { + req: NoParams; + res: Page[]; }; - res: Page; - }; - 'pages/unlike': { - req: { - pageId: Page['id']; + 'pages/like': { + req: { + pageId: Page['id']; + }; + res: null; }; - res: null; - }; - 'pages/update': { - req: TODO; - res: null; - }; - 'ping': { - req: NoParams; - res: { - pong: number; + 'pages/show': { + req: { + pageId?: Page['id']; + name?: string; + username?: string; + }; + res: Page; }; - }; - 'pinned-users': { - req: TODO; - res: TODO; - }; - 'promo/read': { - req: TODO; - res: TODO; - }; - 'request-reset-password': { - req: { - username: string; - email: string; + 'pages/unlike': { + req: { + pageId: Page['id']; + }; + res: null; }; - res: null; - }; - 'reset-password': { - req: { - token: string; - password: string; + 'pages/update': { + req: TODO; + res: null; }; - res: null; - }; - 'room/show': { - req: TODO; - res: TODO; - }; - 'room/update': { - req: TODO; - res: TODO; - }; - 'stats': { - req: NoParams; - res: Stats; - }; - 'server-info': { - req: NoParams; - res: ServerInfo; - }; - 'sw/register': { - req: TODO; - res: TODO; - }; - 'username/available': { - req: { - username: string; + 'ping': { + req: NoParams; + res: { + pong: number; + }; }; - res: { - available: boolean; + 'pinned-users': { + req: TODO; + res: TODO; }; - }; - 'users': { - req: { - limit?: number; - offset?: number; - sort?: UserSorting; - origin?: OriginType; + 'promo/read': { + req: TODO; + res: TODO; }; - res: User[]; - }; - 'users/clips': { - req: TODO; - res: TODO; - }; - 'users/followers': { - req: { - userId?: User['id']; - username?: User['username']; - host?: User['host'] | null; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; + 'request-reset-password': { + req: { + username: string; + email: string; + }; + res: null; }; - res: FollowingFollowerPopulated[]; - }; - 'users/following': { - req: { - userId?: User['id']; - username?: User['username']; - host?: User['host'] | null; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; + 'reset-password': { + req: { + token: string; + password: string; + }; + res: null; }; - res: FollowingFolloweePopulated[]; - }; - 'users/gallery/posts': { - req: TODO; - res: TODO; - }; - 'users/get-frequently-replied-users': { - req: TODO; - res: TODO; - }; - 'users/groups/create': { - req: TODO; - res: TODO; - }; - 'users/groups/delete': { - req: { - groupId: UserGroup['id']; + 'room/show': { + req: TODO; + res: TODO; }; - res: null; - }; - 'users/groups/invitations/accept': { - req: TODO; - res: TODO; - }; - 'users/groups/invitations/reject': { - req: TODO; - res: TODO; - }; - 'users/groups/invite': { - req: TODO; - res: TODO; - }; - 'users/groups/joined': { - req: TODO; - res: TODO; - }; - 'users/groups/owned': { - req: TODO; - res: TODO; - }; - 'users/groups/pull': { - req: TODO; - res: TODO; - }; - 'users/groups/show': { - req: TODO; - res: TODO; - }; - 'users/groups/transfer': { - req: TODO; - res: TODO; - }; - 'users/groups/update': { - req: TODO; - res: TODO; - }; - 'users/lists/create': { - req: { - name: string; + 'room/update': { + req: TODO; + res: TODO; }; - res: UserList; - }; - 'users/lists/delete': { - req: { - listId: UserList['id']; + 'stats': { + req: NoParams; + res: Stats; }; - res: null; - }; - 'users/lists/list': { - req: NoParams; - res: UserList[]; - }; - 'users/lists/pull': { - req: { - listId: UserList['id']; - userId: User['id']; + 'server-info': { + req: NoParams; + res: ServerInfo; }; - res: null; - }; - 'users/lists/push': { - req: { - listId: UserList['id']; - userId: User['id']; + 'sw/register': { + req: TODO; + res: TODO; }; - res: null; - }; - 'users/lists/show': { - req: { - listId: UserList['id']; + 'username/available': { + req: { + username: string; + }; + res: { + available: boolean; + }; }; - res: UserList; - }; - 'users/lists/update': { - req: { - listId: UserList['id']; - name: string; + 'users': { + req: { + limit?: number; + offset?: number; + sort?: UserSorting; + origin?: OriginType; + }; + res: User[]; + }; + 'users/clips': { + req: TODO; + res: TODO; + }; + 'users/followers': { + req: { + userId?: User['id']; + username?: User['username']; + host?: User['host'] | null; + limit?: number; + sinceId?: Following['id']; + untilId?: Following['id']; + }; + res: FollowingFollowerPopulated[]; + }; + 'users/following': { + req: { + userId?: User['id']; + username?: User['username']; + host?: User['host'] | null; + limit?: number; + sinceId?: Following['id']; + untilId?: Following['id']; + }; + res: FollowingFolloweePopulated[]; }; - res: UserList; - }; - 'users/notes': { - req: { - userId: User['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; + 'users/gallery/posts': { + req: TODO; + res: TODO; }; - res: Note[]; - }; - 'users/pages': { - req: TODO; - res: TODO; - }; - 'users/recommendation': { - req: TODO; - res: TODO; - }; - 'users/relation': { - req: TODO; - res: TODO; - }; - 'users/report-abuse': { - req: TODO; - res: TODO; - }; - 'users/search-by-username-and-host': { - req: TODO; - res: TODO; - }; - 'users/search': { - req: TODO; - res: TODO; - }; - 'users/show': { - req: ShowUserReq | { - userIds: User['id'][]; + 'users/get-frequently-replied-users': { + req: TODO; + res: TODO; }; - res: { - $switch: { - $cases: [ - [ - { - userIds: User['id'][]; - }, - UserDetailed[] - ] - ]; - $default: UserDetailed; + 'users/groups/create': { + req: TODO; + res: TODO; + }; + 'users/groups/delete': { + req: { + groupId: UserGroup['id']; }; + res: null; + }; + 'users/groups/invitations/accept': { + req: TODO; + res: TODO; + }; + 'users/groups/invitations/reject': { + req: TODO; + res: TODO; + }; + 'users/groups/invite': { + req: TODO; + res: TODO; + }; + 'users/groups/joined': { + req: TODO; + res: TODO; + }; + 'users/groups/owned': { + req: TODO; + res: TODO; + }; + 'users/groups/pull': { + req: TODO; + res: TODO; + }; + 'users/groups/show': { + req: TODO; + res: TODO; + }; + 'users/groups/transfer': { + req: TODO; + res: TODO; + }; + 'users/groups/update': { + req: TODO; + res: TODO; + }; + 'users/lists/create': { + req: { + name: string; + }; + res: UserList; + }; + 'users/lists/delete': { + req: { + listId: UserList['id']; + }; + res: null; + }; + 'users/lists/list': { + req: NoParams; + res: UserList[]; + }; + 'users/lists/pull': { + req: { + listId: UserList['id']; + userId: User['id']; + }; + res: null; + }; + 'users/lists/push': { + req: { + listId: UserList['id']; + userId: User['id']; + }; + res: null; + }; + 'users/lists/show': { + req: { + listId: UserList['id']; + }; + res: UserList; + }; + 'users/lists/update': { + req: { + listId: UserList['id']; + name: string; + }; + res: UserList; + }; + 'users/notes': { + req: { + userId: User['id']; + limit?: number; + sinceId?: Note['id']; + untilId?: Note['id']; + sinceDate?: number; + untilDate?: number; + }; + res: Note[]; + }; + 'users/pages': { + req: TODO; + res: TODO; + }; + 'users/recommendation': { + req: TODO; + res: TODO; + }; + 'users/relation': { + req: TODO; + res: TODO; + }; + 'users/report-abuse': { + req: TODO; + res: TODO; + }; + 'users/search-by-username-and-host': { + req: TODO; + res: TODO; + }; + 'users/search': { + req: TODO; + res: TODO; + }; + 'users/show': { + req: ShowUserReq | { + userIds: User['id'][]; + }; + res: { + $switch: { + $cases: [ + [ + { + userIds: User['id'][]; + }, + UserDetailed[] + ] + ]; + $default: UserDetailed; + }; + }; + }; + 'users/stats': { + req: TODO; + res: TODO; }; }; - 'users/stats': { - req: TODO; - res: TODO; - }; -}; -declare namespace entities { - export { - ID, - DateString, - User, - UserLite, - UserDetailed, - UserGroup, - UserList, - MeDetailed, - DriveFile, - DriveFolder, - GalleryPost, - Note, - NoteReaction, - Notification_2 as Notification, - MessagingMessage, - CustomEmoji, - LiteInstanceMetadata, - DetailedInstanceMetadata, - InstanceMetadata, - ServerInfo, - Stats, - Page, - PageEvent, - Announcement, - Antenna, - App, - AuthSession, - Ad, - Clip, - NoteFavorite, - FollowRequest, - Channel, - Following, - FollowingFolloweePopulated, - FollowingFollowerPopulated, - Blocking, - Instance, - Signin, - UserSorting, - OriginType + declare namespace entities { + export { + ID, + DateString, + User, + UserLite, + UserDetailed, + UserGroup, + UserList, + MeDetailed, + DriveFile, + DriveFolder, + GalleryPost, + Note, + NoteReaction, + Notification_2 as Notification, + MessagingMessage, + CustomEmoji, + LiteInstanceMetadata, + DetailedInstanceMetadata, + InstanceMetadata, + ServerInfo, + Stats, + Page, + PageEvent, + Announcement, + Antenna, + App, + AuthSession, + Ad, + Clip, + NoteFavorite, + FollowRequest, + Channel, + Following, + FollowingFolloweePopulated, + FollowingFollowerPopulated, + Blocking, + Instance, + Signin, + UserSorting, + OriginType + } } -} -export { entities } + export { entities } -// @public (undocumented) -type FetchLike = (input: string, init?: { - method?: string; - body?: string; - credentials?: RequestCredentials; - cache?: RequestCache; - headers: { - [key in string]: string; - }; -}) => Promise<{ - status: number; - json(): Promise; -}>; + // @public (undocumented) + type FetchLike = (input: string, init?: { + method?: string; + body?: string; + credentials?: RequestCredentials; + cache?: RequestCache; + headers: { + [key in string]: string; + }; + }) => Promise<{ + status: number; + json(): Promise; + }>; -// @public (undocumented) -export const ffVisibility: readonly ["public", "followers", "private"]; + // @public (undocumented) + export const ffVisibility: readonly ["public", "followers", "private"]; -// @public (undocumented) -type Following = { - id: ID; - createdAt: DateString; - followerId: User['id']; - followeeId: User['id']; -}; + // @public (undocumented) + type Following = { + id: ID; + createdAt: DateString; + followerId: User['id']; + followeeId: User['id']; + }; -// @public (undocumented) -type FollowingFolloweePopulated = Following & { - followee: UserDetailed; -}; + // @public (undocumented) + type FollowingFolloweePopulated = Following & { + followee: UserDetailed; + }; -// @public (undocumented) -type FollowingFollowerPopulated = Following & { - follower: UserDetailed; -}; + // @public (undocumented) + type FollowingFollowerPopulated = Following & { + follower: UserDetailed; + }; -// @public (undocumented) -type FollowRequest = { - id: ID; - follower: User; - followee: User; -}; + // @public (undocumented) + type FollowRequest = { + id: ID; + follower: User; + followee: User; + }; -// @public (undocumented) -type GalleryPost = { - id: ID; - createdAt: DateString; - updatedAt: DateString; - userId: User['id']; - user: User; - title: string; - description: string | null; - fileIds: DriveFile['id'][]; - files: DriveFile[]; - isSensitive: boolean; - likedCount: number; - isLiked?: boolean; -}; + // @public (undocumented) + type GalleryPost = { + id: ID; + createdAt: DateString; + updatedAt: DateString; + userId: User['id']; + user: User; + title: string; + description: string | null; + fileIds: DriveFile['id'][]; + files: DriveFile[]; + isSensitive: boolean; + likedCount: number; + isLiked?: boolean; + }; -// @public (undocumented) -type ID = string; + // @public (undocumented) + type ID = string; -// @public (undocumented) -type Instance = { - id: ID; - caughtAt: DateString; - host: string; - usersCount: number; - notesCount: number; - followingCount: number; - followersCount: number; - driveUsage: number; - driveFiles: number; - latestRequestSentAt: DateString | null; - latestStatus: number | null; - latestRequestReceivedAt: DateString | null; - lastCommunicatedAt: DateString; - isNotResponding: boolean; - isSuspended: boolean; - softwareName: string | null; - softwareVersion: string | null; - openRegistrations: boolean | null; - name: string | null; - description: string | null; - maintainerName: string | null; - maintainerEmail: string | null; - iconUrl: string | null; - faviconUrl: string | null; - themeColor: string | null; - infoUpdatedAt: DateString | null; -}; + // @public (undocumented) + type Instance = { + id: ID; + caughtAt: DateString; + host: string; + usersCount: number; + notesCount: number; + followingCount: number; + followersCount: number; + driveUsage: number; + driveFiles: number; + latestRequestSentAt: DateString | null; + latestStatus: number | null; + latestRequestReceivedAt: DateString | null; + lastCommunicatedAt: DateString; + isNotResponding: boolean; + isSuspended: boolean; + softwareName: string | null; + softwareVersion: string | null; + openRegistrations: boolean | null; + name: string | null; + description: string | null; + maintainerName: string | null; + maintainerEmail: string | null; + iconUrl: string | null; + faviconUrl: string | null; + themeColor: string | null; + infoUpdatedAt: DateString | null; + }; -// @public (undocumented) -type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata; + // @public (undocumented) + type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata; -// @public (undocumented) -function isAPIError(reason: any): reason is APIError; + // @public (undocumented) + function isAPIError(reason: any): reason is APIError; -// @public (undocumented) -type LiteInstanceMetadata = { - maintainerName: string | null; - maintainerEmail: string | null; - version: string; - name: string | null; - uri: string; - description: string | null; - langs: string[]; - tosUrl: string | null; - repositoryUrl: string; - feedbackUrl: string; - disableRegistration: boolean; - disableLocalTimeline: boolean; - disableGlobalTimeline: boolean; - driveCapacityPerLocalUserMb: number; - driveCapacityPerRemoteUserMb: number; - emailRequiredForSignup: boolean; - enableHcaptcha: boolean; - hcaptchaSiteKey: string | null; - enableRecaptcha: boolean; - recaptchaSiteKey: string | null; - enableTurnstile: boolean; - turnstileSiteKey: string | null; - swPublickey: string | null; - themeColor: string | null; - mascotImageUrl: string | null; - bannerUrl: string | null; - errorImageUrl: string | null; - iconUrl: string | null; - backgroundImageUrl: string | null; - logoImageUrl: string | null; - maxNoteTextLength: number; - enableEmail: boolean; - enableTwitterIntegration: boolean; - enableGithubIntegration: boolean; - enableDiscordIntegration: boolean; - enableServiceWorker: boolean; - emojis: CustomEmoji[]; - defaultDarkTheme: string | null; - defaultLightTheme: string | null; - ads: { - id: ID; - ratio: number; - place: string; - url: string; - imageUrl: string; - }[]; - translatorAvailable: boolean; -}; + // @public (undocumented) + type LiteInstanceMetadata = { + maintainerName: string | null; + maintainerEmail: string | null; + version: string; + name: string | null; + uri: string; + description: string | null; + langs: string[]; + tosUrl: string | null; + repositoryUrl: string; + feedbackUrl: string; + disableRegistration: boolean; + disableLocalTimeline: boolean; + disableGlobalTimeline: boolean; + driveCapacityPerLocalUserMb: number; + driveCapacityPerRemoteUserMb: number; + emailRequiredForSignup: boolean; + enableHcaptcha: boolean; + hcaptchaSiteKey: string | null; + enableRecaptcha: boolean; + recaptchaSiteKey: string | null; + enableTurnstile: boolean; + turnstileSiteKey: string | null; + swPublickey: string | null; + themeColor: string | null; + mascotImageUrl: string | null; + bannerUrl: string | null; + errorImageUrl: string | null; + iconUrl: string | null; + backgroundImageUrl: string | null; + logoImageUrl: string | null; + maxNoteTextLength: number; + enableEmail: boolean; + enableTwitterIntegration: boolean; + enableGithubIntegration: boolean; + enableDiscordIntegration: boolean; + enableServiceWorker: boolean; + emojis: CustomEmoji[]; + defaultDarkTheme: string | null; + defaultLightTheme: string | null; + ads: { + id: ID; + ratio: number; + place: string; + url: string; + imageUrl: string; + }[]; + translatorAvailable: boolean; + }; -// @public (undocumented) -type MeDetailed = UserDetailed & { - avatarId: DriveFile['id']; - bannerId: DriveFile['id']; - autoAcceptFollowed: boolean; - alwaysMarkNsfw: boolean; - carefulBot: boolean; - emailNotificationTypes: string[]; - hasPendingReceivedFollowRequest: boolean; - hasUnreadAnnouncement: boolean; - hasUnreadAntenna: boolean; - hasUnreadMentions: boolean; - hasUnreadMessagingMessage: boolean; - hasUnreadNotification: boolean; - hasUnreadSpecifiedNotes: boolean; - hideOnlineStatus: boolean; - injectFeaturedNote: boolean; - integrations: Record; - isDeleted: boolean; - isExplorable: boolean; - mutedWords: string[][]; - mutingNotificationTypes: string[]; - noCrawle: boolean; - receiveAnnouncementEmail: boolean; - usePasswordLessLogin: boolean; - [other: string]: any; -}; + // @public (undocumented) + type MeDetailed = UserDetailed & { + avatarId: DriveFile['id']; + bannerId: DriveFile['id']; + autoAcceptFollowed: boolean; + alwaysMarkNsfw: boolean; + carefulBot: boolean; + emailNotificationTypes: string[]; + hasPendingReceivedFollowRequest: boolean; + hasUnreadAnnouncement: boolean; + hasUnreadAntenna: boolean; + hasUnreadMentions: boolean; + hasUnreadMessagingMessage: boolean; + hasUnreadNotification: boolean; + hasUnreadSpecifiedNotes: boolean; + hideOnlineStatus: boolean; + injectFeaturedNote: boolean; + integrations: Record; + isDeleted: boolean; + isExplorable: boolean; + mutedWords: string[][]; + mutingNotificationTypes: string[]; + noCrawle: boolean; + receiveAnnouncementEmail: boolean; + usePasswordLessLogin: boolean; + [other: string]: any; + }; -// @public (undocumented) -type MessagingMessage = { - id: ID; - createdAt: DateString; - file: DriveFile | null; - fileId: DriveFile['id'] | null; - isRead: boolean; - reads: User['id'][]; - text: string | null; - user: User; - userId: User['id']; - recipient?: User | null; - recipientId: User['id'] | null; - group?: UserGroup | null; - groupId: UserGroup['id'] | null; -}; + // @public (undocumented) + type MessagingMessage = { + id: ID; + createdAt: DateString; + file: DriveFile | null; + fileId: DriveFile['id'] | null; + isRead: boolean; + reads: User['id'][]; + text: string | null; + user: User; + userId: User['id']; + recipient?: User | null; + recipientId: User['id'] | null; + group?: UserGroup | null; + groupId: UserGroup['id'] | null; + }; -// @public (undocumented) -export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; + // @public (undocumented) + export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; -// @public (undocumented) -type Note = { - id: ID; - createdAt: DateString; - text: string | null; - cw: string | null; - user: User; - userId: User['id']; - reply?: Note; - replyId: Note['id']; - renote?: Note; - renoteId: Note['id']; - files: DriveFile[]; - fileIds: DriveFile['id'][]; - visibility: 'public' | 'home' | 'followers' | 'specified'; - visibleUserIds?: User['id'][]; - localOnly?: boolean; - myReaction?: string; - reactions: Record; - renoteCount: number; - repliesCount: number; - poll?: { - expiresAt: DateString | null; - multiple: boolean; - choices: { - isVoted: boolean; - text: string; - votes: number; + // @public (undocumented) + type Note = { + id: ID; + createdAt: DateString; + text: string | null; + cw: string | null; + user: User; + userId: User['id']; + reply?: Note; + replyId: Note['id']; + renote?: Note; + renoteId: Note['id']; + event?: { + title: string; + start: DateString; + end: DateString | null; + metadata: Record; + }; + files: DriveFile[]; + fileIds: DriveFile['id'][]; + visibility: 'public' | 'home' | 'followers' | 'specified'; + visibleUserIds?: User['id'][]; + localOnly?: boolean; + myReaction?: string; + reactions: Record; + renoteCount: number; + repliesCount: number; + poll?: { + expiresAt: DateString | null; + multiple: boolean; + choices: { + isVoted: boolean; + text: string; + votes: number; + }[]; + }; + emojis: { + name: string; + url: string; }[]; + uri?: string; + url?: string; + isHidden?: boolean; }; - emojis: { - name: string; - url: string; - }[]; - uri?: string; - url?: string; - isHidden?: boolean; -}; -// @public (undocumented) -type NoteFavorite = { - id: ID; - createdAt: DateString; - noteId: Note['id']; - note: Note; -}; + // @public (undocumented) + type NoteFavorite = { + id: ID; + createdAt: DateString; + noteId: Note['id']; + note: Note; + }; -// @public (undocumented) -type NoteReaction = { - id: ID; - createdAt: DateString; - user: UserLite; - type: string; -}; + // @public (undocumented) + type NoteReaction = { + id: ID; + createdAt: DateString; + user: UserLite; + type: string; + }; -// @public (undocumented) -export const noteVisibilities: readonly ["public", "home", "followers", "specified"]; + // @public (undocumented) + export const noteVisibilities: readonly ["public", "home", "followers", "specified"]; -// @public (undocumented) -type Notification_2 = { - id: ID; - createdAt: DateString; - isRead: boolean; -} & ({ - type: 'reaction'; - reaction: string; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'reply'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'renote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'quote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'mention'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'pollVote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'follow'; - user: User; - userId: User['id']; -} | { - type: 'followRequestAccepted'; - user: User; - userId: User['id']; -} | { - type: 'receiveFollowRequest'; - user: User; - userId: User['id']; -} | { - type: 'groupInvited'; - invitation: UserGroup; - user: User; - userId: User['id']; -} | { - type: 'app'; - header?: string | null; - body: string; - icon?: string | null; -}); + // @public (undocumented) + type Notification_2 = { + id: ID; + createdAt: DateString; + isRead: boolean; + } & ({ + type: 'reaction'; + reaction: string; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'reply'; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'renote'; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'quote'; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'mention'; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'pollVote'; + user: User; + userId: User['id']; + note: Note; + } | { + type: 'follow'; + user: User; + userId: User['id']; + } | { + type: 'followRequestAccepted'; + user: User; + userId: User['id']; + } | { + type: 'receiveFollowRequest'; + user: User; + userId: User['id']; + } | { + type: 'groupInvited'; + invitation: UserGroup; + user: User; + userId: User['id']; + } | { + type: 'app'; + header?: string | null; + body: string; + icon?: string | null; + }); -// @public (undocumented) -export const notificationTypes: readonly ["follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"]; + // @public (undocumented) + export const notificationTypes: readonly ["follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"]; -// @public (undocumented) -type OriginType = 'combined' | 'local' | 'remote'; + // @public (undocumented) + type OriginType = 'combined' | 'local' | 'remote'; -// @public (undocumented) -type Page = { - id: ID; - createdAt: DateString; - updatedAt: DateString; - userId: User['id']; - user: User; - content: Record[]; - variables: Record[]; - title: string; - name: string; - summary: string | null; - hideTitleWhenPinned: boolean; - alignCenter: boolean; - font: string; - script: string; - eyeCatchingImageId: DriveFile['id'] | null; - eyeCatchingImage: DriveFile | null; - attachedFiles: any; - likedCount: number; - isLiked?: boolean; -}; + // @public (undocumented) + type Page = { + id: ID; + createdAt: DateString; + updatedAt: DateString; + userId: User['id']; + user: User; + content: Record[]; + variables: Record[]; + title: string; + name: string; + summary: string | null; + hideTitleWhenPinned: boolean; + alignCenter: boolean; + font: string; + script: string; + eyeCatchingImageId: DriveFile['id'] | null; + eyeCatchingImage: DriveFile | null; + attachedFiles: any; + likedCount: number; + isLiked?: boolean; + }; -// @public (undocumented) -type PageEvent = { - pageId: Page['id']; - event: string; - var: any; - userId: User['id']; - user: User; -}; + // @public (undocumented) + type PageEvent = { + pageId: Page['id']; + event: string; + var: any; + userId: User['id']; + user: User; + }; -// @public (undocumented) -export const permissions: string[]; + // @public (undocumented) + export const permissions: string[]; -// @public (undocumented) -type ServerInfo = { - machine: string; - cpu: { - model: string; - cores: number; - }; - mem: { - total: number; - }; - fs: { - total: number; - used: number; + // @public (undocumented) + type ServerInfo = { + machine: string; + cpu: { + model: string; + cores: number; + }; + mem: { + total: number; + }; + fs: { + total: number; + used: number; + }; }; -}; -// @public (undocumented) -type Signin = { - id: ID; - createdAt: DateString; - ip: string; - headers: Record; - success: boolean; -}; + // @public (undocumented) + type Signin = { + id: ID; + createdAt: DateString; + ip: string; + headers: Record; + success: boolean; + }; -// @public (undocumented) -type Stats = { - notesCount: number; - originalNotesCount: number; - usersCount: number; - originalUsersCount: number; - instances: number; - driveUsageLocal: number; - driveUsageRemote: number; -}; + // @public (undocumented) + type Stats = { + notesCount: number; + originalNotesCount: number; + usersCount: number; + originalUsersCount: number; + instances: number; + driveUsageLocal: number; + driveUsageRemote: number; + }; -// Warning: (ae-forgotten-export) The symbol "StreamEvents" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export class Stream extends EventEmitter { - constructor(origin: string, user: { - token: string; - } | null, options?: { - WebSocket?: any; - }); - // (undocumented) - close(): void; - // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts - // - // (undocumented) - disconnectToChannel(connection: NonSharedConnection): void; - // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "StreamEvents" needs to be exported by the entry point index.d.ts // - // (undocumented) - removeSharedConnection(connection: SharedConnection): void; - // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts - // - // (undocumented) - removeSharedConnectionPool(pool: Pool): void; - // (undocumented) - send(typeOrPayload: string): void; - // (undocumented) - send(typeOrPayload: string, payload: any): void; - // (undocumented) - send(typeOrPayload: Record | any[]): void; - // (undocumented) - state: 'initializing' | 'reconnecting' | 'connected'; - // (undocumented) - useChannel(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnection; -} + // @public (undocumented) + export class Stream extends EventEmitter { + constructor(origin: string, user: { + token: string; + } | null, options?: { + WebSocket?: any; + }); + // (undocumented) + close(): void; + // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts + // + // (undocumented) + disconnectToChannel(connection: NonSharedConnection): void; + // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts + // + // (undocumented) + removeSharedConnection(connection: SharedConnection): void; + // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts + // + // (undocumented) + removeSharedConnectionPool(pool: Pool): void; + // (undocumented) + send(typeOrPayload: string): void; + // (undocumented) + send(typeOrPayload: string, payload: any): void; + // (undocumented) + send(typeOrPayload: Record | any[]): void; + // (undocumented) + state: 'initializing' | 'reconnecting' | 'connected'; + // (undocumented) + useChannel(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnection; + } -// @public (undocumented) -type User = UserLite | UserDetailed; + // @public (undocumented) + type User = UserLite | UserDetailed; -// @public (undocumented) -type UserDetailed = UserLite & { - bannerBlurhash: string | null; - bannerColor: string | null; - bannerUrl: string | null; - birthday: string | null; - createdAt: DateString; - description: string | null; - ffVisibility: 'public' | 'followers' | 'private'; - fields: { - name: string; - value: string; - }[]; - followersCount: number; - followingCount: number; - hasPendingFollowRequestFromYou: boolean; - hasPendingFollowRequestToYou: boolean; - isAdmin: boolean; - isBlocked: boolean; - isBlocking: boolean; - isBot: boolean; - isCat: boolean; - isFollowed: boolean; - isFollowing: boolean; - isLocked: boolean; - isModerator: boolean; - isMuted: boolean; - isSilenced: boolean; - isSuspended: boolean; - lang: string | null; - lastFetchedAt?: DateString; - location: string | null; - notesCount: number; - pinnedNoteIds: ID[]; - pinnedNotes: Note[]; - pinnedPage: Page | null; - pinnedPageId: string | null; - publicReactions: boolean; - securityKeys: boolean; - twoFactorEnabled: boolean; - updatedAt: DateString | null; - uri: string | null; - url: string | null; -}; + // @public (undocumented) + type UserDetailed = UserLite & { + bannerBlurhash: string | null; + bannerColor: string | null; + bannerUrl: string | null; + birthday: string | null; + createdAt: DateString; + description: string | null; + ffVisibility: 'public' | 'followers' | 'private'; + fields: { + name: string; + value: string; + }[]; + followersCount: number; + followingCount: number; + hasPendingFollowRequestFromYou: boolean; + hasPendingFollowRequestToYou: boolean; + isAdmin: boolean; + isBlocked: boolean; + isBlocking: boolean; + isBot: boolean; + isCat: boolean; + isFollowed: boolean; + isFollowing: boolean; + isLocked: boolean; + isModerator: boolean; + isMuted: boolean; + isSilenced: boolean; + isSuspended: boolean; + lang: string | null; + lastFetchedAt?: DateString; + location: string | null; + notesCount: number; + pinnedNoteIds: ID[]; + pinnedNotes: Note[]; + pinnedPage: Page | null; + pinnedPageId: string | null; + publicReactions: boolean; + securityKeys: boolean; + twoFactorEnabled: boolean; + updatedAt: DateString | null; + uri: string | null; + url: string | null; + }; -// @public (undocumented) -type UserGroup = TODO_2; + // @public (undocumented) + type UserGroup = TODO_2; -// @public (undocumented) -type UserList = { - id: ID; - createdAt: DateString; - name: string; - userIds: User['id'][]; -}; + // @public (undocumented) + type UserList = { + id: ID; + createdAt: DateString; + name: string; + userIds: User['id'][]; + }; -// @public (undocumented) -type UserLite = { - id: ID; - username: string; - host: string | null; - name: string; - onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; - avatarUrl: string; - avatarBlurhash: string; - alsoKnownAs: string[]; - movedToUri: any; - emojis: { + // @public (undocumented) + type UserLite = { + id: ID; + username: string; + host: string | null; name: string; - url: string; - }[]; - instance?: { - name: Instance['name']; - softwareName: Instance['softwareName']; - softwareVersion: Instance['softwareVersion']; - iconUrl: Instance['iconUrl']; - faviconUrl: Instance['faviconUrl']; - themeColor: Instance['themeColor']; + onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; + avatarUrl: string; + avatarBlurhash: string; + alsoKnownAs: string[]; + movedToUri: any; + emojis: { + name: string; + url: string; + }[]; + instance?: { + name: Instance['name']; + softwareName: Instance['softwareName']; + softwareVersion: Instance['softwareVersion']; + iconUrl: Instance['iconUrl']; + faviconUrl: Instance['faviconUrl']; + themeColor: Instance['themeColor']; + }; }; -}; -// @public (undocumented) -type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; + // @public (undocumented) + type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; -// Warnings were encountered during analysis: -// -// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts -// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:596:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts -// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts + // Warnings were encountered during analysis: + // + // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts + // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts + // src/api.types.ts:602:27 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts + // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts -// (No @packageDocumentation comment for this package) + // (No @packageDocumentation comment for this package) -``` + ``` diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index aed9f5bf84..3ca1f3d36d 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -499,6 +499,7 @@ export type Endpoints = { 'notes/mentions': { req: { following?: boolean; limit?: number; sinceId?: Note['id']; untilId?: Note['id']; }; res: Note[]; }; 'notes/polls/recommendation': { req: TODO; res: TODO; }; 'notes/polls/vote': { req: { noteId: Note['id']; choice: number; }; res: null; }; + 'notes/events/search': { req: TODO; res: Note[]; }; 'notes/reactions': { req: { noteId: Note['id']; type?: string | null; limit?: number; }; res: NoteReaction[]; }; 'notes/reactions/create': { req: { noteId: Note['id']; reaction: string; }; res: null; }; 'notes/reactions/delete': { req: { noteId: Note['id']; }; res: null; }; From 9ac5053a0eec98c2fafa71d2781bc4df3f5a22f8 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Mon, 17 Apr 2023 20:29:40 -0400 Subject: [PATCH 010/308] Add simple display of event to note in UI --- .../api/endpoints/notes/events/search.ts | 2 +- packages/frontend/src/components/MkEvent.vue | 23 +++++++++++++++++++ packages/frontend/src/components/MkNote.vue | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/src/components/MkEvent.vue diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index 51c192714d..f286ed808d 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -100,7 +100,7 @@ export default class extends Endpoint { const matches = filters[f].filter(x => x !== null); if (matches.length < 1) throw new ApiError(meta.errors.invalidParam); query.andWhere(new Brackets((qb) => { - qb.where('event.metadata ->> :key IN (:...values)', { key: f, values: filters[f].filter(x => x !== null) }); + qb.where('event.metadata ->> :key IN (:...values)', { key: f, values: matches }); if (filters[f].filter(x => x === null).length > 0) { qb.orWhere('event.metadata ->> :key IS NULL', { key: f }); } diff --git a/packages/frontend/src/components/MkEvent.vue b/packages/frontend/src/components/MkEvent.vue new file mode 100644 index 0000000000..9fa351dcee --- /dev/null +++ b/packages/frontend/src/components/MkEvent.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 36ec778a14..8b87136966 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -45,6 +45,7 @@
+

@@ -144,6 +145,7 @@ import MkPoll from '@/components/MkPoll.vue'; import MkUsersTooltip from '@/components/MkUsersTooltip.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; +import MkEvent from '@/components/MkEvent.vue'; import { pleaseLogin } from '@/scripts/please-login'; import { focusPrev, focusNext } from '@/scripts/focus'; import { checkWordMute } from '@/scripts/check-word-mute'; From 753cef9413a7017363513883131da34f620476e9 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 20 Apr 2023 20:11:37 -0400 Subject: [PATCH 011/308] Add events tab to user details page --- .../api/endpoints/notes/events/search.ts | 16 +++--- packages/frontend/src/components/MkEvent.vue | 4 +- packages/frontend/src/pages/user/events.vue | 50 +++++++++++++++++++ packages/frontend/src/pages/user/index.vue | 8 ++- 4 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 packages/frontend/src/pages/user/events.vue diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index f286ed808d..7693cf35cc 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -35,7 +35,7 @@ export const meta = { message: 'Invalid Parameter', code: 'INVALID_PARAM', id: 'e70903d3-0aa2-44d5-a955-4de5723c603d', - } + }, }, } as const; @@ -50,7 +50,7 @@ export const paramDef = { nullable: true, description: 'The local host is represented with `null`.', }, - users: { type: 'array', nullable: true, items: { type: 'object', format: 'misskey:id' } }, + users: { type: 'array', nullable: true, items: { type: 'string', format: 'misskey:id' } }, sinceDate: { type: 'integer', nullable: true }, untilDate: { type: 'integer', nullable: true }, filters: { @@ -109,15 +109,19 @@ export default class extends Endpoint { } if (ps.sinceDate && ps.untilDate && ps.sinceDate > ps.untilDate) throw new ApiError(meta.errors.invalidParam); - const sinceDate = ps.sinceDate ? new Date(ps.sinceDate) : new Date(); - query.andWhere('event.start > :sinceDate', { sinceDate: sinceDate }) - .andWhere('(event.end IS NULL OR event.end > :sinceDate)', { sinceDate: sinceDate }); + + if (ps.sinceDate || ps.sortBy !== 'createdAt') { + const sinceDate = ps.sinceDate ? new Date(ps.sinceDate) : new Date(); + query.andWhere('event.start > :sinceDate', { sinceDate: sinceDate }) + .andWhere('(event.end IS NULL OR event.end > :sinceDate)', { sinceDate: sinceDate }); + } + if (ps.untilDate) { query.andWhere('event.start < :untilDate', { untilDate: new Date(ps.untilDate) }); } if (ps.sortBy === 'createdAt') { - query.orderBy('note.createdAt', 'ASC'); + query.orderBy('note.createdAt', 'DESC'); } else { query.orderBy('event.start', 'ASC'); } diff --git a/packages/frontend/src/components/MkEvent.vue b/packages/frontend/src/components/MkEvent.vue index 9fa351dcee..b38986a23c 100644 --- a/packages/frontend/src/components/MkEvent.vue +++ b/packages/frontend/src/components/MkEvent.vue @@ -3,8 +3,8 @@

Start: {{ note.event!.start }}
End: {{ note.event!.end }}
    -
  • - {{ k }}: {{ note.event!.metadata[k] }} +
  • + {{ k }}: {{ note.event!.metadata[k] }}
diff --git a/packages/frontend/src/pages/user/events.vue b/packages/frontend/src/pages/user/events.vue new file mode 100644 index 0000000000..3e6ffe1a52 --- /dev/null +++ b/packages/frontend/src/pages/user/events.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 03a226cc09..1482ecd11b 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -5,7 +5,8 @@
- + + @@ -32,6 +33,7 @@ import { $i } from '@/account'; const XHome = defineAsyncComponent(() => import('./home.vue')); const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue')); +const XEvent = defineAsyncComponent(() => import('./events.vue')); const XActivity = defineAsyncComponent(() => import('./activity.vue')); const XAchievements = defineAsyncComponent(() => import('./achievements.vue')); const XReactions = defineAsyncComponent(() => import('./reactions.vue')); @@ -74,6 +76,10 @@ const headerTabs = $computed(() => user ? [{ key: 'notes', title: i18n.ts.notes, icon: 'ti ti-pencil', +}, { + key: 'events', + title: 'Events', //i18n.ts.events, + icon: 'ti ti-calendar', }, { key: 'activity', title: i18n.ts.activity, From 07b3f19814b8a239280568ad70c62430b9892d11 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 20 Apr 2023 22:00:06 -0400 Subject: [PATCH 012/308] Change displayed date/time format for events --- packages/frontend/src/components/MkEvent.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/MkEvent.vue b/packages/frontend/src/components/MkEvent.vue index b38986a23c..352e10c77b 100644 --- a/packages/frontend/src/components/MkEvent.vue +++ b/packages/frontend/src/components/MkEvent.vue @@ -1,7 +1,7 @@ @@ -49,6 +66,7 @@ import MkUserList from '@/components/MkUserList.vue'; import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; +import MkSelect from '@/components/MkSelect.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import * as os from '@/os'; @@ -71,8 +89,10 @@ let key = $ref(''); let tab = $ref('note'); let searchQuery = $ref(''); let searchOrigin = $ref('combined'); +let eventSort = $ref('startDate'); let notePagination = $ref(); let userPagination = $ref(); +let eventPagination = $ref(); const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes)); @@ -85,7 +105,8 @@ onMounted(() => { async function search() { const query = searchQuery.toString().trim(); - if (query == null || query === '') return; + // only notes/users search use the query string. event does not use it + if ((query == null || query === '') && tab !== 'event') return; if (query.startsWith('https://')) { const promise = os.api('ap/show', { @@ -123,6 +144,19 @@ async function search() { origin: searchOrigin, }, }; + } else if (tab === 'event') { + eventPagination = { + endpoint: 'notes/events/search', + limit: 10, + params: { + sortBy: eventSort, + }, + }; + + // only refresh search on query/key change + key = JSON.stringify(eventPagination); + + return; } key = query; @@ -138,6 +172,10 @@ const headerTabs = $computed(() => [{ key: 'user', title: i18n.ts.users, icon: 'ti ti-users', +}, { + key: 'event', + title: 'Events', + icon: 'ti ti-calendar', }]); definePageMetadata(computed(() => ({ diff --git a/packages/frontend/src/pages/user/events.vue b/packages/frontend/src/pages/user/events.vue index 3e6ffe1a52..41b0bd50ca 100644 --- a/packages/frontend/src/pages/user/events.vue +++ b/packages/frontend/src/pages/user/events.vue @@ -3,11 +3,11 @@ - + @@ -23,7 +23,7 @@ const props = defineProps<{ user: misskey.entities.UserDetailed; }>(); -const include = ref(null); +const include = ref('upcoming'); const pagination = { endpoint: 'notes/events/search' as const, From 4ecdff8c2dffe793c46330300174e77199e63d3e Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 23 Apr 2023 13:53:44 -0400 Subject: [PATCH 017/308] Add query string to event search --- .../src/server/api/endpoints/notes/events/search.ts | 9 +++++++++ packages/frontend/src/pages/search.vue | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index 7693cf35cc..70a1df6475 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -9,6 +9,7 @@ import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['notes'], @@ -42,6 +43,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { + query: { type: 'string', nullable: true }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, @@ -93,6 +95,13 @@ export default class extends Endpoint { .innerJoinAndSelect(Event, 'event', 'event.noteId = note.id') .innerJoinAndSelect('note.user', 'user'); + if (ps.query && ps.query.trim() !== '') { + query.andWhere(new Brackets((qb) => { + const q = (ps.query ?? '').trim(); + qb.where('event.title ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .orWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); + })); + } if (ps.filters) { const filters: Record = ps.filters; diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index fe7641cc65..ff33773319 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -42,6 +42,9 @@
+ + + @@ -105,7 +108,7 @@ onMounted(() => { async function search() { const query = searchQuery.toString().trim(); - // only notes/users search use the query string. event does not use it + // only notes/users search require the query string if ((query == null || query === '') && tab !== 'event') return; if (query.startsWith('https://')) { @@ -149,6 +152,7 @@ async function search() { endpoint: 'notes/events/search', limit: 10, params: { + query: searchQuery, sortBy: eventSort, }, }; From a57c79061ca8446a3fcdb02f18b89f51e3b9daf9 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 23 Apr 2023 14:51:54 -0400 Subject: [PATCH 018/308] Add date range to event search page --- packages/frontend/src/pages/search.vue | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index ff33773319..b8ff55f15f 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -50,6 +50,14 @@ +
+ + + + + + +
{{ i18n.ts.search }}
@@ -96,6 +104,8 @@ let eventSort = $ref('startDate'); let notePagination = $ref(); let userPagination = $ref(); let eventPagination = $ref(); +let startDate = $ref(null); +let endDate = $ref(null); const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes)); @@ -152,8 +162,10 @@ async function search() { endpoint: 'notes/events/search', limit: 10, params: { - query: searchQuery, + query: !searchQuery ? undefined : searchQuery, sortBy: eventSort, + sinceDate: startDate ? (new Date(startDate)).getTime() : undefined, + untilDate: endDate ? (new Date(endDate)).getTime() + 1000 * 3600 * 24 : undefined, }, }; From b3fb7c9271f88d8e7ddbadaa575fa06004f16fff Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 23 Apr 2023 15:55:22 -0400 Subject: [PATCH 019/308] Update API types --- .../api/endpoints/notes/events/search.ts | 5 ----- packages/misskey-js/src/api.types.ts | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index 70a1df6475..55689b4e18 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -47,11 +47,6 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', - }, users: { type: 'array', nullable: true, items: { type: 'string', format: 'misskey:id' } }, sinceDate: { type: 'integer', nullable: true }, untilDate: { type: 'integer', nullable: true }, diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index 3ca1f3d36d..ad40528cf7 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -488,6 +488,12 @@ export type Endpoints = { expiresAt?: null | number; expiredAfter?: null | number; }; + event?: null | { + title: string; + start: number; + end?: null | number; + metadata: Record; + } }; res: { createdNote: Note }; }; 'notes/delete': { req: { noteId: Note['id']; }; res: null; }; 'notes/favorites/create': { req: { noteId: Note['id']; }; res: null; }; @@ -499,7 +505,17 @@ export type Endpoints = { 'notes/mentions': { req: { following?: boolean; limit?: number; sinceId?: Note['id']; untilId?: Note['id']; }; res: Note[]; }; 'notes/polls/recommendation': { req: TODO; res: TODO; }; 'notes/polls/vote': { req: { noteId: Note['id']; choice: number; }; res: null; }; - 'notes/events/search': { req: TODO; res: Note[]; }; + 'notes/events/search': { req: { + query?: string; + sinceId?: Note['id']; + untilId?: Note['id']; + limit?: number; + users?: User['id'][]; + sinceDate?: number; + untilDate?: number; + sortBy?: 'startDate' | 'craetedAt'; + filters?: Record; + }; res: Note[]; }; 'notes/reactions': { req: { noteId: Note['id']; type?: string | null; limit?: number; }; res: NoteReaction[]; }; 'notes/reactions/create': { req: { noteId: Note['id']; reaction: string; }; res: null; }; 'notes/reactions/delete': { req: { noteId: Note['id']; }; res: null; }; From 661c83a23f81c0372549660c20108e9033acff9b Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sun, 23 Apr 2023 23:29:47 -0400 Subject: [PATCH 020/308] bug fixes and localization: - add localization strings - fixed duplicates in search (use offset when sorting by startDate) --- locales/ja-JP.yml | 12 ++++++++++++ .../server/api/endpoints/notes/events/search.ts | 2 ++ packages/frontend/src/components/MkEvent.vue | 1 - packages/frontend/src/components/MkEventEditor.vue | 14 +++++++------- packages/frontend/src/pages/search.vue | 13 +++++++------ packages/frontend/src/pages/user/events.vue | 5 +++-- packages/frontend/src/pages/user/index.vue | 2 +- packages/misskey-js/src/api.types.ts | 1 + 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4c5bb60e0c..77691236bc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -993,6 +993,18 @@ noteIdOrUrl: "ノートIDまたはURL" accountMigration: "アカウントの引っ越し" accountMoved: "このユーザーは新しいアカウントに引っ越しました:" forceShowAds: "常に広告を表示する" +event: "イベント" +events: "イベント" +reverseChronological: "倒叙" + +_event: + title: "題" + startDate: "開始日" + endDate: "終了日" + startTime: "開始時刻" + endTime: "終了時刻" + detailName: "属性" + detailValue: "値" _accountMigration: moveTo: "このアカウントを新しいアカウントに引っ越す" diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index 55689b4e18..54d5c3af65 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -47,6 +47,7 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + offset: { type: 'integer', default: 0 }, users: { type: 'array', nullable: true, items: { type: 'string', format: 'misskey:id' } }, sinceDate: { type: 'integer', nullable: true }, untilDate: { type: 'integer', nullable: true }, @@ -134,6 +135,7 @@ export default class extends Endpoint { if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateBlockedUserQuery(query, me); + if (ps.offset) query.skip(ps.offset); const notes = await query.take(ps.limit).getMany(); return await this.noteEntityService.packMany(notes, me); diff --git a/packages/frontend/src/components/MkEvent.vue b/packages/frontend/src/components/MkEvent.vue index 352e10c77b..1933515f35 100644 --- a/packages/frontend/src/components/MkEvent.vue +++ b/packages/frontend/src/components/MkEvent.vue @@ -11,7 +11,6 @@ From 47f43e897b7785a1940228a2809d00e534199235 Mon Sep 17 00:00:00 2001 From: ssmucny Date: Thu, 18 May 2023 22:22:29 -0400 Subject: [PATCH 029/308] Change events to use schema.org/Event for metadata --- packages/backend/src/models/entities/Event.ts | 9 ++++++--- .../src/server/api/endpoints/notes/events/search.ts | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/models/entities/Event.ts b/packages/backend/src/models/entities/Event.ts index 9d986b7c85..57d2b50543 100644 --- a/packages/backend/src/models/entities/Event.ts +++ b/packages/backend/src/models/entities/Event.ts @@ -35,10 +35,13 @@ export class Event { public title: string; @Column('jsonb', { - default: {}, - comment: 'metadata mapping for event with more user configurable optional information', + default: { + '@context': 'https://schema.org/', + '@type': 'Event', + }, + comment: 'metadata object describing the event. Follows https://schema.org/Event', }) - public metadata: Record; + public metadata: unknown; //#region Denormalized fields @Column('enum', { diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index f0b04ade25..dfc614039c 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -8,8 +8,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; -import { ApiError } from '../../../error.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['notes'], @@ -58,7 +58,7 @@ export const paramDef = { items: { type: 'object', properties: { - key: { type: 'string', description: 'the metadata property to filter on.' }, + key: { type: 'array', items: { type: 'string', nullable: false }, description: 'the metadata string property to filter on. Can filter on nested properties using an array. such as `["location", "postalCode"]`.' }, values: { type: 'array', items: { type: 'string', nullable: true }, description: 'The values to match the metadata against (case insensitive regex). Each item in this array is applied as an OR. Include null to indicate match on missing metadata' }, }, }, @@ -67,6 +67,10 @@ export const paramDef = { }, } as const; +function notAlphaNumeric(s: string) { + return null !== s.match(/[^\w]/); +} + // eslint-disable-next-line import/no-default-export @Injectable() export default class extends Endpoint { @@ -112,13 +116,14 @@ export default class extends Endpoint { filters.forEach(f => { if (!f.key || !f.values) throw new ApiError(meta.errors.invalidParam); const filterKey = f.key; + if (filterKey.some(notAlphaNumeric)) throw new ApiError(meta.errors.invalidParam); // schema properties don't have special characters const filterValues = f.values as (string | null)[]; const matches = filterValues.filter(x => x !== null) as string[]; const hasNull = filterValues.length !== matches.length; if (matches.length < 1) throw new ApiError(meta.errors.invalidParam); query.andWhere(new Brackets((qb) => { // regex match metadata values case insensitive - qb.where('event.metadata ->> :key ~* :values', { key: filterKey, values: `(${ matches.map(m => m.trim()).filter(m => m.length).join('|') })` }); + qb.where('event.metadata #>> :key ~* :values', { key: `{${filterKey.join(',')}}`, values: `(${ matches.map(m => m.trim()).filter(m => m.length).join('|') })` }); if (hasNull) { qb.orWhere('NOT (event.metadata ? :key)', { key: filterKey }); } From b7c7e4d32da3e4063958b18bf340dc7b45e2072f Mon Sep 17 00:00:00 2001 From: ssmucny Date: Mon, 22 May 2023 22:09:12 -0400 Subject: [PATCH 030/308] Add Event schema to metadata - add types from https://schema.org/Event - updated MkEvent to display new fields - minor refactoring --- .../core/activitypub/models/ApEventService.ts | 8 +-- packages/backend/src/models/entities/Event.ts | 43 ++++++++++++- .../api/endpoints/notes/events/search.ts | 2 +- packages/frontend/src/components/MkEvent.vue | 63 ++++++++++++++++++- packages/misskey-js/src/api.types.ts | 2 +- 5 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/core/activitypub/models/ApEventService.ts b/packages/backend/src/core/activitypub/models/ApEventService.ts index 81d40a88f6..b525f09ae0 100644 --- a/packages/backend/src/core/activitypub/models/ApEventService.ts +++ b/packages/backend/src/core/activitypub/models/ApEventService.ts @@ -3,13 +3,13 @@ import { DI } from '@/di-symbols.js'; import type { EventsRepository, NotesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { IEvent } from '@/models/entities/Event.js'; import { isEvent } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApResolverService } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject } from '../type.js'; -import { bindThis } from '@/decorators.js'; -import { IEvent } from '@/models/entities/Event.js'; @Injectable() export class ApEventService { @@ -32,8 +32,8 @@ export class ApEventService { } @bindThis - public async extractEventFromNote(source: string | IObject, resolver?: Resolver): Promise { - if (resolver == null) resolver = this.apResolverService.createResolver(); + public async extractEventFromNote(source: string | IObject, resolverParam?: Resolver): Promise { + const resolver = resolverParam ?? this.apResolverService.createResolver(); const note = await resolver.resolve(source); diff --git a/packages/backend/src/models/entities/Event.ts b/packages/backend/src/models/entities/Event.ts index 57d2b50543..bad8b5d8ac 100644 --- a/packages/backend/src/models/entities/Event.ts +++ b/packages/backend/src/models/entities/Event.ts @@ -35,13 +35,13 @@ export class Event { public title: string; @Column('jsonb', { - default: { + default: { '@context': 'https://schema.org/', '@type': 'Event', }, comment: 'metadata object describing the event. Follows https://schema.org/Event', }) - public metadata: unknown; + public metadata: EventSchema; //#region Denormalized fields @Column('enum', { @@ -74,9 +74,46 @@ export class Event { } } +export type EventSchema = { + '@context': 'https://schema.org'; + '@type': 'Event'; + name?: string; + url?: string; + description?: string; + audience?: { + '@type': 'Audience'; + name: string; + }; + doorTime?: string; + startDate?: string; + endDate?: string; + eventStatus?: 'https://schema.org/EventCancelled' | 'https://schema.org/EventMovedOnline' | 'https://schema.org/EventPostponed' | 'https://schema.org/EventRescheduled' | 'https://schema.org/EventScheduled'; + inLanguage?: string; + isAccessibleForFree?: boolean; + keywords?: string; + location?: string; + offers?: { + '@type': 'Offer'; + price?: string; + priceCurrency?: string; + availabilityStarts?: string; + availabilityEnds?: string; + url?: string; + }; + organizer?: { + name: string; + sameAs?: string; // ie. URL to website/social + }; + performer?: { + name: string; + sameAs?: string; // ie. URL to website/social + }[]; + typicalAgeRange?: string; +} + export type IEvent = { start: Date; end: Date | null title: string; - metadata: Record; + metadata: EventSchema; } diff --git a/packages/backend/src/server/api/endpoints/notes/events/search.ts b/packages/backend/src/server/api/endpoints/notes/events/search.ts index dfc614039c..737867b1ef 100644 --- a/packages/backend/src/server/api/endpoints/notes/events/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/events/search.ts @@ -67,7 +67,7 @@ export const paramDef = { }, } as const; -function notAlphaNumeric(s: string) { +function notAlphaNumeric(s: string): boolean { return null !== s.match(/[^\w]/); } diff --git a/packages/frontend/src/components/MkEvent.vue b/packages/frontend/src/components/MkEvent.vue index 3b445ce1b5..a3dd01e796 100644 --- a/packages/frontend/src/components/MkEvent.vue +++ b/packages/frontend/src/components/MkEvent.vue @@ -15,9 +15,66 @@ - - From 803e077e60948bb2472fa4d1282da3f19e10d0f8 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Thu, 15 Jun 2023 17:08:12 +0900 Subject: [PATCH 217/308] =?UTF-8?q?enhance(client):=20Renote=E6=99=82?= =?UTF-8?q?=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E3=83=88?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=83=88=E3=83=9D=E3=83=83=E3=83=97=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=81=AB=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + packages/frontend/src/components/MkNote.vue | 4 +- .../src/components/MkNoteDetailed.vue | 4 +- .../frontend/src/components/MkNoteToast.vue | 75 +++++++++++++++++++ packages/frontend/src/os.ts | 7 ++ 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 packages/frontend/src/components/MkNoteToast.vue diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index fdec985d0e..9d8a4a2bbf 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -68,6 +68,7 @@ - 네비게이션 메뉴 편집 페이지 UI 개선 ([shrimpia bf8c84d](https://github.com/shrimpia/misskey/commit/bf8c84d299bd06cb21e18a9fe68ff9abc11fd4a0)) - 「노트 본문에 번역 버튼 표시」를 선택 사항으로 설정 - 답글도 번역할 수 있도록 개선 +- 리노트 했을 때 뜨는 토스트 팝업에 아이콘 추가 - Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제 - Fix: (Friendly) 타임라인이 변경되었을 때 네비게이션 바의 인디케이터가 사라지지 않는 문제 - Fix: (Friendly) 모바일에서 헤더가 사라졌을 때 프로필 아이콘의 높이가 잘못 설정되는 문제 diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index a42659660d..2f7b4ecf3e 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -306,7 +306,7 @@ function renote(viaKeyboard = false) { renoteId: appearNote.id, channelId: appearNote.channelId, }).then(() => { - os.toast(i18n.ts.renoted); + os.noteToast(i18n.ts.renoted); }); }, }, { @@ -336,7 +336,7 @@ function renote(viaKeyboard = false) { os.api('notes/create', { renoteId: appearNote.id, }).then(() => { - os.toast(i18n.ts.renoted); + os.noteToast(i18n.ts.renoted); }); }, }, { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index cce0898b38..b93899e2f1 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -274,7 +274,7 @@ function renote(viaKeyboard = false) { renoteId: appearNote.id, channelId: appearNote.channelId, }).then(() => { - os.toast(i18n.ts.renoted); + os.noteToast(i18n.ts.renoted); }); }, }, { @@ -304,7 +304,7 @@ function renote(viaKeyboard = false) { os.api('notes/create', { renoteId: appearNote.id, }).then(() => { - os.toast(i18n.ts.renoted); + os.noteToast(i18n.ts.renoted); }); }, }, { diff --git a/packages/frontend/src/components/MkNoteToast.vue b/packages/frontend/src/components/MkNoteToast.vue new file mode 100644 index 0000000000..a2520bb74e --- /dev/null +++ b/packages/frontend/src/components/MkNoteToast.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 7937caf536..1c4a2fc698 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -11,6 +11,7 @@ import MkPostFormDialog from '@/components/MkPostFormDialog.vue'; import MkWaitingDialog from '@/components/MkWaitingDialog.vue'; import MkPageWindow from '@/components/MkPageWindow.vue'; import MkToast from '@/components/MkToast.vue'; +import MkNoteToast from '@/components/MkNoteToast.vue'; import MkWelcomeToast from '@/components/MkWelcomeToast.vue'; import MkDialog from '@/components/MkDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; @@ -179,6 +180,12 @@ export function toast(message: string) { }, {}, 'closed'); } +export function noteToast(message: string) { + popup(MkNoteToast, { + message, + }, {}, 'closed'); +} + export function welcomeToast(message: string) { popup(MkWelcomeToast, { message, From f8560eca2ecee2ea4699dc1616d3097c3641535f Mon Sep 17 00:00:00 2001 From: NoriDev Date: Thu, 15 Jun 2023 17:12:35 +0900 Subject: [PATCH 218/308] improve ko-KR.yml --- locales/ko-KR.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index ebeafe285c..6c3ad016f9 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1098,8 +1098,8 @@ specifyUser: "사용자 지정" failedToPreviewUrl: "미리 볼 수 없음" update: "업데이트" rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할" -rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면, 누구나 이 이모지를 리액션으로 사용할 수 있습니다." -rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 합니다." +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면 누구나 이 이모지를 리액션으로 사용할 수 있어요." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 해요." cancelReactionConfirm: "리액션을 취소하시겠습니까?" changeReactionConfirm: "리액션을 변경하시겠습니까?" later: "나중에" @@ -1109,13 +1109,13 @@ installed: "설치됨" translateProfile: "프로필 번역하기" _cherrypick: function: "고유 기능" - functionDescription: "CherryPick이 추가하는 고유 기능을 활성화/비활성화합니다." + functionDescription: "CherryPick이 추가하는 고유 기능을 활성화/비활성화할 수 있어요." nickname: "닉네임 기능" - nicknameDescription: "유저 페이지에서 이름을 클릭 또는 탭하여 원하는 이름으로 변경할 수 있습니다. 변경은 자신에게만 반영됩니다.\n자주 이름을 바꾸는 사용자를 식별하는 데 사용할 수 있습니다." + nicknameDescription: "유저 페이지에서 이름을 클릭 또는 탭하여 원하는 이름으로 변경할 수 있어요. 변경한 이름은 자신에게만 반영돼요.\n자주 이름을 바꾸는 사용자를 식별하는 데 사용할 수 있어요." patch: "패치" - patchDescription: "Misskey의 기능을 변경합니다." + patchDescription: "Misskey의 기능을 변경해요." infoButtonForNoteActions: "노트에 자세히 버튼 표시" - infoButtonForNoteActionsDescription: "「노트 액션 버튼을 마우스를 올렸을 때에만 표시」기능을 켰을 때만 적용됩니다." + infoButtonForNoteActionsDescription: "「노트 액션 버튼을 마우스를 올렸을 때에만 표시」기능을 켰을 때만 적용돼요." rememberPostFormToggleState: "노트 작성 화면에서 본문 미리보기 활성화 상태 기억" reactableRemoteReaction: "서버에 리모트 이모지와 이름이 같은 이모지가 있으면 리모트 이모지에도 반응할 수 있음" showFollowingMessageInsteadOfButton: "이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음" From 713e3e0197cdf17e82783b12131519a89b389718 Mon Sep 17 00:00:00 2001 From: yuriha-chan Date: Thu, 15 Jun 2023 22:30:08 +0900 Subject: [PATCH 219/308] Suppress ReferenceError on some environments (i.e. older iOS) --- packages/frontend/src/workers/test-webgl2.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts index 4769524d9c..0c07094ab3 100644 --- a/packages/frontend/src/workers/test-webgl2.ts +++ b/packages/frontend/src/workers/test-webgl2.ts @@ -1,3 +1,7 @@ +if (!OffscreenCanvas) { + postMessage({ result: false }); + return; +} const canvas = new OffscreenCanvas(1, 1); const gl = canvas.getContext('webgl2'); if (gl) { From fc54e56ca70a2d7e9104aa1077a1979db98cd402 Mon Sep 17 00:00:00 2001 From: yuriha-chan Date: Thu, 15 Jun 2023 23:09:19 +0900 Subject: [PATCH 220/308] fix --- packages/frontend/src/workers/test-webgl2.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts index 0c07094ab3..2b489062cf 100644 --- a/packages/frontend/src/workers/test-webgl2.ts +++ b/packages/frontend/src/workers/test-webgl2.ts @@ -1,11 +1,11 @@ -if (!OffscreenCanvas) { - postMessage({ result: false }); - return; -} -const canvas = new OffscreenCanvas(1, 1); -const gl = canvas.getContext('webgl2'); -if (gl) { - postMessage({ result: true }); +if (window.OffscreenCanvas) { + const canvas = new OffscreenCanvas(1, 1); + const gl = canvas.getContext('webgl2'); + if (gl) { + postMessage({ result: true }); + } else { + postMessage({ result: false }); + } } else { postMessage({ result: false }); } From 211833889883560642b87d1bb2185a9fc5be57ee Mon Sep 17 00:00:00 2001 From: yuriha-chan Date: Thu, 15 Jun 2023 23:20:27 +0900 Subject: [PATCH 221/308] fix --- packages/frontend/src/workers/test-webgl2.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts index 2b489062cf..bdd582788f 100644 --- a/packages/frontend/src/workers/test-webgl2.ts +++ b/packages/frontend/src/workers/test-webgl2.ts @@ -1,4 +1,5 @@ -if (window.OffscreenCanvas) { +try { + // throw ReferenceError in Safari <= 16.3 const canvas = new OffscreenCanvas(1, 1); const gl = canvas.getContext('webgl2'); if (gl) { @@ -6,6 +7,7 @@ if (window.OffscreenCanvas) { } else { postMessage({ result: false }); } -} else { +} catch (e) { + // assert(e instanceof ReferenceError) postMessage({ result: false }); } From 68b3305a9f77d3322521c8c262e86f8ac1af4172 Mon Sep 17 00:00:00 2001 From: yuriha-chan Date: Thu, 15 Jun 2023 23:49:44 +0900 Subject: [PATCH 222/308] lint --- packages/frontend/src/workers/test-webgl2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts index bdd582788f..5027fa0784 100644 --- a/packages/frontend/src/workers/test-webgl2.ts +++ b/packages/frontend/src/workers/test-webgl2.ts @@ -7,7 +7,7 @@ try { } else { postMessage({ result: false }); } -} catch (e) { +} catch (err) { // assert(e instanceof ReferenceError) postMessage({ result: false }); } From 642650909d73d67d5d804a6f68930f7f172bc657 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Fri, 16 Jun 2023 16:14:13 +0900 Subject: [PATCH 223/308] =?UTF-8?q?enhance(client):=20=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E6=83=85?= =?UTF-8?q?=E5=A0=B1=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E9=A0=86=E5=BA=8F=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + packages/frontend/src/pages/settings/general.vue | 4 ++-- packages/frontend/src/store.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 9d8a4a2bbf..cd2fdf7601 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -69,6 +69,7 @@ - 「노트 본문에 번역 버튼 표시」를 선택 사항으로 설정 - 답글도 번역할 수 있도록 개선 - 리노트 했을 때 뜨는 토스트 팝업에 아이콘 추가 +- 노트의 서버 정보 옵션의 순서를 변경 - Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제 - Fix: (Friendly) 타임라인이 변경되었을 때 네비게이션 바의 인디케이터가 사라지지 않는 문제 - Fix: (Friendly) 모바일에서 헤더가 사라졌을 때 프로필 아이콘의 높이가 잘못 설정되는 문제 diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index a56fb0c40e..be5b54399d 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -64,9 +64,9 @@ - - + + diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 9d09ff33db..34b09b6f19 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -238,7 +238,7 @@ export const defaultStore = markRaw(new Storage('base', { }, instanceTicker: { where: 'device', - default: 'remote' as 'none' | 'remote' | 'always', + default: 'remote' as 'always' | 'remote' | 'none', }, reactionPickerSize: { where: 'device', From 04d9cc7e3d377f0cbcdc26c705ce6cadf6ef8c81 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Fri, 16 Jun 2023 16:42:13 +0900 Subject: [PATCH 224/308] tweak 63afde0b --- .../frontend/src/components/MkCwButton.vue | 9 ++- packages/frontend/src/components/MkNote.vue | 72 +++++++++---------- .../src/components/MkNoteDetailed.vue | 42 ++++++----- .../frontend/src/components/MkNoteSimple.vue | 27 +++---- 4 files changed, 80 insertions(+), 70 deletions(-) diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue index 71b89f4edf..4a9602de42 100644 --- a/packages/frontend/src/components/MkCwButton.vue +++ b/packages/frontend/src/components/MkCwButton.vue @@ -36,14 +36,17 @@ const toggle = () => { diff --git a/packages/frontend/src/pages/my-groups/index.vue b/packages/frontend/src/pages/my-groups/index.vue index 1d043c4c2e..e296112b27 100644 --- a/packages/frontend/src/pages/my-groups/index.vue +++ b/packages/frontend/src/pages/my-groups/index.vue @@ -2,35 +2,35 @@ -
- - -
{{ group.name }}
+
+ + +
{{ group.name }}
-
- - -
{{ group.name }}
+
+ + +
{{ group.name }}
-
- {{ i18n.ts.leaveGroup }} +
+ {{ i18n.ts.leaveGroup }}
-
+
- -
{{ invitation.group.name }}
+ +
{{ invitation.group.name }}
-
- {{ i18n.ts.accept }} - {{ i18n.ts.reject }} +
+ {{ i18n.ts.accept }} + {{ i18n.ts.reject }}
@@ -138,28 +138,34 @@ definePageMetadata({ - From 3ccefa7b00f637584cbc9e2d5045d53b8b4cef39 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 17 Jun 2023 03:12:24 +0900 Subject: [PATCH 251/308] delete description --- .../server/api/endpoints/users/groups/invitations/accept.ts | 4 +--- .../server/api/endpoints/users/groups/invitations/reject.ts | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index f154a57f61..08b3da7a1f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -13,8 +13,6 @@ export const meta = { kind: 'write:user-groups', - description: 'Join a group the authenticated user has been invited to.', - errors: { noSuchInvitation: { message: 'No such invitation.', @@ -66,7 +64,7 @@ export default class extends Endpoint { userGroupId: invitation.userGroupId, } as UserGroupJoining); - this.userGroupInvitationsRepository.delete(invitation.id); + return await this.userGroupInvitationsRepository.delete(invitation.id); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 1fd3b2f4b3..f8290f7c0c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -11,8 +11,6 @@ export const meta = { kind: 'write:user-groups', - description: 'Delete an existing group invitation for the authenticated user without joining the group.', - errors: { noSuchInvitation: { message: 'No such invitation.', From ebbd759a03831da204e1472ac697fc1828db7f8d Mon Sep 17 00:00:00 2001 From: caipira113 Date: Sat, 17 Jun 2023 04:06:42 +0900 Subject: [PATCH 252/308] fix(backend): Resolve issue with UserGroupInvitation in notifications This commit resolves the issue of `Internal error occurred in i/notifications: Could not find any entity of type "UserGroupInvitation"`. --- .../src/core/entities/NotificationEntityService.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index fe3908c3f6..7bbaf0e725 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository, UserGroupInvitationsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Notification } from '@/models/entities/Notification.js'; import type { Note } from '@/models/entities/Note.js'; @@ -43,6 +43,9 @@ export class NotificationEntityService implements OnModuleInit { @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, + //private userEntityService: UserEntityService, //private noteEntityService: NoteEntityService, //private userGroupInvitationEntityService: UserGroupInvitationEntityService, @@ -156,6 +159,14 @@ export class NotificationEntityService implements OnModuleInit { validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId)); } + const groupInvitedNotifications = validNotifications.filter(x => x.type === 'groupInvited'); + if (groupInvitedNotifications.length > 0) { + const existingInvitationIds = await this.userGroupInvitationsRepository.find({ + where: { id: In(groupInvitedNotifications.map(x => x.userGroupInvitationId!)) }, + }); + validNotifications = validNotifications.filter(x => (x.type !== 'groupInvited') || existingInvitationIds.some(r => r.id === x.userGroupInvitationId)); + } + return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, { packedNotes, packedUsers, From 407759f7c4ccec32439e794a920dfe0330d92ab7 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 17 Jun 2023 13:23:14 +0900 Subject: [PATCH 253/308] Update CHANGELOG_CHERRYPICK.md --- CHANGELOG_CHERRYPICK.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index ace69fdeab..0306e07e68 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -92,6 +92,7 @@ ### Server - mfm-js를 cherrypick-mfm-js로 변경 - misskey-js를 cherrypick-js로 변경 +- Fix: 알림에서 UserGroupInvitation 관련 문제 해결 (kokonect-link/cherrypick#75) --- From 8c7bcdf998844d419496b5c767a8e0c83c0a2757 Mon Sep 17 00:00:00 2001 From: NoriDev <11006910+noridev@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:54:54 +0900 Subject: [PATCH 254/308] =?UTF-8?q?fix(client):=20=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=83=A1=E3=83=88=E3=83=AA=E3=82=AF=E3=82=B9?= =?UTF-8?q?=E3=81=8C90=E5=BA=A6=E5=82=BE=E3=81=84=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=20(#11012)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ packages/frontend/src/widgets/server-metric/pie.vue | 1 + 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45dc0e3c90..681105fb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ --> +## 13.x.x (unreleased) + +### Client +- Fix: サーバーメトリクスが90度傾いている + ## 13.13.2 ### General diff --git a/packages/frontend/src/widgets/server-metric/pie.vue b/packages/frontend/src/widgets/server-metric/pie.vue index 398815a6ae..8f7471061a 100644 --- a/packages/frontend/src/widgets/server-metric/pie.vue +++ b/packages/frontend/src/widgets/server-metric/pie.vue @@ -15,6 +15,7 @@ :stroke-dashoffset="strokeDashoffset" fill="none" stroke-width="0.1" + :class="$style.circle" :stroke="color" /> {{ (value * 100).toFixed(0) }}% From 5d64edf8c14755900eeb33f72bb12d0bae2f153c Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 17 Jun 2023 17:39:23 +0900 Subject: [PATCH 255/308] fix(friendly): sidebar username overflow --- packages/frontend/src/ui/friendly/navbar-for-mobile.vue | 1 + packages/frontend/src/ui/friendly/navbar.vue | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/frontend/src/ui/friendly/navbar-for-mobile.vue b/packages/frontend/src/ui/friendly/navbar-for-mobile.vue index 2e53835806..406f6096c9 100644 --- a/packages/frontend/src/ui/friendly/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/friendly/navbar-for-mobile.vue @@ -216,6 +216,7 @@ function openProfile() { margin-left: 5px; font-weight: bold; max-width: 90px; + text-overflow: clip !important; } .drawer { diff --git a/packages/frontend/src/ui/friendly/navbar.vue b/packages/frontend/src/ui/friendly/navbar.vue index b78b41faa6..385d73cdd7 100644 --- a/packages/frontend/src/ui/friendly/navbar.vue +++ b/packages/frontend/src/ui/friendly/navbar.vue @@ -275,6 +275,7 @@ function openProfile() { margin-left: 5px; font-weight: bold; max-width: 100px; + text-overflow: clip !important; } .drawer { From f46879ac1a5ce057bc8f15e84630227886aad2f8 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 17 Jun 2023 17:53:29 +0900 Subject: [PATCH 256/308] Update CHANGELOG_CHERRYPICK.md --- CHANGELOG_CHERRYPICK.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 0306e07e68..97b5646a26 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -30,19 +30,19 @@ - 타임라인에 노트의 답글을 표시하는 옵션의 기본값을 켜짐으로 설정 - 네비게이션 바의 배치를 수정 - 프로필 아이콘의 기본값을 사각형으로 설정 -- 미디어 타임라인 추가 ([misskey.design c01be0d](https://github.com/kiyo4act/misskey.design/commit/c01be0dc7674cdf0bcac6081c63baab52c4c9abe)) -- ruby 표기 지원 ([misskey.design 8826fbf](https://github.com/kiyo4act/misskey.design/commit/8826fbf77a9d6ff5cac7368da999f3d04aa68c97)) -- 노트 검색을 전체/로컬/리모트로 나누도록 변경 ([misskey.design 4adad07](https://github.com/kiyo4act/misskey.design/commit/4adad0768ce02bd49207a94678cf3c9130ed9e10)) +- 미디어 타임라인 추가 ([kiyo4act/misskey.design@c01be0d](https://github.com/kiyo4act/misskey.design/commit/c01be0dc7674cdf0bcac6081c63baab52c4c9abe)) +- ruby 표기 지원 ([kiyo4act/misskey.design@8826fbf](https://github.com/kiyo4act/misskey.design/commit/8826fbf77a9d6ff5cac7368da999f3d04aa68c97)) +- 노트 검색을 전체/로컬/리모트로 나누도록 변경 ([kiyo4act/misskey.design@4adad07](https://github.com/kiyo4act/misskey.design/commit/4adad0768ce02bd49207a94678cf3c9130ed9e10)) - 노트/유저 검색 페이지에서 Enter 키를 누르면 검색하도록 - 프로필 번역 기능 추가 - 네비게이션 메뉴에 배너 표시 옵션 추가 - 노트에서 프로필 아이콘을 숨기는 옵션 추가 -- 닉네임 기능 ([shrimpia 126f145](https://github.com/shrimpia/misskey/commit/126f145560caa0cc34fe8d2c9ee22f3be922ea10), [shrimpia 58f70be](https://github.com/shrimpia/misskey/commit/58f70beb9aff2287a64d903b43583184340294aa)) +- 닉네임 기능 ([shrimpia/misskey@126f145](https://github.com/shrimpia/misskey/commit/126f145560caa0cc34fe8d2c9ee22f3be922ea10), [shrimpia/misskey@58f70be](https://github.com/shrimpia/misskey/commit/58f70beb9aff2287a64d903b43583184340294aa)) - 유저 페이지에서 사용자의 이름을 클릭 또는 탭하여 원하는 이름으로 변경할 수 있습니다. -- 「노트 액션 버튼을 마우스를 올렸을 때에만 표시」 옵션을 켰을 때, 자세히 버튼을 표시하도록 변경 ([shrimpia 4802191](https://github.com/shrimpia/misskey/commit/48021913bb9b6b2a314e8d88e3816f6f66a52888)) -- 「이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음」을 선택 사항으로 설정 ([shrimpia 9345149](https://github.com/shrimpia/misskey/commit/9345149f5d0447058a6ed1524708925a84744bd7)) -- 노트 작성 폼에서 본문 미리보기 상태 기억 ([shrimpia](https://github.com/shrimpia/misskey)) -- 리모트에 존재하는 커스텀 이모지도 자신의 서버 내에 같은 이름의 이모지가 있으면 리액션 할 수 있도록 ([shrimpia e91295f](https://github.com/shrimpia/misskey/commit/e91295ff9c6f8ac90f61c8de7a891a6836e48e95), [shrimpia 010378f](https://github.com/shrimpia/misskey/commit/010378fae659ad3015bfade4346209e01bb2a902), [shrimpia acf2a30](https://github.com/shrimpia/misskey/commit/acf2a30e8a8c57525dfbab499dbb0b6c7d8e43c2)) +- 「노트 액션 버튼을 마우스를 올렸을 때에만 표시」 옵션을 켰을 때, 자세히 버튼을 표시하도록 변경 ([shrimpia/misskey@4802191](https://github.com/shrimpia/misskey/commit/48021913bb9b6b2a314e8d88e3816f6f66a52888)) +- 「이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음」을 선택 사항으로 설정 ([shrimpia/misskey@9345149](https://github.com/shrimpia/misskey/commit/9345149f5d0447058a6ed1524708925a84744bd7)) +- 노트 작성 폼에서 본문 미리보기 상태 기억 ([shrimpia/misskey](https://github.com/shrimpia/misskey)) +- 리모트에 존재하는 커스텀 이모지도 자신의 서버 내에 같은 이름의 이모지가 있으면 리액션 할 수 있도록 ([shrimpia/misskey@e91295f](https://github.com/shrimpia/misskey/commit/e91295ff9c6f8ac90f61c8de7a891a6836e48e95), [shrimpia/misskey@010378f](https://github.com/shrimpia/misskey/commit/010378fae659ad3015bfade4346209e01bb2a902), [shrimpia/misskey@acf2a30](https://github.com/shrimpia/misskey/commit/acf2a30e8a8c57525dfbab499dbb0b6c7d8e43c2)) - 「이미 본 리노트를 간략화하기」 옵션의 기본값을 꺼짐으로 설정 ### Client @@ -56,16 +56,16 @@ - 이미 읽은 채팅은 가독성 개선을 위해 배경을 연하게 - 답글 노트의 디자인 개선 - 업데이트 팝업의 버튼 디자인 변경 -- 팔로우/팔로워를 비공개로 하고 있는 경우 표시는 '0'이 아닌 키 아이콘을 표시하도록 ([misskey-dev#10934](https://github.com/misskey-dev/misskey/pull/10934)) -- 신고의 초기 댓글에 사용자 ID 추가 ([misskey.design fded63c](https://github.com/kiyo4act/misskey.design/commit/fded63c7317721daeb8babcdf901dc00ab475231), [misskey.design 8b6e303](https://github.com/kiyo4act/misskey.design/commit/8b6e303f184888193f4ce1daaa1629fedb46c7a9)) -- OGP 미리보기 추가 ([misskey.design 4eb0a6d](https://github.com/kiyo4act/misskey.design/commit/4eb0a6d8467c0c601e6fe37b0170c6c36f4bc8f2)) +- 팔로우/팔로워를 비공개로 하고 있는 경우 표시는 '0'이 아닌 키 아이콘을 표시하도록 (misskey-dev/misskey#10934) +- 신고의 초기 댓글에 사용자 ID 추가 ([kiyo4act/misskey.design@fded63c](https://github.com/kiyo4act/misskey.design/commit/fded63c7317721daeb8babcdf901dc00ab475231), [kiyo4act/misskey.design@8b6e303](https://github.com/kiyo4act/misskey.design/commit/8b6e303f184888193f4ce1daaa1629fedb46c7a9)) +- OGP 미리보기 추가 ([kiyo4act/misskey.design@4eb0a6d](https://github.com/kiyo4act/misskey.design/commit/4eb0a6d8467c0c601e6fe37b0170c6c36f4bc8f2)) - 더 보기! 메뉴에 도움말 추가 - 노트를 자세히 볼 때 역할 배지를 표시하도록 - 일부 제어판 페이지의 헤더 개선 - 스크롤이 최상단일 때 헤더를 누르면 새로고침 메뉴를 표시하도록 -- MkImgWithBlurhash에서 blurhash 그리기에 사용하는 canvas는 재사용하도록([misskey-dev#10966](https://github.com/misskey-dev/misskey/pull/10966)) -- CherryPick 고유 기능 및 개선된 기능은 [CherryPick] 배지 추가 -- 네비게이션 메뉴 편집 페이지 UI 개선 ([shrimpia bf8c84d](https://github.com/shrimpia/misskey/commit/bf8c84d299bd06cb21e18a9fe68ff9abc11fd4a0)) +- MkImgWithBlurhash에서 blurhash 그리기에 사용하는 canvas는 재사용하도록(misskey-dev/misskey#10966) +- CherryPick 고유 기능 및 개선된 기능은 「CherryPick」 배지 추가 +- 네비게이션 메뉴 편집 페이지 UI 개선 ([shrimpia/misskey@bf8c84d](https://github.com/shrimpia/misskey/commit/bf8c84d299bd06cb21e18a9fe68ff9abc11fd4a0)) - 「노트 본문에 번역 버튼 표시」를 선택 사항으로 설정 - 답글도 번역할 수 있도록 개선 - 리노트 했을 때 뜨는 토스트 알림에 아이콘 추가 @@ -101,9 +101,9 @@ 전체 변경 사항을 확인하려면, [CHANGELOG.md#13131](CHANGELOG.md#13131) 문서를 참고하십시오. ### General -- 채팅 및 그룹 기능 유지 (revert: [misskey-dev/misskey#9919](https://github.com/misskey-dev/misskey/pull/9919), [misskey-dev/misskey#9942](https://github.com/misskey-dev/misskey/pull/9942)) -- 노트 수식 삽입 기능 복원 (MathML 호환을 위해 기존에 제거된 KaTex를 Temml로 대체 ([misskey-dev/misskey#9754](https://github.com/misskey-dev/misskey/issues/9754))) -- Cloud Translation - Advanced(v3) 지원 추가 ([mk-castella](https://github.com/libnare/mk-castella/commit/3c582dd850d00f5b8faea027fd054118efb97856)) +- 채팅 및 그룹 기능 유지 (revert: misskey-dev/misskey#9919, misskey-dev/misskey#9942) +- 노트 수식 삽입 기능 복원 (MathML 호환을 위해 기존에 제거된 KaTex를 Temml로 대체 (misskey-dev/misskey#9754)) +- Cloud Translation - Advanced(v3) 지원 추가 ([libnare/mk-castella@3c582dd](https://github.com/libnare/mk-castella/commit/3c582dd850d00f5b8faea027fd054118efb97856)) - mfm-cheat-sheet 복원 ### Client @@ -114,7 +114,7 @@ - (Friendly) 데스크톱 모드에서 타임라인 옆에 알림 영역을 배치하도록 - (Friendly) 오조작 방지를 위해 플로팅 버튼을 길게 눌러 새로고침 기능 제거 - (Friendly) 채팅 탭에 플로팅 버튼 추가 -- 클라이언트에서 사용되는 폰트의 확장자를 ttf에서 woff2 및 woff로 변경 ([mk-castella](https://github.com/libnare/mk-castella/commit/f439b3e007618c02da7a352016b3d0f397311f54)) +- 클라이언트에서 사용되는 폰트의 확장자를 ttf에서 woff2 및 woff로 변경 ([libnare/mk-castella@f439b3e](https://github.com/libnare/mk-castella/commit/f439b3e007618c02da7a352016b3d0f397311f54)) - about-misskey 페이지에 CherryPick 관련 정보 추가 및 Misskey 문단 구분명 추가 - 계정 초기 설정 마법사 개선 - 뒤로 가기 버튼 추가 @@ -137,7 +137,7 @@ - 알림 기본 정렬을 수직으로 변경 - 채팅방 목록 페이지 디자인 개선 - 리노트 문구를 노트 최상단으로 배치 -- 특정 MFM 구문이 포함된 노트를 간략화 할지 선택할 수 있음(enhance: [aba0755](https://github.com/kokonect-link/cherrypick/commit/aba0755880d6797f49d34c8b7fe2c602d153e367)) +- 특정 MFM 구문이 포함된 노트를 간략화 할지 선택할 수 있음(enhance: [@aba0755](https://github.com/kokonect-link/cherrypick/commit/aba0755880d6797f49d34c8b7fe2c602d153e367)) - 노트 사이를 띄우는 옵션 활성화 시 알림 페이지의 노트도 띄우도록 - 안테나, 그룹, 리스트, 클립 페이지의 생성 버튼을 헤더로 이동 - 채팅 디자인 일부 개선 From 70172440900d60907308d7b45bff6371a8e203e5 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 17 Jun 2023 19:13:13 +0900 Subject: [PATCH 257/308] =?UTF-8?q?enhance(client):=20=E3=82=B0=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=97=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AE=E3=83=87?= =?UTF-8?q?=E3=82=B6=E3=82=A4=E3=83=B3=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + .../src/components/MkNotification.vue | 7 +-- .../frontend/src/pages/my-groups/group.vue | 43 +++++++++++++------ .../frontend/src/pages/my-groups/index.vue | 31 ++++++++++--- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 97b5646a26..1f17c3ff69 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -75,6 +75,7 @@ - 노트를 게시했을 때 토스트 알림 표시 - 노트 액션 버튼 추가 및 편의성 향상 - 데이터 세이버를 활성화하면 설정을 반영하기 위해 페이지를 새로 고치도록 +- 그룹 페이지의 전반적인 디자인 개선 - Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제 - Fix: (Friendly) 타임라인이 변경되었을 때 네비게이션 바의 인디케이터가 사라지지 않는 문제 - Fix: (Friendly) 모바일에서 헤더가 사라졌을 때 프로필 아이콘의 높이가 잘못 설정되는 문제 diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index e377447353..048fedefb9 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -89,8 +89,9 @@ @@ -149,7 +150,7 @@ const acceptGroupInvitation = () => { const rejectGroupInvitation = () => { groupInviteDone.value = true; - os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); + os.apiWithDialog('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); }; useTooltip(reactionRef, (showing) => { diff --git a/packages/frontend/src/pages/my-groups/group.vue b/packages/frontend/src/pages/my-groups/group.vue index cc622f9ba1..0cbaed4f04 100644 --- a/packages/frontend/src/pages/my-groups/group.vue +++ b/packages/frontend/src/pages/my-groups/group.vue @@ -1,19 +1,11 @@ @@ -136,6 +139,7 @@ const endDate = ref(''); const endTime = ref(''); const location = ref(props.modelValue?.metadata.location ?? null); const url = ref(props.modelValue?.metadata.url ?? null); +const showAdvanced = ref(false); const doorTime = ref(props.modelValue?.metadata.doorTime ?? null); const organizer = ref(props.modelValue?.metadata.organizer?.name ?? null); const organizerLink = ref(props.modelValue?.metadata.organizer?.sameAs ?? null); @@ -178,6 +182,7 @@ function get(): misskey.entities.Note['event'] { } : undefined, inLanguage: language.value ?? undefined, typicalAgeRange: ageRange.value ?? undefined, + isAccessibleForFree: isFree, offers: ticketsUrl.value || price.value ? { price: price.value ?? undefined, priceCurrency: undefined, @@ -233,11 +238,6 @@ watch([title, startDate, startTime, endDate, endTime, location, url, doorTime, o } } - >.add { - margin: 8px 0; - z-index: 1; - } - >section { margin: 16px 0 0 0; From 8293c1babe90138b7772abfb0a29319f4e3f0c6d Mon Sep 17 00:00:00 2001 From: ssmucny Date: Sat, 17 Jun 2023 17:27:42 -0400 Subject: [PATCH 277/308] - refactor search page - lint fixes - UI bug fix --- packages/frontend/src/pages/search.event.vue | 65 +++++++++++ packages/frontend/src/pages/search.vue | 112 +------------------ packages/frontend/src/pages/user/events.vue | 4 +- packages/frontend/src/pages/user/index.vue | 15 --- 4 files changed, 71 insertions(+), 125 deletions(-) create mode 100644 packages/frontend/src/pages/search.event.vue diff --git a/packages/frontend/src/pages/search.event.vue b/packages/frontend/src/pages/search.event.vue new file mode 100644 index 0000000000..2e29740d74 --- /dev/null +++ b/packages/frontend/src/pages/search.event.vue @@ -0,0 +1,65 @@ + + + diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index db3ada3cf5..d4525dbcf7 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -14,48 +14,16 @@ - -
-
- - - - - - - - -
- - - - - - -
- {{ i18n.ts.search }} -
- - - - - -
+ + - - diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 5d11b96630..4b81f2a5d9 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -218,9 +218,9 @@ export default function(props: { } case 'ruby': { let rb, rt, tokens; - token.children.forEach((t) => { if (t.type == 'text') { t.props.text = t.props.text.trim(); } }); - const children = token.children.filter((t) => t.type != 'text' || t.props.text != ''); - if (children.length == 1 && children[0].type == 'text') { + token.children.forEach((t) => { if (t.type === 'text') { t.props.text = t.props.text.trim(); } }); + const children = token.children.filter((t) => t.type !== 'text' || t.props.text !== ''); + if (children.length === 1 && children[0].type === 'text') { tokens = children[0].props.text.split(' '); rb = [tokens[0]]; rt = [tokens.slice(1).join(' ')]; diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 58747bd2bd..7d62846610 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -206,6 +206,7 @@ import * as os from '@/os'; import { definePageMetadata } from '@/scripts/page-metadata'; import { claimAchievement, claimedAchievements } from '@/scripts/achievements'; import { $i } from '@/account'; +import { instance } from '@/instance'; const patronsWithIconWithCherryPick = [{ name: 'Inger 잉어', @@ -353,6 +354,12 @@ const patronsWithMisskey = [ ]; let isKokonect = false; +const instanceList = [ + 'http://localhost:3000', + 'https://kokonect.link', + 'https://beta.kokonect.link', + 'https://universe.noridev.moe', +]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); @@ -361,8 +368,7 @@ let easterEggEmojis = $ref([]); let easterEggEngine = $ref(null); const containerEl = $shallowRef(); -const meta = await os.api('meta', { detail: true }); -if (meta.uri == 'https://kokonect.link' || 'http://localhost:3000') isKokonect = true; +if (instanceList.includes(instance.uri)) isKokonect = true; function iconLoaded() { const emojis = defaultStore.state.reactions; diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue index 287738145c..cf034d85a5 100644 --- a/packages/frontend/src/pages/messaging/messaging-room.form.vue +++ b/packages/frontend/src/pages/messaging/messaging-room.form.vue @@ -198,7 +198,7 @@ function clear() { } function saveDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') ?? '{}'); drafts[draftKey] = { updatedAt: new Date(), @@ -213,7 +213,7 @@ function saveDraft() { } function deleteDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') ?? '{}'); delete drafts[draftKey]; @@ -221,7 +221,7 @@ function deleteDraft() { } async function insertEmoji(ev: MouseEvent) { - os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl); + await os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl); } onMounted(() => { @@ -232,7 +232,7 @@ onMounted(() => { //new Autocomplete(textEl, this, { model: 'text' }); // 書きかけの投稿を復元 - const draft = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}')[draftKey]; + const draft = JSON.parse(miLocalStorage.getItem('message_drafts') ?? '{}')[draftKey]; if (draft) { text = draft.data.text; file = draft.data.file; diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index 724d462921..e8a79bf3cd 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -112,7 +112,7 @@ async function addItem(ev: MouseEvent) { }]; }, }, - ], ev.target || ev.currentTarget); + ], ev.currentTarget ?? ev.target ); } function removeItem(index: number) { diff --git a/packages/frontend/src/scripts/edit-nickname.ts b/packages/frontend/src/scripts/edit-nickname.ts index 1b8dcd9591..b458c797bc 100644 --- a/packages/frontend/src/scripts/edit-nickname.ts +++ b/packages/frontend/src/scripts/edit-nickname.ts @@ -10,8 +10,8 @@ export async function editNickname(user: User) { placeholder: user.name || user.username, default: defaultStore.state.nicknameMap[user.id] ?? null, }); - if (canceled) return - const newMap = {...defaultStore.state.nicknameMap}; + if (canceled) return; + const newMap = { ...defaultStore.state.nicknameMap }; if (result) { newMap[user.id] = result; } else { From a7be2e0cd77efb7b0e847f992a51fdae36c309d7 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Mon, 19 Jun 2023 16:58:00 +0900 Subject: [PATCH 299/308] 13.13.2-cp-4.1.0-rc.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94f77ea502..214210733e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cherrypick", - "version": "13.13.2-cp-4.1.0-rc.5", + "version": "13.13.2-cp-4.1.0-rc.6", "codename": "nasubi", "repository": { "type": "git", From 38ab7141f22dbb37865baaf76020ad1d00200671 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Mon, 19 Jun 2023 19:06:09 +0900 Subject: [PATCH 300/308] fix jest --- packages/backend/test/e2e/users.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index f35b3bfc08..cd7b736355 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -150,6 +150,7 @@ describe('ユーザー', () => { hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes, hasUnreadMentions: user.hasUnreadMentions, + hasUnreadMessagingMessage: user.hasUnreadMessagingMessage, hasUnreadAnnouncement: user.hasUnreadAnnouncement, hasUnreadAntenna: user.hasUnreadAntenna, hasUnreadChannel: user.hasUnreadChannel, @@ -395,6 +396,7 @@ describe('ユーザー', () => { assert.strictEqual(response.hideOnlineStatus, false); assert.strictEqual(response.hasUnreadSpecifiedNotes, false); assert.strictEqual(response.hasUnreadMentions, false); + assert.strictEqual(response.hasUnreadMessagingMessage, false); assert.strictEqual(response.hasUnreadAnnouncement, false); assert.strictEqual(response.hasUnreadAntenna, false); assert.strictEqual(response.hasUnreadChannel, false); From 030597e572abaaeb3bdf2b4c0992b775853b40ec Mon Sep 17 00:00:00 2001 From: NoriDev Date: Mon, 19 Jun 2023 20:03:56 +0900 Subject: [PATCH 301/308] fix jest --- packages/backend/test/e2e/antennas.ts | 3 +++ packages/backend/test/e2e/users.ts | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index a94b02769d..bdf266e52a 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -43,6 +43,7 @@ describe('アンテナ', () => { name: 'test', notify: false, src: 'all' as const, + userGroupId: null, userListId: null, users: [''], withFile: false, @@ -165,6 +166,7 @@ describe('アンテナ', () => { name: 'test', notify: false, src: 'all', + userGroupId: null, userListId: null, users: [''], withFile: false, @@ -216,6 +218,7 @@ describe('アンテナ', () => { { parameters: (): object => ({ src: 'all' }) }, { parameters: (): object => ({ src: 'users' }) }, { parameters: (): object => ({ src: 'list' }) }, + { parameters: (): object => ({ userGroupId: null }) }, { parameters: (): object => ({ userListId: null }) }, { parameters: (): object => ({ src: 'list', userListId: aliceList.id }) }, { parameters: (): object => ({ keywords: [['x']] }) }, diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index cd7b736355..9abd310b4a 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -405,7 +405,7 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.mutedWords, []); assert.deepStrictEqual(response.mutedInstances, []); assert.deepStrictEqual(response.mutingNotificationTypes, []); - assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest']); + assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest', 'groupInvited']); assert.deepStrictEqual(response.achievements, []); assert.deepStrictEqual(response.loggedInDays, 0); assert.deepStrictEqual(response.policies, DEFAULT_POLICIES); @@ -485,9 +485,9 @@ describe('ユーザー', () => { { parameters: (): object => ({ mutedWords: [] }) }, { parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) }, { parameters: (): object => ({ mutedInstances: [] }) }, - { parameters: (): object => ({ mutingNotificationTypes: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) }, + { parameters: (): object => ({ mutingNotificationTypes: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'groupInvited', 'app'] }) }, { parameters: (): object => ({ mutingNotificationTypes: [] }) }, - { parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) }, + { parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest', 'groupInvited'] }) }, { parameters: (): object => ({ emailNotificationTypes: [] }) }, ] as const)('を書き換えることができる($#)', async ({ parameters }) => { const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters(), user: alice }); From fec18294e00da937c4792bf65d759a435d90214a Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 04:30:34 +0900 Subject: [PATCH 302/308] fix test --- cypress/e2e/basic.cy.js | 1 - cypress/e2e/widgets.cy.js | 7 ------- .../src/server/api/endpoints/notes/media-timeline.ts | 3 ++- packages/backend/test/e2e/endpoints.ts | 2 +- packages/frontend/src/components/MkSignupDialog.form.vue | 2 +- packages/frontend/src/ui/universal.widgets.vue | 2 +- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index 8258684eab..098b221502 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -64,7 +64,6 @@ describe('After setup instance', () => { cy.get('[data-cy-signup-submit]').should('be.disabled'); cy.get('[data-cy-signup-password-retype] input').type('alice1234'); cy.get('[data-cy-signup-submit]').should('not.be.disabled'); - cy.get('[data-cy-signup-back]').click(); cy.get('[data-cy-signup-submit]').click(); cy.wait('@signup'); diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js index f5a982eb0a..2158aca571 100644 --- a/cypress/e2e/widgets.cy.js +++ b/cypress/e2e/widgets.cy.js @@ -22,24 +22,17 @@ describe('After user signed in', () => { cy.wait(1000); }); - it('widget edit toggle is visible', () => { - cy.get('[data-cy-widget-edit]').should('be.visible'); - }); - it('widget select should be visible in edit mode', () => { - cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-widget-select]').should('be.visible'); }); it('first widget should be removed', () => { - cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-customize-container]:first-child [data-cy-customize-container-remove]._button').click(); cy.get('[data-cy-customize-container]').should('have.length', 2); }); function buildWidgetTest(widgetName) { it(`${widgetName} widget should get added`, () => { - cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-widget-select] select').select(widgetName, { force: true }); cy.get('[data-cy-bg]._modalBg[data-cy-transparent]').click({ multiple: true, force: true }); cy.get('[data-cy-widget-add]').click({ force: true }); diff --git a/packages/backend/src/server/api/endpoints/notes/media-timeline.ts b/packages/backend/src/server/api/endpoints/notes/media-timeline.ts index ea36aeddcd..cc4830ac94 100644 --- a/packages/backend/src/server/api/endpoints/notes/media-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/media-timeline.ts @@ -41,6 +41,7 @@ export const paramDef = { default: false, description: 'Only show notes that have attached files.', }, + withReplies: { type: 'boolean', default: false }, fileType: { type: 'array', items: { type: 'string', } }, @@ -87,7 +88,7 @@ export default class extends Endpoint { .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateChannelQuery(query, me); - this.queryService.generateRepliesQuery(query, me); + this.queryService.generateRepliesQuery(query, ps.withReplies, me); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedNoteQuery(query, me); diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 565dbd6866..2d5381e81c 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -964,7 +964,7 @@ describe('Endpoints', () => { test('文字数オーバーで怒られる', async () => { const res = await api('/messaging/messages/create', { userId: bob.id, - text: '!'.repeat(1001), + text: '!'.repeat(3001), }, alice); assert.strictEqual(res.status, 400); diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 3736eecae0..26e79200c3 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -60,7 +60,7 @@
- {{ i18n.ts.goBack }} + {{ i18n.ts.goBack }} From 49d326a108905ebc759bd0da48cda8bcbcc12c99 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 05:14:46 +0900 Subject: [PATCH 303/308] fix e2e test --- cypress/e2e/widgets.cy.js | 7 +++++++ packages/frontend/src/ui/universal.widgets.vue | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js index 2158aca571..f5a982eb0a 100644 --- a/cypress/e2e/widgets.cy.js +++ b/cypress/e2e/widgets.cy.js @@ -22,17 +22,24 @@ describe('After user signed in', () => { cy.wait(1000); }); + it('widget edit toggle is visible', () => { + cy.get('[data-cy-widget-edit]').should('be.visible'); + }); + it('widget select should be visible in edit mode', () => { + cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-widget-select]').should('be.visible'); }); it('first widget should be removed', () => { + cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-customize-container]:first-child [data-cy-customize-container-remove]._button').click(); cy.get('[data-cy-customize-container]').should('have.length', 2); }); function buildWidgetTest(widgetName) { it(`${widgetName} widget should get added`, () => { + cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-widget-select] select').select(widgetName, { force: true }); cy.get('[data-cy-bg]._modalBg[data-cy-transparent]').click({ multiple: true, force: true }); cy.get('[data-cy-widget-add]').click({ force: true }); diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue index 8083e459b5..87d31f2c3b 100644 --- a/packages/frontend/src/ui/universal.widgets.vue +++ b/packages/frontend/src/ui/universal.widgets.vue @@ -4,7 +4,7 @@ - +
From f9951e73210c6cafdbdeada90773aa4e7035b87e Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 05:22:04 +0900 Subject: [PATCH 304/308] =?UTF-8?q?fix:=20=E3=83=81=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=88=E5=86=85=E5=AE=B9=E3=81=8C=E9=95=B7=E3=81=99=E3=81=8E?= =?UTF-8?q?=E3=82=8B=E3=81=A8=E3=80=81=E3=81=9D=E3=81=AE=E5=88=86=E3=82=B9?= =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B9=E3=82=92=E5=8D=A0=E6=9C=89=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + packages/frontend/src/pages/messaging/index.vue | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 436a7f43c0..ba4e701cd6 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -162,6 +162,7 @@ - Fix: 테마의 호환성이 저하되는 문제 - Fix: 채팅 입력란의 위치를 고정하도록 - Fix: 안테나 편집 페이지에 헤더가 누락됨 +- Fix: 채팅 내용이 길이가 너무 길어도 그만큼 공간을 차지하는 문제 --- diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue index 890dbfef66..458c81900f 100644 --- a/packages/frontend/src/pages/messaging/index.vue +++ b/packages/frontend/src/pages/messaging/index.vue @@ -255,6 +255,8 @@ definePageMetadata({ padding: 0; overflow: hidden; overflow-wrap: break-word; + line-height: 1.35; + max-height: 4.05em; color: var(--faceText); } From 8a9c2d94e585e889c85d8340490a374c07b95050 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 05:22:37 +0900 Subject: [PATCH 305/308] fix CHANGELOG_CHERRYPICK.md --- CHANGELOG_CHERRYPICK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index ba4e701cd6..bf27d63266 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -94,6 +94,7 @@ - Fix: 프로필 아이콘을 사각형으로 설정했을 때 유저 팝업의 디자인 개선 - Fix: 노트 헤더에서 유저 이름이 너무 긴 경우 디자인에 문제가 발생할 수 있음 - Fix: 그룹 페이지에서 기능이 제대로 작동하지 않음 +- Fix: 채팅 내용이 길이가 너무 길어도 그만큼 공간을 차지하는 문제 ### Server - mfm-js를 cherrypick-mfm-js로 변경 @@ -162,7 +163,6 @@ - Fix: 테마의 호환성이 저하되는 문제 - Fix: 채팅 입력란의 위치를 고정하도록 - Fix: 안테나 편집 페이지에 헤더가 누락됨 -- Fix: 채팅 내용이 길이가 너무 길어도 그만큼 공간을 차지하는 문제 --- From 34d179f7fb571535d40a66db46a6b5377fd2d8d9 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 05:51:53 +0900 Subject: [PATCH 306/308] fix widget e2e test --- packages/frontend/src/ui/universal.widgets.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue index 87d31f2c3b..ec5e8bb03f 100644 --- a/packages/frontend/src/ui/universal.widgets.vue +++ b/packages/frontend/src/ui/universal.widgets.vue @@ -2,9 +2,8 @@
- - + +
From 2beb4b2b84bfe0407b7d147801213f20d3011223 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 15:28:04 +0900 Subject: [PATCH 307/308] tweak ab8a920a --- packages/frontend/src/pages/settings/general.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index ed99aa76ee..445eac2510 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -378,6 +378,7 @@ watch([ instanceTicker, overridedDeviceKind, enableDataSaverMode, + enableAbsoluteTime, ], async () => { await reloadAsk(); }); From 366198445cdd9a6d9fb38341bbc40a15a2f21861 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Tue, 20 Jun 2023 15:29:05 +0900 Subject: [PATCH 308/308] 13.13.2-cp-4.1.0 --- CHANGELOG_CHERRYPICK.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index bf27d63266..da345c0189 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -23,7 +23,7 @@ 이 문서는 CherryPick의 변경 사항만 포함합니다. ## 13.13.2-cp-4.1.0 -출시일: unreleased
+출시일: 2023/06/20
전체 변경 사항을 확인하려면, [CHANGELOG.md#13132](CHANGELOG.md#13132) 문서를 참고하십시오. ### General diff --git a/package.json b/package.json index 214210733e..e8fe76fac3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cherrypick", - "version": "13.13.2-cp-4.1.0-rc.6", + "version": "13.13.2-cp-4.1.0", "codename": "nasubi", "repository": { "type": "git",