From 605f1492350c6f90108018d895e47fbc37197c4a Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:17:32 +0900 Subject: feat: 自分用メモ機能 (#10516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 自分用メモを作成する機能 * 不要なCSSを削除 * メモ: デザイン調整 * デザイン崩れを修正 * fix: メモ機能のe2eテストで見つかった不具合を修正 * デザイン調整 * fix(frontend): 自分用メモtextareaにline-heightが適用されない問題を修正 --- packages/backend/src/models/RepositoryModule.ts | 10 +++++- packages/backend/src/models/entities/UserMemo.ts | 42 ++++++++++++++++++++++++ packages/backend/src/models/index.ts | 3 ++ packages/backend/src/models/json-schema/user.ts | 4 +++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/models/entities/UserMemo.ts (limited to 'packages/backend/src/models') diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 7be7b81904..a415e37904 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, 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, UserMemo } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -388,6 +388,12 @@ const $roleAssignmentsRepository: Provider = { inject: [DI.db], }; +const $userMemoRepository: Provider = { + provide: DI.userMemoRepository, + useFactory: (db: DataSource) => db.getRepository(UserMemo), + inject: [DI.db], +}; + @Module({ imports: [ ], @@ -456,6 +462,7 @@ const $roleAssignmentsRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, + $userMemoRepository, ], exports: [ $usersRepository, @@ -522,6 +529,7 @@ const $roleAssignmentsRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, + $userMemoRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/models/entities/UserMemo.ts b/packages/backend/src/models/entities/UserMemo.ts new file mode 100644 index 0000000000..7dc34b4346 --- /dev/null +++ b/packages/backend/src/models/entities/UserMemo.ts @@ -0,0 +1,42 @@ +import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { id } from '../id.js'; +import { User } from './User.js'; + +@Entity() +@Index(['userId', 'targetUserId'], { unique: true }) +export class UserMemo { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column({ + ...id(), + comment: 'The ID of author.', + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The ID of target user.', + }) + public targetUserId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public targetUser: User | null; + + @Column('varchar', { + length: 2048, + comment: 'Memo.', + }) + public memo: string; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 48d6e15f2a..b8ba28db9b 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -55,6 +55,7 @@ import { UserPending } from '@/models/entities/UserPending.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; import { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; +import { UserMemo } from '@/models/entities/UserMemo.js'; import { Webhook } from '@/models/entities/Webhook.js'; import { Channel } from '@/models/entities/Channel.js'; import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; @@ -129,6 +130,7 @@ export { RoleAssignment, Flash, FlashLike, + UserMemo, }; export type AbuseUserReportsRepository = Repository; @@ -195,3 +197,4 @@ export type RolesRepository = Repository; export type RoleAssignmentsRepository = Repository; export type FlashsRepository = Repository; export type FlashLikesRepository = Repository; +export type UserMemoRepository = Repository; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 7d40979e3d..836368886e 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -250,6 +250,10 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'boolean', nullable: false, optional: true, }, + memo: { + type: 'string', + nullable: false, optional: true, + }, //#endregion }, } as const; -- cgit v1.2.3-freya From 97abfd48ceaebaa0f8e5ee60fcc0a656a3eb13c7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 13 Apr 2023 13:31:54 +0900 Subject: refactor(backend): tweak repository name --- packages/backend/src/core/entities/UserEntityService.ts | 6 +++--- packages/backend/src/di-symbols.ts | 2 +- packages/backend/src/models/RepositoryModule.ts | 8 ++++---- .../backend/src/server/api/endpoints/users/update-memo.ts | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'packages/backend/src/models') diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 3f8254bb6c..68d30fe174 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -114,8 +114,8 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, - @Inject(DI.userMemoRepository) - private userMemoRepository: UserMemoRepository, + @Inject(DI.userMemosRepository) + private userMemosRepository: UserMemoRepository, //private noteEntityService: NoteEntityService, //private driveFileEntityService: DriveFileEntityService, @@ -340,7 +340,7 @@ export class UserEntityService implements OnModuleInit { const falsy = opts.detail ? false : undefined; - const memo = meId == null ? null : await this.userMemoRepository.findOneBy({ + const memo = meId == null ? null : await this.userMemosRepository.findOneBy({ userId: meId, targetUserId: user.id, }).then(row => row?.memo ?? null); diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index b93ba899fb..190d8d65c2 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -70,6 +70,6 @@ export const DI = { roleAssignmentsRepository: Symbol('roleAssignmentsRepository'), flashsRepository: Symbol('flashsRepository'), flashLikesRepository: Symbol('flashLikesRepository'), - userMemoRepository: Symbol('userMemoRepository'), + userMemosRepository: Symbol('userMemosRepository'), //#endregion }; diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index a415e37904..588c98b58d 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -388,8 +388,8 @@ const $roleAssignmentsRepository: Provider = { inject: [DI.db], }; -const $userMemoRepository: Provider = { - provide: DI.userMemoRepository, +const $userMemosRepository: Provider = { + provide: DI.userMemosRepository, useFactory: (db: DataSource) => db.getRepository(UserMemo), inject: [DI.db], }; @@ -462,7 +462,7 @@ const $userMemoRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, - $userMemoRepository, + $userMemosRepository, ], exports: [ $usersRepository, @@ -529,7 +529,7 @@ const $userMemoRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, - $userMemoRepository, + $userMemosRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts index 300435b9ff..ca7756ef75 100644 --- a/packages/backend/src/server/api/endpoints/users/update-memo.ts +++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts @@ -39,8 +39,8 @@ export const paramDef = { @Injectable() export default class extends Endpoint { constructor( - @Inject(DI.userMemoRepository) - private userMemoRepository: UserMemoRepository, + @Inject(DI.userMemosRepository) + private userMemosRepository: UserMemoRepository, private getterService: GetterService, private idService: IdService, ) { @@ -53,7 +53,7 @@ export default class extends Endpoint { // 引数がnullか空文字であれば、パーソナルメモを削除する if (ps.memo === '' || ps.memo == null) { - await this.userMemoRepository.delete({ + await this.userMemosRepository.delete({ userId: me.id, targetUserId: target.id, }); @@ -61,20 +61,20 @@ export default class extends Endpoint { } // 以前に作成されたパーソナルメモがあるかどうか確認 - const previousMemo = await this.userMemoRepository.findOneBy({ + const previousMemo = await this.userMemosRepository.findOneBy({ userId: me.id, targetUserId: target.id, }); if (!previousMemo) { - await this.userMemoRepository.insert({ + await this.userMemosRepository.insert({ id: this.idService.genId(), userId: me.id, targetUserId: target.id, memo: ps.memo, }); } else { - await this.userMemoRepository.update(previousMemo.id, { + await this.userMemosRepository.update(previousMemo.id, { userId: me.id, targetUserId: target.id, memo: ps.memo, -- cgit v1.2.3-freya From e1f9ab77f86f5a12091c864cdb502970715cd46e Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 19 Apr 2023 21:24:31 +0900 Subject: feat: Server rules (#10660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): サーバールールのデザイン調整 * enhance(frontend): i18n * enhance(frontend): 利用規約URLの設定を「モデレーション」ページへ移動 * enhance(frontend): サーバールールのデザイン調整 * Update CHANGELOG.md * 不要な差分を削除 * fix(frontend): lint * ui tweak * test: add stories * tweak * test: bind args * test: add interaction tests * fix bug * Update packages/frontend/src/pages/admin/server-rules.vue Co-authored-by: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> * Update misskey-js.api.md * chore: windowを明示 * :art: * refactor * :art: * :art: * fix e2e test * :art: * :art: * fix icon * fix e2e --------- Co-authored-by: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> Co-authored-by: Acid Chicken (硫酸鶏) --- CHANGELOG.md | 1 + cypress/e2e/basic.cy.js | 12 + locales/ja-JP.yml | 9 +- .../backend/migration/1681400427971-serverRules.js | 11 + packages/backend/src/models/entities/Meta.ts | 7 + .../src/server/api/endpoints/admin/update-meta.ts | 5 + packages/backend/src/server/api/endpoints/meta.ts | 2 + packages/frontend/.storybook/generate.tsx | 1 + packages/frontend/src/components/MkFolder.vue | 8 +- packages/frontend/src/components/MkModal.vue | 6 - packages/frontend/src/components/MkModalWindow.vue | 5 +- packages/frontend/src/components/MkSignup.vue | 263 -------------------- .../src/components/MkSignupDialog.form.vue | 272 +++++++++++++++++++++ .../MkSignupDialog.rules.stories.impl.ts | 94 +++++++ .../src/components/MkSignupDialog.rules.vue | 114 +++++++++ .../frontend/src/components/MkSignupDialog.vue | 45 +++- packages/frontend/src/components/MkSwitch.vue | 2 +- packages/frontend/src/pages/about.vue | 2 +- packages/frontend/src/pages/admin/moderation.vue | 9 + packages/frontend/src/pages/admin/server-rules.vue | 128 ++++++++++ packages/frontend/src/pages/admin/settings.vue | 8 - packages/frontend/src/router.ts | 4 + packages/frontend/src/store.ts | 4 +- packages/misskey-js/etc/misskey-js.api.md | 1 + packages/misskey-js/src/entities.ts | 1 + 25 files changed, 719 insertions(+), 295 deletions(-) create mode 100644 packages/backend/migration/1681400427971-serverRules.js delete mode 100644 packages/frontend/src/components/MkSignup.vue create mode 100644 packages/frontend/src/components/MkSignupDialog.form.vue create mode 100644 packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts create mode 100644 packages/frontend/src/components/MkSignupDialog.rules.vue create mode 100644 packages/frontend/src/pages/admin/server-rules.vue (limited to 'packages/backend/src/models') diff --git a/CHANGELOG.md b/CHANGELOG.md index 979caa8a89..ec626d1447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Node.js 18.6.0以上が必要になりました ### General +- 新規登録前に簡潔なルールをユーザーに表示できる、サーバールール機能を追加 - ユーザーへの自分用メモ機能 * ユーザーに対して、自分だけが見られるメモを追加できるようになりました。 (自分自身に対してもメモを追加できます。) diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index 8dc07c1800..b0c3d4c19a 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -52,6 +52,12 @@ describe('After setup instance', () => { cy.intercept('POST', '/api/signup').as('signup'); cy.get('[data-cy-signup]').click(); + cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); + cy.get('[data-cy-signup-rules-notes] [data-cy-folder-header]').click(); + cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); + cy.get('[data-cy-signup-rules-continue]').click(); + cy.get('[data-cy-signup-submit]').should('be.disabled'); cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-submit]').should('be.disabled'); @@ -71,6 +77,12 @@ describe('After setup instance', () => { // ユーザー名が重複している場合の挙動確認 cy.get('[data-cy-signup]').click(); + cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); + cy.get('[data-cy-signup-rules-notes] [data-cy-folder-header]').click(); + cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); + cy.get('[data-cy-signup-rules-continue]').click(); + cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-password-retype] input').type('alice1234'); diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9a526e67e9..cd9521a9f8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -263,9 +263,10 @@ noMoreHistory: "これより過去の履歴はありません" startMessaging: "チャットを開始" nUsersRead: "{n}人が読みました" agreeTo: "{0}に同意" +agree: "同意する" agreeBelow: "下記に同意する" basicNotesBeforeCreateAccount: "基本的な注意事項" -tos: "利用規約" +termsOfService: "利用規約" start: "始める" home: "ホーム" remoteUserCaution: "リモートユーザーのため、情報が不完全です。" @@ -1010,6 +1011,12 @@ stackAxis: "スタック方向" vertical: "縦" horizontal: "横" position: "位置" +serverRules: "サーバールール" +pleaseConfirmBelowBeforeSignup: "このサーバーに登録する前に、以下を確認してください。" +continue: "続ける" + +_serverRules: + description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。" _accountMigration: moveTo: "このアカウントを新しいアカウントに引っ越す" diff --git a/packages/backend/migration/1681400427971-serverRules.js b/packages/backend/migration/1681400427971-serverRules.js new file mode 100644 index 0000000000..2364e8e1d2 --- /dev/null +++ b/packages/backend/migration/1681400427971-serverRules.js @@ -0,0 +1,11 @@ +export class ServerRules1681400427971 { + name = 'ServerRules1681400427971' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "serverRules" character varying(280) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverRules"`); + } +} diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index 2e4f90b57f..c8df141a0b 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -405,4 +405,11 @@ export class Meta { default: { }, }) public policies: Record; + + @Column('varchar', { + length: 280, + array: true, + default: '{}', + }) + public serverRules: string[]; } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 11de29bf83..ae2fc84b50 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -94,6 +94,7 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, + serverRules: { type: 'array', items: { type: 'string' } }, }, required: [], } as const; @@ -387,6 +388,10 @@ export default class extends Endpoint { set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; } + if (ps.serverRules !== undefined) { + set.serverRules = ps.serverRules; + } + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 37974ce2a3..a5cb3fa7ee 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -310,6 +310,8 @@ export default class extends Endpoint { translatorAvailable: instance.deeplAuthKey != null, + serverRules: instance.serverRules, + policies: { ...DEFAULT_POLICIES, ...instance.policies }, mediaProxy: this.config.mediaProxy, diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index bb98805743..dbe9729170 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -398,6 +398,7 @@ Promise.all([ glob('src/components/global/*.vue'), glob('src/components/Mk{A,B}*.vue'), glob('src/components/MkGalleryPostPreview.vue'), + glob('src/components/MkSignupServerRules.vue'), glob('src/pages/user/home.vue'), ]) .then((globs) => globs.flat()) diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 58cc0de5c8..fd070a5f13 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -1,8 +1,8 @@